Skip to content

Vendored pnpm rewrites a CRLF package.json as LF, and vendor --revert does not restore it #662

Description

[agent] Filed by the scheduled architecture audit routine (ecosystems and formats). Register: register comment.

Kind: bug. Source: new finding, register E53 (a sibling of the JSON-writer drift that #357 fixed for npm locks; review Part 4.4 "JSON re-serialization, three strategies").

Problem

Three vendored JS backends write the project's root package.json. npm and yarn berry render it through vendor::common::JsonLayout, which keeps the BOM, indent, line ending and trailer. The pnpm backend (v9 and legacy, which share vendor_pnpm_dialect / revert_pnpm_dialect since #583) still uses detect_indent + common::serialize_json, which always writes LF and no BOM:

For comparison, yarn berry: yarn_berry_lock.rs JsonLayout is pinned by crlf_bom_and_newline_shapes_vendor_and_revert_byte_exact; npm by vendor_and_revert_keep_crlf_tab_and_bom_lock_layout (npm_lock.rs#L2990).

Repro (unit probe on 045d7ec, run twice, not committed)

Using the existing P1_BEFORE_PKG / P1_BEFORE_LOCK fixture in pnpm_lock.rs tests, with only package.json converted:

let crlf_pkg = P1_BEFORE_PKG.replace('\n', "\r\n");
let fx = fixture_with(&crlf_pkg, P1_BEFORE_LOCK).await;
let (_, entry, _) = expect_done(fx.vendor(false).await);   // package.json now has 0 CRLF
revert_pnpm(&entry.unwrap(), fx.root(), false).await;      // success
fx.read(PACKAGE_JSON).await == crlf_pkg                    // false: still all LF

Output (both runs): after vendor CRLF count=0, after revert restored==original: false, CRLF count=0.

With a UTF-8 BOM in front of the same package.json (pnpm and npm install from it), vendoring refuses with vendor_pkg_json_unsupported: package.json is not a JSON object; cannot add pnpm.overrides, a misleading reason.

Impact

  • A Windows/autocrlf pnpm project gets a whole-file package.json diff from scan --mode vendored / vendor, and vendor --revert / remove leave it reformatted. README promises a byte-exact revert.
  • A BOM package.json can't be vendored, and the error names the wrong cause.
  • The pnpm lock itself refuses CRLF on purpose (vendor_lockfile_crlf_unsupported), so this is only about package.json. Size: ~10 production lines.

Proposed change

  • Parse package.json with common::parse_json_manifest in pnpm read_project (and in the revert's pkg_state read).
  • Render it with JsonLayout::of(original).render(&pkg) on vendor and revert; drop the detect_indent + serialize_json pair and the indent field of pkg_state.

Size and scope

vendor/pnpm_lock.rs only, ~15 production lines plus tests. Out of scope: the lock's CRLF refusal, and hosted pnpm (it doesn't write package.json).

Acceptance criteria

  • Vendor + revert of a CRLF, a BOM and a BOM+CRLF+tab package.json (v9 and legacy 6.0 fixtures) is byte-exact, and the vendored file differs from the original only in pnpm.overrides.
  • revert_round_trips_both_files_and_removes_the_artifact and the crlf_lock_refuses_naming_line_endings refusal stay green.
  • No remaining serialize_json( call writes a package.json (a grep in review is enough).

Dependencies

None. Touches only vendor/pnpm_lock.rs; no open PR changes that file.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent:claimedagent:triagedarch-auditFiled by a scheduled architecture audit routine (see the architecture review discussion)bugSomething isn't workingpm:pnpmpnpmpriority:p1

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions