Skip to content

prerelease-link.sh: repin rewrites the whole manifest — indentation and \uXXXX escapes are normalized #324

Description

@dmealing

Found while evaluating 0.24.0-rc.3 in an adopter project.

Summary

repin_npm() writes with JSON.stringify(p, null, 2). For any manifest not already 2-space indented, this reformats every line — turning a one-line version bump into a whole-file diff.

Observed

  • A tab-indented package.json: 100 changed lines (50− / 50+) for a single dependency pin.
  • A second manifest had a \u2192 escape inside its description string unescaped to a literal . Semantically identical JSON, but another spurious diff line.

Why it matters

The function already recognises this hazard. Its own comment reads:

Only WRITE when something changed. JSON.stringify normalizes indentation and line endings, so an unconditional write reformats a file it had no reason to touch — a whole-file diff (LF over CRLF) on a manifest with no vendor deps.

The guard built from that reasoning is if (n) plus CRLF/LF preservation. But the same normalization applies to indentation and \uXXXX escapes inside a file that legitimately does change, and neither is preserved.

The practical cost lands on the rule the tooling is trying to enforce. link warns "never commit what this writes to a tracked file" — but a reviewer cannot spot the one pin among 50 reformatted lines, so the reformat actively works against the review that is supposed to catch an accidental commit. It also churns any repo whose formatter disagrees with 2-space JSON.

Suggested fix

Skip the parse/serialize round-trip on write. A targeted textual substitution over the raw source preserves the file byte-for-byte apart from the version literals:

  • find "<scope>/<pkg>": "<version>" within the dependency blocks, replace only the version literal.

If a structural edit is preferred, detect the file's existing indentation (tabs vs n spaces, from the first indented line) and reuse it, and re-escape non-ASCII to match the input.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions