Skip to content

fix(arborist): correct dev/prod dep flags for workspaces under the linked strategy#9655

Merged
owlstronaut merged 2 commits into
npm:latestfrom
manzoorwanijk:fix/linked-workspace-dep-flags
Jun 25, 2026
Merged

fix(arborist): correct dev/prod dep flags for workspaces under the linked strategy#9655
owlstronaut merged 2 commits into
npm:latestfrom
manzoorwanijk:fix/linked-workspace-dep-flags

Conversation

@manzoorwanijk

Copy link
Copy Markdown
Contributor

In continuation of our exploration of using install-strategy=linked in the Gutenberg monorepo, which powers the WordPress Block Editor.

Under install-strategy=linked, npm query reports the wrong dev/prod flags for workspaces and their dependencies. In a workspace project the entire non-root tree is flagged dev, so :is(.prod) returns almost nothing and :is(.dev) returns almost everything — the opposite of the hoisted strategy. This breaks tooling that classifies dependencies via npm query, e.g. a license checker that selects .prod dependencies.

Why

Two compounding defects, both exercised only by the linked layout.

First, the linked strategy does not symlink undeclared workspaces into the root's node_modules, so the root's workspace edges resolve to null. calcDepFlags walks outward from the root via edges, dead-ends immediately, and never reaches any workspace or its transitive deps, leaving them at their default dev=true.

Second, the node.isLink branch in calcDepFlags assigned target flags unconditionally (target.dev = link.dev), unlike every other flag in that file which is only ever unset (true to false). When a target is reachable through more than one link — the norm under linked, where each workspace's own node_modules links to a shared target — the last link visited could overwrite an already-correct dev=false back to true.

How

Make the calcDepFlags link branch monotonic: only unset flags, matching the edge walk below it, and queue the target on first visit so its own deps are still walked. A target reachable through multiple links now keeps the most permissive flags regardless of visit order.

In loadActual, when the install strategy is linked, synthesize the missing root-to-workspace links from the already-loaded workspace targets so the root's workspace edges resolve and flags propagate. The synthesis is gated to linked because under hoisted an unresolved workspace edge is a genuinely missing symlink that reify must recreate, not synthesize. Workspaces already linked into the root node_modules are skipped.

This targets the path used by npm query and non-lockfile npm sbom, which force a filesystem read of the actual tree. Commands that load from the hidden lockfile (npm ls, npm outdated, npm audit signatures) are unchanged; their separate, pre-existing linked flag gap is left for a follow-up.

References

Fixes #9100

@manzoorwanijk manzoorwanijk marked this pull request as ready for review June 25, 2026 08:10
@manzoorwanijk manzoorwanijk requested review from a team as code owners June 25, 2026 08:10
@owlstronaut owlstronaut merged commit f9e3a80 into npm:latest Jun 25, 2026
22 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

🎉 Backport to release/v11 created: #9666

owlstronaut pushed a commit that referenced this pull request Jun 25, 2026
…egy (#9666)

Backport of #9655 to `release/v11`.

Co-authored-by: Manzoor Wani <manzoorwani.jk@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] linked strategy: calcDepFlags produces incorrect dev flags due to multiple Links overwriting target

2 participants