fix(lockfiles): retain base packages entry for peer-variant injected workspace deps#12819
Closed
Eyalm321 wants to merge 3 commits into
Closed
fix(lockfiles): retain base packages entry for peer-variant injected workspace deps#12819Eyalm321 wants to merge 3 commits into
Eyalm321 wants to merge 3 commits into
Conversation
…workspace deps vercel#12073 retains injected `file:` workspace snapshots, but when the dep has peer dependencies that resolve per-consumer, pnpm writes the variant under `pkg@file:packages/pkg(peer@1)` (snapshots) while `resolution` lives on the base `pkg@file:packages/pkg` (packages). vercel#12073 only looked up get_packages(variant_key) — None for the variant — so the base entry with the directory resolution was dropped. pnpm's convertToLockfileObject then inherits an undefined resolution and crashes with `Cannot use 'in' operator to search for 'directory' in undefined` during `pnpm install --frozen-lockfile`. Strip the peer suffix to also retain the base entry, mirroring the peer-suffix handling the transitive-deps path in this same function already uses. No-op when there is no peer suffix (base_key == key), so the non-variant case vercel#12073 added is unaffected. Residual of vercel#12002 / vercel#11059. Adds a peer-variant regression test.
Contributor
|
Someone is attempting to deploy a commit to the Vercel Team on Vercel. A member of the Team first needs to authorize it. |
Eyalm321
pushed a commit
to Eyalm321/pnpm
that referenced
this pull request
May 17, 2026
Per the PR review (zkochan); pairs with the turbo-side root-cause fix vercel/turborepo#12819. Bug 1 (peer-variant resolution): normalize once at the read layer in convertToLockfileObject — when a pruned lockfile dropped the base packages entry, synthesize the directory resolution from the file: depPath (reconstruction of exactly what pnpm's writer emits, not a guess). This makes inheritOrSynthesizeResolution + its 5 reader call sites inert idempotent guards (resolution is always populated before they run); the active normalization now lives solely in the converter. Mechanical removal of the now-dead helper is a trivial follow-up. Bug 2 (lifecycle re-import .bin ENOENT): drop the pnpm#4299 scanDir-into- filesMap workaround and pass keepModulesDir: true so importIndexedDir skips the pnpm#11088 makeEmptyDir fast path and preserves the target's existing node_modules via its staging/move path. Stays on storeController.importPackage so source files keep hardlinks — replaces the copy-loop mirror. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s on fork main only)
Contributor
|
Closed for slop. |
Eyalm321
pushed a commit
to Eyalm321/pnpm
that referenced
this pull request
May 18, 2026
vercel/turborepo#12819 was closed; the live root-cause fix (retain the base packages entry for peer-variant injected deps) is tracked at vercel/turborepo#12825. Keeps the source comment consistent with the PR description.
github-actions Bot
pushed a commit
to Eyalm321/pnpm
that referenced
this pull request
May 18, 2026
Per the PR review (zkochan); pairs with the turbo-side root-cause fix vercel/turborepo#12819. Bug 1 (peer-variant resolution): normalize once at the read layer in convertToLockfileObject — when a pruned lockfile dropped the base packages entry, synthesize the directory resolution from the file: depPath (reconstruction of exactly what pnpm's writer emits, not a guess). This makes inheritOrSynthesizeResolution + its 5 reader call sites inert idempotent guards (resolution is always populated before they run); the active normalization now lives solely in the converter. Mechanical removal of the now-dead helper is a trivial follow-up. Bug 2 (lifecycle re-import .bin ENOENT): drop the pnpm#4299 scanDir-into- filesMap workaround and pass keepModulesDir: true so importIndexedDir skips the pnpm#11088 makeEmptyDir fast path and preserves the target's existing node_modules via its staging/move path. Stays on storeController.importPackage so source files keep hardlinks — replaces the copy-loop mirror. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
github-actions Bot
pushed a commit
to Eyalm321/pnpm
that referenced
this pull request
May 18, 2026
vercel/turborepo#12819 was closed; the live root-cause fix (retain the base packages entry for peer-variant injected deps) is tracked at vercel/turborepo#12825. Keeps the source comment consistent with the PR description.
zkochan
pushed a commit
to Eyalm321/pnpm
that referenced
this pull request
May 19, 2026
Per the PR review (zkochan); pairs with the turbo-side root-cause fix vercel/turborepo#12819. Bug 1 (peer-variant resolution): normalize once at the read layer in convertToLockfileObject — when a pruned lockfile dropped the base packages entry, synthesize the directory resolution from the file: depPath (reconstruction of exactly what pnpm's writer emits, not a guess). This makes inheritOrSynthesizeResolution + its 5 reader call sites inert idempotent guards (resolution is always populated before they run); the active normalization now lives solely in the converter. Mechanical removal of the now-dead helper is a trivial follow-up. Bug 2 (lifecycle re-import .bin ENOENT): drop the pnpm#4299 scanDir-into- filesMap workaround and pass keepModulesDir: true so importIndexedDir skips the pnpm#11088 makeEmptyDir fast path and preserves the target's existing node_modules via its staging/move path. Stays on storeController.importPackage so source files keep hardlinks — replaces the copy-loop mirror. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zkochan
pushed a commit
to Eyalm321/pnpm
that referenced
this pull request
May 19, 2026
vercel/turborepo#12819 was closed; the live root-cause fix (retain the base packages entry for peer-variant injected deps) is tracked at vercel/turborepo#12825. Keeps the source comment consistent with the PR description.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Residual of #12002 / #11059, not covered by #12073.
#12073 made
turbo pruneretain injectedfile:workspace-dep entries. But when the injected workspace package has peer dependencies that resolve per-consumer, pnpm v9 writes:snapshots:→@scope/pkg@file:packages/pkg(peer@1.0.0)resolutionon the basepackages:entry →@scope/pkg@file:packages/pkg→resolution: {type: directory, directory: packages/pkg}The injected handler computes
key = format_key(dep, version)whereversionis the peer-variant string, thenget_packages(&key)— which isNonefor the variant (the base entry has no suffix). So the base entry carrying the directoryresolutionis dropped from the pruned lockfile.pnpm's
convertToLockfileObjectthen doesObject.assign(snapshot, packages[removeSuffix(depPath)])→undefined→ and later crashes:…on
pnpm install --frozen-lockfileagainst the pruned lockfile (reproducible withinjectWorkspacePackages: true+ a workspace dep that has per-consumer peer deps).Fix
After retaining the variant, strip the peer suffix and also retain the base
packages:entry — reusing the exactDepPath::parse(...).format_key(dp.name, dp.version)peer-suffix handling the transitive-deps path in this same function already uses a few lines below. It's a no-op when there is no peer suffix (base_key == key), so the non-variant case #12073 added is unaffected.~13 lines, no new APIs.
Test
Adds
test_subgraph_injected_peer_variant_retains_base_resolution(mirrors #12073's owntest_subgraph_with_injected_workspace_packages_setting): an injected@repo/sharedwith areactpeer → basepackages:entry + peer-variantsnapshots:entry. Asserts the pruned lockfile retains the base entry and itsdirectory:resolution. Fails before this change, passes after.🤖 Generated with Claude Code