Skip to content

fix(arborist): link meta-only optional peers in linked strategy#9461

Merged
owlstronaut merged 1 commit into
npm:latestfrom
manzoorwanijk:fix/linked-meta-only-optional-peers
Jun 3, 2026
Merged

fix(arborist): link meta-only optional peers in linked strategy#9461
owlstronaut merged 1 commit into
npm:latestfrom
manzoorwanijk:fix/linked-meta-only-optional-peers

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, an optional peer dependency declared only in peerDependenciesMeta (with no matching entry in peerDependencies) was not linked into the dependent's isolated node_modules, even when an ancestor in the graph provided it.
For example @emotion/react declares react in peerDependencies and @types/react only in peerDependenciesMeta; after a linked install its store node_modules contained react but not @types/react, so its shipped .d.ts could not resolve the React typings under strict isolation and the breakage cascaded into consumers as TypeScript errors.

The root cause is that npm only creates dependency edges from peerDependencies.
A peer named only in peerDependenciesMeta gets no edge at all.
The isolated reifier materializes a dependency into a package's store node_modules only when the package has a resolved edge for it (edge.to.target), so the meta-only optional peer was silently dropped.
Required peers (and optional peers that also appear in peerDependencies) have edges and were materialized correctly; meta-only optional peers were the gap.

This PR resolves the gap in #assignCommonProperties in isolated-reifier.js.
After the edge-derived dependency lists are built, it iterates peerDependenciesMeta, and for each optional entry that is not already a dependency it resolves the name against the tree via node.resolve() and, when found, materializes it as an optional store dependency.
Because the peer is added to the package's store dependency set, the store instance's hash is keyed by it, and it resolves to the same instance the consumer uses rather than a duplicate copy.
If no ancestor provides the peer, node.resolve() returns nothing and it stays omitted, preserving optional semantics.

References

Fixes #9460

@manzoorwanijk manzoorwanijk marked this pull request as ready for review June 2, 2026 12:59
@manzoorwanijk manzoorwanijk requested review from a team as code owners June 2, 2026 12:59
@owlstronaut owlstronaut merged commit a105799 into npm:latest Jun 3, 2026
18 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 3, 2026

🎉 Backport to release/v11 created: #9485

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] Isolated mode does not materialize optional peer dependencies into the dependent's store node_modules

2 participants