fix(vendor): write pnpm overrides to pnpm-workspace.yaml for pnpm >= 11 - #174
fix(vendor): write pnpm overrides to pnpm-workspace.yaml for pnpm >= 11#174Mikola Lysenko (mikolalysenko) wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Migration wipe of revert wiring
- Fixed by returning AlreadyPatched when package.json and lock are in sync, preventing replacement of ledger entry that would lose revert wiring records.
Or push these changes by commenting:
@cursor push d2439191b3
Preview (d2439191b3)
diff --git a/crates/socket-patch-core/src/vendor/pnpm_lock.rs b/crates/socket-patch-core/src/vendor/pnpm_lock.rs
--- a/crates/socket-patch-core/src/vendor/pnpm_lock.rs
+++ b/crates/socket-patch-core/src/vendor/pnpm_lock.rs
@@ -271,11 +271,14 @@
Err(e) => return done_failure(purl, format!("{PNPM_WORKSPACE} surgery failed: {e}")),
};
- if !pkg_changed && !lock_changed && ws_edit.new_text.is_none() {
- // Everything already carries this uuid + the packed integrity: the
- // project is in sync. The tarball re-pack above was byte-identical
- // by determinism; synthesize AlreadyPatched and record nothing (the
- // existing ledger entry stays authoritative).
+ if !pkg_changed && !lock_changed {
+ // package.json and the lock already carry this uuid + the packed
+ // integrity: the core wiring is in sync. The tarball re-pack above was
+ // byte-identical by determinism. Even if the workspace file (pnpm >= 11
+ // surface) needs first-time creation during migration, that must not
+ // replace the ledger entry and wipe package.json/lock revert wiring.
+ // Synthesize AlreadyPatched and record nothing (the existing ledger
+ // entry stays authoritative).
return done(
already_patched_result(purl, &project_root.join(&rel_tgz), &record.files),
None,You can send follow-ups to the cloud agent here.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit a23abb1. Configure here.
| created_overrides_table, | ||
| created_pnpm_table, | ||
| created_workspace_file: ws_edit.created_file, | ||
| created_workspace_overrides: ws_edit.created_overrides, |
There was a problem hiding this comment.
Migration wipe of revert wiring
High Severity
When only pnpm-workspace.yaml needs writing (the upgrade path for projects already vendored before this fix), the new ledger entry keeps only the workspace wiring. persist_vendor_entry replaces the prior entry, so package.json and pnpm-lock.yaml originals are dropped and vendor --revert can no longer restore them.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit a23abb1. Configure here.
…pm >= 11 pnpm 11 stopped reading `overrides` from package.json's `pnpm` field — it moved to `pnpm-workspace.yaml` (https://pnpm.io/settings). The pnpm vendor backend wrote only `package.json` `pnpm.overrides`, so pnpm 11 ignored it and a frozen install of the committable artifact refused with `ERR_PNPM_LOCKFILE_CONFIG_MISMATCH` — the lock carried an `overrides:` section but pnpm resolved none from config. The vendored tarball and lock were correct (they even pass pnpm 11's tarball-URL supply-chain policy); only the override LOCATION was wrong. Vendoring now mirrors the same versioned `<name>@<version>` → `file:` selector into `pnpm-workspace.yaml` alongside the existing package.json override, so whichever surface the installed pnpm reads matches the lock's `overrides:` section: - no workspace file → create one with a root-only `packages: ['.']` list (pnpm 9 refuses a workspace file whose `packages` field is missing/empty; `.` is the sole importer already and cannot glob a stray `packages/` subtree into the workspace the way `packages/*` would) plus the `overrides:` block; - existing file without `overrides:` → append the section (packages untouched); - existing `overrides:` section → insert our key (or take over a user's exact pin), fail-closed on a conflicting same-name override or an inline mapping. package.json `pnpm.overrides` is kept for pnpm 9/10. Verified across real pnpm 9.15.9 / 10.34.5 / 11.21.0: a cold `pnpm install --frozen-lockfile --offline` from only the committable files installs the vendored (patched) bytes with no config mismatch. `vendor --revert` deletes a file it created (when still the bare scaffold) or splices its override back out of one it edited; the three surfaces are committed override-first / lock-last so a lock-write failure never leaves a desynced override behind. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… surface The pnpm >= 11 support (#174) mirrors the tarball override into `pnpm-workspace.yaml` in addition to `package.json` + `pnpm-lock.yaml`. On the UPGRADE path — a project vendored by the pre-workspace code, then re-vendored under the new code — `package.json` and `pnpm-lock.yaml` already carry the override, so `vendor_pnpm` rewrites neither and its fresh `VendorEntry.wiring` names ONLY the newly created `pnpm-workspace.yaml` surface. The CLI vendor flow re-invokes the backend on every installed package (there is no ledger short-circuit; the `already_vendored` classification only happens AFTER the backend runs and returns `AlreadyPatched`). So `persist_vendor_entry` would replace the prior ledger entry wholesale with the workspace-only entry, dropping the `package.json` + `pnpm-lock.yaml` pre-vendor originals the FIRST vendoring recorded. `vendor --revert` could then restore only the workspace file, leaving the override wired into the other two surfaces forever. Fix: extract the re-vendor reconciliation into `vendor::carry_forward_wiring` and extend it to (1) union the prior entry's wiring records for surfaces THIS run left in sync, and (2) OR-merge the pnpm "created this table/file/section" bookkeeping — both scoped to a same-uuid re-vendor so a new-uuid re-vendor still rewires every surface fresh. The existing original-fill and go-takeover carry-forward are unchanged. Revert now byte-restores all three surfaces on the upgrade path. Regression test `revendor_upgrade_adds_workspace_and_revert_restores_all_three_surfaces` stages a pre-workspace vendored project, re-vendors (adding only the workspace mirror), reconciles, then reverts and asserts package.json + pnpm-lock.yaml + pnpm-workspace.yaml are all byte-restored to their pre-vendor originals. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
a23abb1 to
1af04da
Compare
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>



Problem
In vendored mode for the npm/pnpm ecosystem, the CLI pinned the vendored tarball by writing a
pnpm.overridesentry intopackage.json. pnpm >= 11 no longer readsoverridesfrom the package.jsonpnpmfield — it moved topnpm-workspace.yaml(https://pnpm.io/settings). So pnpm 11 ignored the override, resolved no overrides from config, and a frozen install of the committable artifact refused:The vendored tarball + lock were correct (they even pass pnpm 11's tarball-URL supply-chain policy) — only the override location was wrong. Surfaced by PR #169's real-production vendored e2e (
e2e_vendored_production.rs, pnpm leg) and documented indocs/testing/vendored-production-e2e.md.Reproduced on this host (pnpm 11.21.0):
pnpm add minimist@1.2.2→scan --mode vendored→ copy only the committable files → coldpnpm install --frozen-lockfile --offline→ERR_PNPM_LOCKFILE_CONFIG_MISMATCH.Fix
The pnpm vendor backend now mirrors the same versioned
<name>@<version>→file:<rel-tgz>selector intopnpm-workspace.yaml, alongside the existing package.json override, so whichever surface the installed pnpm reads matches the lock'soverrides:section (what pnpm compares for the mismatch):packages: ['.']list (pnpm 9 refuses a workspace file whosepackagesfield is missing/empty;.is already the sole importer and, unlikepackages/*, cannot glob a straypackages/subtree into the workspace) plus theoverrides:block;overrides:→ append the section,packages:untouched;overrides:section → insert our key (or take over a user's exact-version pin), fail-closed on a conflicting same-name override or an inline/flow mapping.package.json
pnpm.overridesis kept for pnpm 9/10 back-compat. Edits are line splices (byte-stable), tracked with wiring records +PnpmMetaflags. The three surfaces are committed override-first / lock-last so a lock-write failure never strands a desynced override.vendor --revertdeletes a file it created (when still the bare scaffold) or splices its override back out of one it edited (byte-for-byte), restoring a taken-over value and dropping an emptied section it created. Scoped to the pnpm vendored path; no other ecosystem touched.Test
Verified on real pnpm 9.15.9 / 10.34.5 / 11.21.0: a cold
pnpm install --frozen-lockfile --offlinefrom only the committable files installs the vendored (patched)minimist@1.2.2bytes — noERR_PNPM_LOCKFILE_CONFIG_MISMATCH— andvendor --revertremoves the createdpnpm-workspace.yamland restorespackage.json/pnpm-lock.yaml.vendor::pnpm_lock): create-scaffold + revert-deletes; append-section + byte-restore; insert-beside-existing + remove-only-ours; inline-mapping refused; conflicting-override refused; commit unwind deletes a created workspace file.state.rsround-trip covers the newPnpmMetafields. All 602vendor::*lib tests pass.e2e_vendor_pnpm_build.rs(real corepack pnpm@10/9): now asserts the workspace file is created with the override + root-onlypackages:, is copied into the fresh-checkout proof, stays byte-stable on re-vendor, and is deleted on revert. Passes.e2e_vendored_production.rs(real Socket production + real npm registry, host pnpm 11): the pnpm leg's xfail workaround is removed — the frozen install now succeeds directly and byte-checks the patched bytes.pnpm_vendored_install_proofpasses.cargo build+cargo clippyclean (the only clippy warnings are pre-existingdoc_lazy_continuationnotes in an unrelated module-doc header, present onmain).🤖 Generated with Claude Code
Note
Medium Risk
Changes pnpm vendoring commit/revert behavior and creates or edits
pnpm-workspace.yaml, which affects install correctness across pnpm 9–11; scope is limited to the pnpm vendor path with extensive tests.Overview
Fixes pnpm >= 11 frozen installs failing with
ERR_PNPM_LOCKFILE_CONFIG_MISMATCHbecause overrides were only written topackage.jsonpnpm.overrides, while pnpm 11 reads them frompnpm-workspace.yaml.The pnpm vendor backend now mirrors the same versioned
<name>@<version>→file:override intopnpm-workspace.yaml(still updatingpackage.jsonfor pnpm 9/10). If no workspace file exists, it creates one with a root-onlypackages: ['.']list plusoverrides:so pnpm 9 accepts the file. Edits use line splices for byte-stable revert;commit_surfacesunwinds override files if the lock write fails.vendor --revertdeletes a scaffold file it created or removes only its override from an edited file.PnpmMetagainscreatedWorkspaceFile/createdWorkspaceOverrides. E2E and production tests no longer use a manual workspace workaround; docs mark the pnpm 11 gap as fixed.Reviewed by Cursor Bugbot for commit a23abb1. Configure here.