Fix JSON writers dropping CRLF/BOM/indent (#324, #351) - #357
Mikola Lysenko (mikolalysenko) wants to merge 7 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
Regression tests for #324 (npm lock CRLF, tab indent and BOM through hosted rewrite, rollback, vendor and vendor --revert) and #351 (composer.json CRLF and string escapes through setup and setup --remove). The composer tests pass with the splice-based editor added in this commit; the npm ones fail until the next. Assisted-by: Claude Code:claude-opus-5-5
The hosted and vendored npm lock rewriters, rollback and vendor --revert now render the lock in its own layout (BOM, indent, line ending, trailer) through JsonLayout, so a CRLF or tab-indented lock gets a two-line diff and undo restores the original bytes. A UTF-8 BOM lock, which npm installs from, is now read past its BOM instead of being skipped (hosted) or refused with the wrong code (vendored), and VEX discovery reads it too. Fixes #324 Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
Vendors a CRLF lock with a UTF-8 BOM, proves a fresh npm ci installs the patched bytes from it, and that vendor --revert restores the lock byte for byte (#324). Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
The v5 consolidation (#277) removed `setup` (with its Composer hook) and replaced the takeover-ledger npm revert with the upstream restorer in patch/redirect/upstream/npm.rs, which still re-serialized the lock with LF and a 2-space indent and choked on a BOM. Port the revert half of the fix there (parse past the BOM, write in the lock's own layout) and pin it with a CRLF/tab/BOM round-trip test in upstream_restore_golden. Drop the #351 composer.json splice and its lock_text refactor, which only served the removed `setup`; keep both CHANGELOG entries. Co-Authored-By: Claude <[email protected]> Claude-Session: https://claude.ai/code/session_01GQoii5oP1pwcJh5mzzo1HU
|
bugbot run Generated by Claude Code |
|
[burn-down agent] Ready for review on Generated by Claude Code |
Resolve the redirect/mod.rs import conflict by keeping both the JsonLayout helpers this branch needs and main's npm_origin imports. Co-Authored-By: Claude <[email protected]>
|
bugbot run Generated by Claude Code |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit a6b975b. Configure here.
|
[burn-down agent] Ready for review on
Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #324
Fixes #351
Summary
npm locks and
composer.jsonnow keep their own layout when socket-patch edits them, and the undo commands put the original bytes back.package-lock.json/npm-shrinkwrap.jsonwith LF endings:scan --mode hosted, itsrollback,scan --mode vendoredandvendor --revert. Hosted mode also forced a 2-space indent. They now render the lock through the existingvendor::common::JsonLayout, which keeps the BOM, indent, line ending and trailer.redirect_npm_lock_unparseablein hosted mode and refused asvendor_lockfile_version_unsupportedin vendored mode. Both now read past the BOM and keep it.vex::discover::parse_json) and the npm lock inventory read BOM locks too, so a wired BOM lock is still attested.setup/setup --removeno longer re-serialize the wholecomposer.json. A newsplice_scriptsedits only the top-levelscriptsvalue, the way Composer's ownJsonManipulatordoes:render_in_style_of, factored out of the composer.lock entry splicer.scriptsis appended as the last member, and removing it cuts exactly that span back out. That's whysetup+setup --removeis byte-exact.JsonLayout.Fixedentry. README already promised a byte-for-bytevendor --revert/remove; this makes that true for CRLF, tab and BOM locks.Root cause
The npm lock writers and Composer
setupre-serialized the parsed document through either a fixed 2-space/LF serializer (patch/redirect/mod.rsserialize_json) ordetect_indent+serialize_json. Neither keeps line endings or a BOM.serde_json::from_str/from_slicealso reject a BOM outright. The layout-preservingJsonLayoutalready existed forpackage.jsonand yarn berry, but these writers never used it. The escape half of #351 needed a span-level edit, because any parse and re-serialize normalizes escapes.Test evidence
mainresolved/integritychange)patch::redirect::tests::npm_lock_rewrite_keeps_crlf_tabs_and_bomrollbackbyte-exact: CRLF, tabs, BOMpatch::redirect::takeover::tests::npm_revert_restores_crlf_tab_and_bom_locks_byte_for_bytevendor --revertbyte-exactvendor::npm_lock::tests::vendor_and_revert_keep_crlf_tab_and_bom_lock_layoutvex::discover::npm::tests::bom_prefixed_wired_lock_is_discoveredvendor→ freshnpm ciinstalls patched bytes →vendor --revertbyte-exacte2e_vendor_npm_build::npm_vendor_keeps_a_crlf_bom_lock_and_reverts_it_byte_for_bytesetup::composer::tests::test_round_trip_preserves_crlf_line_endingstest_round_trip_preserves_string_escapesscripts, CRLF + escapes, no trailing newlinetest_round_trip_with_user_scripts_keeps_crlf_and_escapesFor the npm and VEX rows, red was checked by running the tests before the fix commit, and for VEX by reverting
parse_jsonalone. The Composer tests ran red beforesplice_scriptswas written.SOCKET_PATCH_NPM_E2E_REQUIRED=1 cargo test -p socket-patch-cli --all-features --test e2e_vendor_npm_build --test e2e_redirect_npm_build -- --include-ignored: 14 + 15 passed (real npm 10.9.7, Node 22).cargo test -p socket-patch-cli --all-features --test e2e_composer --test setup_matrix_composer -- --include-ignored: all passed.e2e_vendor_composer_build: 15 passed, 2 failed. The 2 failures (composer_vendor_fast_path_heals_legacy_copy,composer_vendor_keeps_files_mirror_filters_would_drop) are sandbox-only. Composer here fell back to a source install, so the vendored copy carries.git/HEAD. That's the separate Composer vendor copies a --prefer-source package's .git into .socket/vendor, so git commits it as an embedded repo and a fresh clone installs an empty package #355, and this PR doesn't touch that code. Both pass in CI.cargo clippy --workspace --all-features -- -D warnings: clean.cargo fmt --all -- --check: the new code is rustfmt-clean. The remaining diffs are already onmain, in files and hunks this PR doesn't touch.cargo test --workspace --all-features --no-fail-fast: 10,400 passed and 22 failed. The 22 are the same sandbox-only set reported on Fix #258: state the real Maven Trusted Checksums floor #322, Fix Poetry venv discovery to match Poetry (#327, #329) #330, Fix npm VEX attesting packages with an unpatched bundled copy (#325) #337 and Fix npm rewiring git/URL/file lock entries (#326) #345: write-failure and permission tests that fail because the sandbox runs as root, plus the peak-RSSstage_local_artifact_caps_oversized_artifact_before_buffering.downgradeandcanary). Bugbot's review of 0dbe6cc found no issues.Notes
rewrite_one_npm_lockandvendor/npm_lock.rsnext to Fix npm rewiring git/URL/file lock entries (#326) #345, but at different lines: the parse and serialize calls here, entry selection there. Whichever lands second should need at most a trivial merge.package.jsonwrites and the Pipenv / composer.lock whole-document fallbacks still usedetect_indent+serialize_json. No open issue reports them. They're candidates for the sameJsonLayoutswitch in a follow-up.🤖 Generated with Claude Code
https://claude.ai/code/session_01GZ6Poya3NwsgeV8gN7WL3Q
Note
Medium Risk
Changes shared npm lock parse/serialize paths used by hosted scan, rollback, vendoring, and VEX; layout bugs could break installs or byte-exact revert, but behavior is narrowly scoped to formatting preservation with broad test coverage.
Overview
npm lock edits no longer normalize the whole file. Hosted redirect, hosted rollback, vendored
vendor, andvendor --revertused to pretty-printpackage-lock.json/npm-shrinkwrap.jsonwith LF (and hosted mode forced 2-space indent), so CRLF/tab/BOM locks got noisy diffs and undo did not restore original bytes.Parsing now strips a leading UTF-8 BOM like npm (
parse_json_text/parse_json_manifest), and writes go throughJsonLayoutviaserialize_json_likeso onlyresolved/integrity(and related) values change. VEX discovery and npm lock inventory accept BOM-prefixed locks instead of treating them as invalid JSON.Regression coverage adds unit tests for CRLF/tabs/BOM shapes, golden hosted round-trips, vendored vendor/revert layout, BOM VEX discovery, and a real-npm e2e for CRLF+BOM vendor →
npm ci→ byte-exact revert.Reviewed by Cursor Bugbot for commit a6b975b. Configure here.
Generated by Claude Code