Fix empty patch hash on pnpm 11 lockfiles - #202
Conversation
pnpm 11 simplified the lockfile patchedDependencies format from
Record<string, { path, hash }> to Record<string, string> (selector to
hash). @pnpm/lockfile-file@9 passes that field through unchanged, so
reading a pnpm 11 lockfile yields bare hash strings. copyPatches only
read `.hash`, leaving the hash empty and causing pnpm to reject the
isolated install with ERR_PNPM_LOCKFILE_CONFIG_MISMATCH.
Read the hash from either the string (pnpm 11) or the object (pnpm <=10)
shape, and add regression tests for both formats.
Also exclude nested git worktrees (.worktrees/) from vitest and oxlint
so they no longer run another branch's tests and lint.
Fixes #201
WalkthroughUpdated 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/lib/patches/copy-patches.test.ts (1)
657-775: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider extracting shared test setup.
Both new tests share ~25 lines of nearly identical mock setup (target manifest,
readTypedYamlSync,readTypedJson,filterPatchedDependencies,fs.existsSync,usePackageManager). A small helper orbeforeEachwould reduce duplication and make it easier to add future format variants.♻️ Suggested shared setup helper
+function mockCopyPatchesCommonDeps() { + readTypedYamlSync.mockReturnValue({ + patchedDependencies: { + "lodash@4.17.21": "patches/lodash.patch", + }, + }); + readTypedJson.mockResolvedValue({ + name: "root", + version: "1.0.0", + } as PackageManifest); + filterPatchedDependencies.mockReturnValue({ + "lodash@4.17.21": "patches/lodash.patch", + }); + fs.existsSync.mockReturnValue(true); + usePackageManager.mockReturnValue({ + name: "pnpm", + majorVersion: 9, + version: "9.0.0", + packageManagerString: "pnpm@9.0.0", + }); +} + it("should read the patch hash from the pnpm 11 string lockfile format (regression: issue `#201`)", async () => { const targetManifest: PackageManifest = { name: "test", version: "1.0.0", dependencies: { lodash: "^4.0.0" }, }; - readTypedYamlSync.mockReturnValue({ - patchedDependencies: { - "lodash@4.17.21": "patches/lodash.patch", - }, - }); - readTypedJson.mockResolvedValue({ - name: "root", - version: "1.0.0", - } as PackageManifest); - - filterPatchedDependencies.mockReturnValue({ - "lodash@4.17.21": "patches/lodash.patch", - }); - - fs.existsSync.mockReturnValue(true); - - usePackageManager.mockReturnValue({ - name: "pnpm", - majorVersion: 9, - version: "9.0.0", - packageManagerString: "pnpm@9.0.0", - }); + mockCopyPatchesCommonDeps(); readWantedLockfile_v9.mockResolvedValue({
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e38407e6-571d-4fcb-9433-51c5485bae71
📒 Files selected for processing (4)
.oxlintrc.jsonsrc/lib/patches/copy-patches.test.tssrc/lib/patches/copy-patches.tsvitest.config.ts
pnpm 11 simplified the lockfile
patchedDependenciesformat fromRecord<string, { path, hash }>toRecord<string, string>(selector to hash), and existing lockfiles are migrated automatically.@pnpm/lockfile-file@9passes this field through unchanged, so reading a pnpm 11 lockfile yields bare hash strings instead of{ path, hash }objects.copyPatchesonly readoriginalPatchFile?.hash, which isundefinedfor a string entry, so the hash ended up empty. That empty hash was written into the isolated lockfile, and pnpm then rejected the isolated install withERR_PNPM_LOCKFILE_CONFIG_MISMATCH: Cannot proceed with the frozen installation. The current "patchedDependencies" configuration doesn't match the value found in the lockfile.The hash is now read from either the string (pnpm 11) or the object (pnpm <=10) shape, with the reader's return type widened accordingly. Regression tests cover both formats.
Separately, nested git worktrees under
.worktrees/are now excluded from vitest and oxlint. Git auto-ignores registered worktree paths, but both tools walk the filesystem and were running another branch's tests and linting its files.Decisions:
{ path, hash }format by@pnpm/lockfile-file@9. This is intentional: pnpm 11 reads old-format lockfiles, extracts the hash (discarding the path), and respectsfrozen-lockfilemode while doing so (refactor: simplify patchedDependencies lockfile format pnpm/pnpm#10911), so a correct hash in the old format is accepted. Serializing the new string format would require post-processing YAML the library does not emit, for no functional gain.Fixes #201
Scope: packages (isolate-package)
Visibility: user-facing