Skip to content

fix(arborist): record the linked .store layout in the hidden lockfile (backport #9630)#9642

Merged
owlstronaut merged 1 commit into
npm:release/v11from
manzoorwanijk:backport/linked-hidden-lockfile-v11
Jun 24, 2026
Merged

fix(arborist): record the linked .store layout in the hidden lockfile (backport #9630)#9642
owlstronaut merged 1 commit into
npm:release/v11from
manzoorwanijk:backport/linked-hidden-lockfile-v11

Conversation

@manzoorwanijk

Copy link
Copy Markdown
Contributor

Backport of #9630 to release/v11.

Under install-strategy=linked, the hidden lockfile node_modules/.package-lock.json recorded the hoisted logical layout instead of the on-disk .store/symlink layout, so it was rejected on every reload and never served as the actual-tree cache it is meant to be.

How

reify.js serializes the hidden lockfile from the isolated tree, which mirrors the on-disk layout, while package-lock.json still comes from the logical tree. assertNoNewer() additionally validates the store package node_modules and undeclared-workspace subtrees that the plain node_modules walk cannot reach under the linked strategy, gated so the hoisted strategy keeps its existing, stricter validation.

The original commit's change to test/arborist/reify-npm-extension.js was dropped because the .npm-extension feature does not exist on release/v11.

References

Backport of #9630
Part of #9608

…npm#9630)

In continuation of our exploration of using `install-strategy=linked` in
the [Gutenberg
monorepo](WordPress/gutenberg#75814), which
powers the WordPress Block Editor.

Under `install-strategy=linked`, the hidden lockfile
`node_modules/.package-lock.json` recorded the hoisted logical layout
(`node_modules/<pkg>`) instead of the actual on-disk `.store`/symlink
layout. The hidden lockfile is meant to cache what `loadActual()` finds
on disk so the actual tree can be validated cheaply, but because it
recorded the wrong layout it was rejected on every reload, so it never
served as a cache and misrepresented the installed layout.

A linked reify swaps `idealTree` for the isolated tree, materializes the
`.store`/symlink layout, then swaps the logical tree back before saving.
The hidden lockfile was serialized from that logical tree, so it listed
packages at their hoisted paths. On the next load, `assertNoNewer()`
walked the real `node_modules` (the root symlink plus `.store/`) and
could not reconcile it with the hoisted entries, throwing `missing from
lockfile`, so `loadActual()` always fell back to a full filesystem scan.

`reify.js` serializes the hidden lockfile from the isolated tree, which
mirrors the on-disk layout, while `package-lock.json` still comes from
the logical tree. It records every store package directory and symlink,
adds an entry for each `.store/<key>` container directory (these are the
fsParents `loadVirtual()` needs so a store package can resolve its
sibling deps), includes the workspace directories, and skips tree-only
undeclared-workspace self-links that are never materialized on disk.

`assertNoNewer()` additionally validates the directories the plain
`node_modules` walk cannot reach under the linked strategy: a store
package's deps live as symlinked siblings under
`.store/<key>/node_modules` (and `.store` is skipped as a dot-dir), and
an undeclared workspace is not symlinked into the root `node_modules` at
all. These directories are derived from the lockfile entries. A
workspace directory is only walked when it is not the target of a link
entry, so the hoisted strategy keeps its existing, stricter validation
unchanged — a stale workspace symlink that points at the wrong target
still surfaces as a missing entry and rejects the cache.

Fixes npm#9612
Part of npm#9608
@owlstronaut owlstronaut merged commit ed37d24 into npm:release/v11 Jun 24, 2026
16 checks passed
@manzoorwanijk manzoorwanijk deleted the backport/linked-hidden-lockfile-v11 branch June 24, 2026 18:16
@github-actions github-actions Bot mentioned this pull request Jun 24, 2026
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.

2 participants