Skip to content

fix(lockfile): synthesize npm-alias entries for transitive deps in pnpm lockfiles#403

Merged
jdx merged 2 commits intomainfrom
claude/eager-torvalds-7deb94
Apr 30, 2026
Merged

fix(lockfile): synthesize npm-alias entries for transitive deps in pnpm lockfiles#403
jdx merged 2 commits intomainfrom
claude/eager-torvalds-7deb94

Conversation

@jdx
Copy link
Copy Markdown
Contributor

@jdx jdx commented Apr 30, 2026

Summary

  • pnpm encodes aliased transitive deps as <alias>: <real>@<resolved> inside a snapshot's dependency map (e.g. string-width-cjs: string-width@4.2.3 for the "string-width-cjs": "npm:string-width@^4.2.0" declared by @isaacs/cliui@8.0.2). The reader passed the value through unchanged, so the linker keyed the symlink against <dep_name>@<dep_value> and produced a broken string-width-cjs@string-width@4.2.3 virtual store path. On a re-resolve, the resolver's lockfile-reuse path enqueued a transitive task with the literal string-width@4.2.3 range that no string-width-cjs version could satisfy, surfacing as no version of string-width-cjs matches range \string-width@4.2.3``.
  • Detect the alias shape at parse, rewrite the dep value to the bare resolved version (preserving any peer-context suffix), and feed the existing alias_remaps channel that the importer path already uses to synthesize an alias-keyed LockedPackage with alias_of=Some(real). The linker then resolves the alias symlink against the synthetic dir, and the resolver's reuse path matches the synthetic entry by name+version with no malformed range.
  • Reported in endevco/aube#345, repro at stevelandeydescript/aube-bug-repros/npm-alias-resolution-failure. The earlier fix(cli,linker,lockfile): patch-commit destination, CRLF patches, npm-alias catalog #384 fix covered the importer-level alias case from a pnpm-generated lockfile; this is the matching fix for the snapshot dependency map.

Test plan

  • cargo test -p aube-lockfile parse_synthesizes_npm_alias_for_transitive_deps (new)
  • cargo test -p aube-lockfile parse_handles_npm_alias_for_transitive_deps_with_peer_suffix (new)
  • cargo test -p aube-lockfile -p aube-resolver -p aube-linker — full lockfile/resolver/linker suites
  • cargo clippy -p aube-lockfile --all-targets -- -D warnings
  • cargo fmt --check
  • End-to-end against the repro: pnpm installrm pnpm-lock.yaml node_modulesaube install --frozen-lockfile (or fresh resolve) → node -e "require('jackspeak')" succeeds. Before the fix, string-width-cjs symlink dangled; after, it resolves through the synthetic string-width-cjs@4.2.3 entry.

🤖 Generated with Claude Code


Note

Medium Risk
Changes pnpm lockfile dependency rewriting and package synthesis logic, which can affect install graph correctness if alias detection is too broad or misses edge cases; coverage is improved with targeted tests.

Overview
Fixes pnpm v9 lockfile parsing for npm-aliased transitive dependencies encoded in snapshots as <alias>: <real>@<resolved>(peers…). The parser now rewrites those snapshot dependency values to the bare resolved version (preserving any peer-context suffix) and feeds the existing alias_remaps path so alias-keyed LockedPackage entries are synthesized consistently.

This logic is applied both in the main snapshot loop and when absorbing snapshot deps for local (file:) workspace packages, and includes new unit tests covering plain transitive aliases, peer-suffixed aliases, and local-package transitives; also slightly clarifies the missing-package error message for alias synthesis.

Reviewed by Cursor Bugbot for commit df2fe15. Bugbot is set up for automated code reviews on this repo. Configure here.

…pm lockfiles

pnpm encodes aliased transitive deps as `<alias>: <real>@<resolved>` inside
a snapshot's dependency map (e.g. `string-width-cjs: string-width@4.2.3`),
but the reader was passing the value through unchanged. The linker then
keyed the symlink against `<dep_name>@<dep_value>` and produced a broken
`string-width-cjs@string-width@4.2.3` path, while the resolver's lockfile
reuse path enqueued a transitive with a synthetic `string-width@4.2.3`
range that no `string-width-cjs` version could satisfy.

Detect the alias shape during parse, rewrite the dep value to the bare
resolved version (preserving any peer-context suffix), and feed the same
`alias_remaps` channel the importer-level path uses to synthesize an
alias-keyed `LockedPackage` with `alias_of=Some(real)`.

Repro: https://github.com/stevelandeydescript/aube-bug-repros/tree/main/npm-alias-resolution-failure

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 30, 2026

Greptile Summary

This PR fixes a bug in pnpm lockfile parsing where transitive npm-aliased dependencies encoded as <alias>: <real>@<resolved> in snapshot maps were passed through unchanged, causing broken virtual store paths and malformed re-resolve ranges. The fix adds a rewrite_snapshot_alias_deps helper that detects the alias shape, rewrites the dep value to the bare resolved version (preserving any peer-context suffix), and feeds the existing alias_remaps synthesis path — covering both the main snapshot loop and the previously-missed local-packages loop. Both previous P2 comments (stale error message and local_packages bypass) are addressed in this PR.

Confidence Score: 5/5

Safe to merge — fix is well-scoped, correctly handles peer-suffix and duplicate alias cases, and is backed by three targeted tests.

No P0 or P1 issues found. The alias detection logic is sound: parse_dep_path correctly handles scoped packages and strips peer suffixes; the real_name == dep_name guard skips non-aliased deps; peer-suffixed real_dep_path values are present in packages because the main snapshot loop uses full snapshot keys (including peer suffix) as map keys; and duplicate alias_remaps entries are handled by the contains_key guard in the synthesis loop. Both previously flagged P2s (stale error message, local_packages bypass) are resolved in this diff.

No files require special attention.

Important Files Changed

Filename Overview
crates/aube-lockfile/src/pnpm.rs Adds rewrite_snapshot_alias_deps helper to detect and rewrite pnpm transitive alias dep values, calls it in both the main snapshot loop and the local-packages loop, fixes the stale error message, and adds three new focused tests covering basic alias synthesis, peer-suffix preservation, and local-package transitives.

Reviews (2): Last reviewed commit: "fix(lockfile): apply npm-alias rewrite t..." | Re-trigger Greptile

Greptile flagged two follow-ups on PR #403:

- The local-packages absorption loop pulls a `file:` workspace
  package's transitive deps directly out of `raw.snapshots` and
  bypassed `rewrite_alias_values`, so a workspace package with a
  npm-aliased transitive (`"string-width-cjs": "npm:string-width@^4.2.0"`)
  would still produce the broken `string-width-cjs@string-width@4.2.3`
  virtual store path. Lift the rewrite into a free
  `rewrite_snapshot_alias_deps` fn and call it from both the
  local-packages loop and the main snapshots loop.

- The `alias_remaps` synthesis error said "npm-alias importer
  references missing package …" but the channel is now also fed
  from snapshot transitives where no importer is involved. Drop
  "importer".

Adds a regression test covering an aliased transitive declared by a
`file:` local package.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jdx jdx merged commit 361b0e6 into main Apr 30, 2026
17 checks passed
@jdx jdx deleted the claude/eager-torvalds-7deb94 branch April 30, 2026 16:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant