Skip to content

fix(js): include transitive workspace deps in pruned pnpm lockfile#35532

Merged
FrozenPandaz merged 13 commits intomasterfrom
fix/34655-transitive-workspace-deps
May 4, 2026
Merged

fix(js): include transitive workspace deps in pruned pnpm lockfile#35532
FrozenPandaz merged 13 commits intomasterfrom
fix/34655-transitive-workspace-deps

Conversation

@FrozenPandaz
Copy link
Copy Markdown
Contributor

Current Behavior

@nx/js:prune-lockfile does not recursively walk workspace→workspace dependencies. Given an app → @myorg/lib-a → @myorg/lib-b → lodash chain (where lib-a and lib-b are workspace packages), the executor produces a pruned pnpm-lock.yaml that:

  1. Misses the importer block for workspace_modules/@myorg/lib-b (the transitive workspace dep).
  2. Misses lodash (lib-b's npm dep) from the packages: section.
  3. Keeps specifier: workspace:* for lib-b inside lib-a's importer block, even though @nx/js:copy-workspace-modules rewrites lib-a/package.json to "@myorg/lib-b": "file:../lib-b".

The specifier mismatch causes pnpm install --frozen-lockfile to fail in the deployed output:

ERR_PNPM_OUTDATED_LOCKFILE  Cannot install with "frozen-lockfile" because
pnpm-lock.yaml is not up to date with workspace_modules/@myorg/lib-a/package.json
  - @myorg/lib-b (lockfile: workspace:*, manifest: file:../lib-b)

Expected Behavior

@nx/js:prune-lockfile recursively discovers transitive workspace dependencies and produces a lockfile that:

  1. Contains an importers block for every workspace module that copy-workspace-modules writes to disk.
  2. Includes the npm dependencies of all transitive workspace modules in the packages: section.
  3. Rewrites workspace-package references inside nested importers to the flat workspace_modules/ layout (specifier: file:<rel> / version: link:<rel>), matching what copy-workspace-modules writes to each package's package.json.

pnpm install --frozen-lockfile succeeds in the pruned output directory.

Implementation

Two coordinated changes in lockfile-side code:

  • project-graph-pruning.tstraverseWorkspaceNode now recurses into workspace→workspace dependency edges with a visited set, so transitive workspace npm deps reach the pruned graph.
  • pnpm-parser.tsstringifyPnpmLockfile BFS-collects transitive workspace importers, deep-clones each importer block, and rewrites workspace-package references to the flat workspace_modules/ layout.

Tests

  • 3 new unit tests in pnpm-parser.spec.ts covering the canonical chain, dependency cycles, and diamond shapes.
  • 1 new e2e test in e2e/js/src/js-executor-prune-lockfile.test.ts exercising the canonical chain end-to-end.
  • Verified end-to-end against the reported reproduction repo: pnpm install --frozen-lockfile now succeeds in the pruned output where it previously failed with ERR_PNPM_OUTDATED_LOCKFILE.

Credit

Diagnosis and original fix sketch by @estevaolucas in #35347 — this PR carries forward the recursion + specifier rewrite portions in a focused change. The other concerns from #35347 (devDep/peerDep stripping, catalog reference resolution in copy-workspace-modules) are real but separable and intentionally left for follow-up issues.

Related Issue(s)

Fixes #34655

@netlify
Copy link
Copy Markdown

netlify Bot commented May 1, 2026

Deploy Preview for nx-docs ready!

Name Link
🔨 Latest commit 7460849
🔍 Latest deploy log https://app.netlify.com/projects/nx-docs/deploys/69f53032cd05660008e25c70
😎 Deploy Preview https://deploy-preview-35532--nx-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link
Copy Markdown

netlify Bot commented May 1, 2026

Deploy Preview for nx-dev ready!

Name Link
🔨 Latest commit 7460849
🔍 Latest deploy log https://app.netlify.com/projects/nx-dev/deploys/69f530321444d80007b4184f
😎 Deploy Preview https://deploy-preview-35532--nx-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@nx-cloud
Copy link
Copy Markdown
Contributor

nx-cloud Bot commented May 1, 2026

View your CI Pipeline Execution ↗ for commit 7460849

Command Status Duration Result
nx affected --targets=lint,test,build,e2e,e2e-c... ✅ Succeeded 14m 20s View ↗
nx run-many -t check-imports check-lock-files c... ✅ Succeeded 3s View ↗
nx-cloud record -- pnpm nx-cloud conformance:check ✅ Succeeded 16s View ↗
nx build workspace-plugin ✅ Succeeded <1s View ↗
nx-cloud record -- nx sync:check ✅ Succeeded 23s View ↗
nx-cloud record -- nx format:check ✅ Succeeded 7s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-02 04:45:24 UTC

nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

FrozenPandaz and others added 13 commits May 1, 2026 18:57
When app -> lib-a -> lib-b, prune-lockfile produced a lockfile missing
the lib-b importer, missing lib-b's npm deps from packages:, and kept
'workspace:*' as the specifier for lib-b in lib-a's importer. Since
copy-workspace-modules rewrites lib-a/package.json to "file:../lib-b",
pnpm install --frozen-lockfile failed with ERR_PNPM_OUTDATED_LOCKFILE.

- traverseWorkspaceNode now recurses into workspace->workspace edges
  with a visited set, so transitive workspace npm deps reach the
  pruned graph.
- stringifyPnpmLockfile BFS-collects transitive workspace importers
  and rewrites workspace-package references to the flat
  workspace_modules/ layout via file:<rel> / link:<rel>, matching
  what copy-workspace-modules writes to disk.

Fixes #34655
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
…agers

Fold the standalone pnpm transitive-workspace-dep test into the existing
describe.each so pnpm/yarn/npm all exercise transitive workspace deps.
Replace YAML-regex assertions with a real install contract: run the
package manager's frozen-lockfile install against the pruned dist/.
…pace

Copy the pruned dist to a fresh tmp dir outside the e2e workspace before
running install. Previously the install ran inside the workspace, where
parent pnpm-workspace.yaml / hoisted node_modules could mask missing deps
and let a broken pruned lockfile pass. The dist is the deployment artifact;
install it the way it actually gets deployed.
…ilure

runCommand silently swallows non-zero exits by default — pass failOnError:
true so the install step is a real assertion. The prior CI green didn't
actually validate that pnpm/yarn/npm install succeeded against the
pruned dist.
Same class of bug as the pnpm fix: mapWorkspaceModules only iterated
the root packageJson.dependencies, so app -> lib-a -> lib-b only emitted
lib-a's entries. `npm ci` against the pruned dist then failed with
"Missing: <lib-b> from lock file". BFS through workspace deps so
transitive workspace packages get their node_modules + workspace_modules
entries too, with a visited guard to handle cycles.
…ansitives

- pnpm-parser: walk peerDependencies in addition to dependencies and
  optionalDependencies during the transitive workspace BFS, so workspace
  packages referenced only as peers still get importer blocks. Initialize
  importer.specifiers when missing (lockfile v6+ may omit it) so the
  rewrite to file:<rel> always lands.
- npm-parser: walk optionalDependencies and peerDependencies in the
  transitive BFS for the same reason.
- Add unit tests for the npm-parser transitive workspace dep + cycle
  cases mirroring the pnpm-parser coverage.
app -> lib-a <-> lib-b across pnpm/yarn/npm. Without the visited guard
in the parser BFS or in traverseWorkspaceNode, this hangs at
prune-lockfile time.
- Extract WORKSPACE_DEP_TYPES constant (used 4x across both parsers)
- Pre-build name->snapshot map in npm-parser to avoid O(n*m) scan inside the BFS
- Drop redundant defensive checks (workspaceModules.has() already guards)
- Trim narrative comments that restated the code; keep only WHY notes
- Tighten test assertions
Native algorithm, no string round-trip — slightly faster and idiomatic
on Node 17+.
…t the task graph level

Cycle protection is still covered by the parser unit tests, which bypass
Nx's task scheduler and exercise the BFS visited guard directly.
@FrozenPandaz FrozenPandaz force-pushed the fix/34655-transitive-workspace-deps branch from cafcf0b to 7460849 Compare May 1, 2026 22:58
Copy link
Copy Markdown
Contributor

@nx-cloud nx-cloud Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

At least one additional CI pipeline execution has run since the conclusion below was written and it may no longer be applicable.

Nx Cloud has identified a possible root cause for your failed CI:

We determined this failure is unrelated to the PR changes. The e2e-nx:e2e-ci--src/cache-no-daemon.test.ts task fails with EADDRINUSE :::3000 — port 3000 is already occupied in the CI environment when the remote cache fixture tries to bind to it. Since this project is not in the set of touched projects and the port conflict is a pre-existing infrastructure issue, no code changes are needed.

No code changes were suggested for this issue.

Trigger a rerun:

Rerun CI

Nx Cloud View detailed reasoning on Nx Cloud ↗


🎓 Learn more about Self-Healing CI on nx.dev

@FrozenPandaz FrozenPandaz marked this pull request as ready for review May 4, 2026 14:02
@FrozenPandaz FrozenPandaz requested a review from a team as a code owner May 4, 2026 14:02
@FrozenPandaz FrozenPandaz requested a review from AgentEnder May 4, 2026 14:02
@FrozenPandaz FrozenPandaz merged commit 058e566 into master May 4, 2026
25 of 26 checks passed
@FrozenPandaz FrozenPandaz deleted the fix/34655-transitive-workspace-deps branch May 4, 2026 18:13
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.

@nx/js:prune-lockfile does not include transitive workspace dependencies in pruned lockfile (pnpm)

2 participants