Skip to content

fix: skip package.json writting when content is deeply equal#913

Merged
sxzz merged 2 commits intomainfrom
ocavue/update-dev
Apr 15, 2026
Merged

fix: skip package.json writting when content is deeply equal#913
sxzz merged 2 commits intomainfrom
ocavue/update-dev

Conversation

@ocavue
Copy link
Copy Markdown
Collaborator

@ocavue ocavue commented Apr 14, 2026

  • This PR contains AI-generated code, but I have carefully reviewed it myself. Otherwise, my PR may be closed.

Description

When exports.devExports is enabled, tsdown will try to update package.json if it's outdated.

Previously, the outdated status is using string comparing here.
In this PR, the outdated check is using isDeepStrictEqual from node:utils (added in Node.js v9).

These changes ensure that tsdown won't rewrite package.json if the package.json somehow has weird formatting, such as specifically ordering in publishConfig.exports, or mixing the use of spaces and \t as indentation.

Linked Issues

Additional context

@netlify
Copy link
Copy Markdown

netlify bot commented Apr 14, 2026

Deploy Preview for tsdown-main ready!

Name Link
🔨 Latest commit 2b2f73c
🔍 Latest deploy log https://app.netlify.com/projects/tsdown-main/deploys/69df1d6481ea2d00089f8176
😎 Deploy Preview https://deploy-preview-913--tsdown-main.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 14, 2026

Open in StackBlitz

tsdown

pnpm add https://pkg.pr.new/tsdown@913 -D
npm i https://pkg.pr.new/tsdown@913 -D
yarn add https://pkg.pr.new/tsdown@913.tgz -D

create-tsdown

pnpm add https://pkg.pr.new/create-tsdown@913 -D
npm i https://pkg.pr.new/create-tsdown@913 -D
yarn add https://pkg.pr.new/create-tsdown@913.tgz -D

@tsdown/css

pnpm add https://pkg.pr.new/@tsdown/css@913 -D
npm i https://pkg.pr.new/@tsdown/css@913 -D
yarn add https://pkg.pr.new/@tsdown/css@913.tgz -D

@tsdown/exe

pnpm add https://pkg.pr.new/@tsdown/exe@913 -D
npm i https://pkg.pr.new/@tsdown/exe@913 -D
yarn add https://pkg.pr.new/@tsdown/exe@913.tgz -D

tsdown-migrate

pnpm add https://pkg.pr.new/tsdown-migrate@913 -D
npm i https://pkg.pr.new/tsdown-migrate@913 -D
yarn add https://pkg.pr.new/tsdown-migrate@913.tgz -D

commit: 2b2f73c

@ocavue ocavue marked this pull request as ready for review April 14, 2026 11:26
@ocavue ocavue requested review from Copilot and sxzz April 14, 2026 11:26
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to prevent unnecessary package.json rewrites (notably when exports.devExports is enabled) by switching the “outdated” check from string comparison to deep structural equality, while preserving existing formatting details (indentation, EOLs, trailing newline) when writing.

Changes:

  • Added a writeJsonFile helper that reads existing JSON, preserves formatting, and skips writes when content is deeply equal.
  • Updated writeExports to use writeJsonFile instead of manual stringify/compare/write logic.
  • Added Vitest coverage for the new JSON writing helper (creation, no-rewrite, formatting preservation).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
src/utils/json.ts Introduces JSON write helper with formatting preservation and deep-equality skip.
src/utils/json.test.ts Adds tests validating no-rewrite behavior and formatting preservation.
src/features/pkg/exports.ts Replaces manual package.json rewrite logic with writeJsonFile.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/utils/json.ts
Comment on lines +25 to +28
if (originalContent && isDeepStrictEqual(originalContent, content)) {
// The content is the same. We just return without updating the file format
return
}
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deep-equality short-circuit is guarded by a truthy check (originalContent && ...), which means valid JSON roots like null, false, 0, or "" will never be treated as equal and will be rewritten unnecessarily. Also, callers may include undefined properties (e.g. objects that rely on JSON.stringify omitting them), which will make isDeepStrictEqual fail even when the on-disk JSON and the serialized output are identical. Consider tracking whether parsing succeeded separately (instead of truthiness) and normalizing content to JSON-serializable form (e.g. by removing undefined keys) before comparing.

Copilot uses AI. Check for mistakes.
Comment thread src/utils/json.ts
Comment on lines +21 to +22
} catch {
// File doesn't exist or isn't valid JSON, we'll overwrite it with our content
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bare catch {} swallows all errors from readFileSync/JSON.parse, including permission errors, transient IO failures, and other unexpected conditions. That can mask real operational problems and then proceed to overwrite the file with default formatting. It would be safer to only ignore the specific cases you expect (e.g. missing file / invalid JSON) and rethrow anything else.

Suggested change
} catch {
// File doesn't exist or isn't valid JSON, we'll overwrite it with our content
} catch (error) {
if (error instanceof SyntaxError) {
// The file isn't valid JSON, we'll overwrite it with our content
} else if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
// The file doesn't exist, we'll overwrite it with our content
} else {
throw error
}

Copilot uses AI. Check for mistakes.
Comment thread src/utils/json.ts
Comment on lines +30 to +38
let jsonString = JSON.stringify(content, null, originalIndent)
if (originalEOL !== '\n') {
jsonString = jsonString.replaceAll('\n', originalEOL)
}
if (originalHasTrailingNewline) {
jsonString += originalEOL
}

writeFileSync(filePath, jsonString, 'utf8')
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSON.stringify can return undefined for inputs like undefined, functions, or symbols. In that case writeFileSync will throw with a less helpful error. Since this helper is exported and accepts unknown, consider narrowing the parameter type to JSON-serializable values and/or validating the result of JSON.stringify to throw a clearer error before writing.

Copilot uses AI. Check for mistakes.
Comment thread src/utils/json.test.ts
writeJsonFile(filePath, { foo: 'bar' })
expect(readFileSync(filePath, 'utf8')).toBe(original)
})

Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There isn't a test covering the case where content contains properties with value undefined (which JSON.stringify omits). With the current deep-equality check, this can cause a rewrite even if the serialized JSON would be identical to the existing file (relevant for callers that “remove” fields via foo: undefined). Adding a fixture/test for this scenario would prevent regressions.

Suggested change
test('does not rewrite when content only adds undefined properties', async (context) => {
const original = '{\n "foo": "bar"\n}'
const { testDir } = await writeFixtures(context, { 'pkg.json': original })
const filePath = path.join(testDir, 'pkg.json')
writeJsonFile(filePath, { foo: 'bar', removed: undefined })
expect(readFileSync(filePath, 'utf8')).toBe(original)
})

Copilot uses AI. Check for mistakes.
@sxzz sxzz merged commit d8e1c1f into main Apr 15, 2026
15 checks passed
@sxzz sxzz deleted the ocavue/update-dev branch April 15, 2026 05:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants