Skip to content

[pull] latest from npm:latest - #223

Merged
pull[bot] merged 5 commits into
LadyK-21:latestfrom
npm:latest
Jun 25, 2026
Merged

[pull] latest from npm:latest#223
pull[bot] merged 5 commits into
LadyK-21:latestfrom
npm:latest

Conversation

@pull

@pullpullBot commented Jun 25, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

…ategy (#9628)
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`, if a top-level `node_modules/<dep>`
symlink points to a store key that exists on disk but is the **wrong
version**, re-running `npm install` does not repair it. npm reports
success and leaves the dependency resolving to the wrong version. This
is the state an interrupted update leaves behind: the new store key is
extracted but the symlink has not yet been repointed.
A symlink pointing at a **non-existent** target is already repaired on
reinstall; only a wrong-but-existing target slips through, because
cleanup validates the link name, not its target.
## Why
For linked installs, `#buildLinkedActualForDiff` synthesizes the
"actual" tree the diff compares against from the **ideal** children,
never reading the real on-disk symlink target. So a link whose on-disk
target is a valid-but-wrong store key looks identical to the ideal node,
the diff reports no change, and the symlink is left untouched. The
hoisted strategy is unaffected because it self-heals the analogous
corruption.
## How
In `#buildLinkedActualForDiff`, when an existing link's resolved on-disk
target differs from its ideal target, skip creating a synthetic actual
entry for it. With no actual entry to match, the diff treats the link as
an `ADD`, and `#reifyNode` removes the old symlink and recreates it
pointing at the correct store key. A new `#linkTargetMismatch` helper
compares the two resolved targets; it runs only after the existing
`existsSync` guards, so both paths are known to exist.
This repairs both the top-level symlink and wrong transitive/sibling
links inside the store, and leaves already-correct trees untouched (no
spurious relinking on an idempotent reinstall).
## References
Fixes#9611
)
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`, several common installs failed with
`npm error invalid filterNode: outside idealTree/actualTree`, with no
workaround besides dropping the linked strategy. Hoisted handled all of
them. This fixes two distinct paths that both produced that error.
## Why
A linked reify diffs the ideal tree against a synthesized actual wrapper
(`#linkedActualForDiff`) rather than `this.actualTree`. `Diff.calculate`
rejects any filter node whose root is neither the ideal nor the actual
it was given, so a filter node taken from the real `this.actualTree` is
"outside" the diff and throws.
Two places fed it such nodes:
- `--workspaces=false` and `-w <ws> --include-workspace-root` go through
the `includeRootDeps` branch of `_diffTrees()`, which collected root-dep
edge targets from both `this.idealTree` and `this.actualTree`. The
actual-side targets are rooted at the real actual tree, not the wrapper,
so they tripped the guard. The sibling `includeWorkspaces` branch
already accounted for this; the root-dep branch did not.
- A global install with a per-call `installStrategy: 'linked'`
re-engaged the linked path even though the constructor normalizes global
installs to `shallow` (the linked layout is unsupported for globals).
Re-installing an already-present global package then hit the global
explicit-request branch, which pushes actual-side nodes, and tripped the
same guard. Suppressing the crash there was worse: the isolated reifier
does not materialize the global layout and removed the package instead.
## How
`_diffTrees()` now iterates only the ideal tree for root-dep filter
nodes when the linked wrapper is in use, matching the existing
workspace-node handling. The ideal-side nodes are sufficient to scope
the diff, and the post-reify orphan sweep continues to prune deps
removed from the manifest.
`reify()` now honors the constructor's global-to-shallow normalization
when deriving the `linked` flag, so a global install never engages the
linked path regardless of a per-call `installStrategy`. Global installs
fall back to shallow, which materializes and upgrades packages
correctly. No change to the global explicit-request branch is needed
once global is never linked.
## References
Fixes#9614
Part of #9608
Restores the global 100% coverage gate on `latest`, which broke after
#9626.
`filterLinkedStrategyEdges` in `lib/commands/ls.js` skips dev edges on
non-root packages — a guard added in #9095 to suppress false `UNMET
DEPENDENCY` output in the linked strategy. #9626 fixed the root cause
for store packages (they no longer load `devDependencies` as required
edges), so the store-based test no longer produces a dev edge, leaving
that branch unexercised.
Rather than ignore the line, this adds a regression test that genuinely
exercises the guard: arborist still loads dev edges for a `file:`-linked
transitive package, so listing one with `--all` under the linked
strategy reaches the guard at depth > 0 and confirms its devDependency
is suppressed instead of reported as `UNMET DEPENDENCY`. The full test
suite passes at 100%.
This is the `latest` counterpart of #9636, which restored coverage on
`release/v11` via an `istanbul ignore`.
## References
Follows up #9626
…egy (#9639)
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`, `npm exec -w <ws> -- <bin>` ignored a
workspace-local bin (provided by a sibling workspace dependency) and
fell through to the registry, producing a spurious `E404`. The hoisted
strategy ran the local bin correctly.
## Why
For a workspace exec, the command computed the local bin directory as
`resolve(this.npm.localDir, name, 'node_modules', '.bin')`, i.e.
`<root>/node_modules/<name>/node_modules/.bin`. That path only resolves
when the workspace is symlinked into the root `node_modules` as
`<name>`, which is how the hoisted strategy lays workspaces out. The
linked strategy does not hoist workspaces into the root `node_modules`;
the workspace's real bin lives at `<workspace>/node_modules/.bin`.
libnpmexec walks up from the given bin directory looking for
`node_modules/.bin/<bin>`, so starting from the nonexistent hoisted path
never reached the workspace's actual bin and the lookup fell back to the
registry.
## How
Base the local bin directory on the workspace's own path (`runPath`)
instead of the hoisted `localDir/<name>` location. This is correct under
both strategies: linked finds the bin in the workspace's
`node_modules/.bin`, and hoisted still finds the root-hoisted bin
because the walk-up continues from the workspace directory to the root
`node_modules/.bin`.
## References
Fixes#9616
…tch (#9647)
In continuation of our exploration of using `install-strategy=linked` in
the [Gutenberg
monorepo](WordPress/gutenberg#75814), which
powers the WordPress Block Editor.
Switching `install-strategy` in the same project directory left behind
the previous strategy's layout. Going hoisted → linked kept the stale
real top-level transitive directories alongside the new `.store/` and
symlinks; going linked → hoisted kept the entire `node_modules/.store/`
directory. A fresh install of either strategy was already clean — only
the switch was affected.
## Why
Under the linked strategy the actual tree the diff compares against is
synthesized from the ideal tree (`#buildLinkedActualForDiff`), so real
directories left over from a prior hoisted layout are never seen, and
`#cleanOrphanedTopLevelLinks` only removed symlinks. In the other
direction `load-actual` ignores dot-directories, so the hoisted diff
never sees `node_modules/.store` and never removes it.
## How
`reify.js` now removes the leftover `.store` on a non-linked reify via
`#removeStaleStoreDir`. The store lives only at the project root and is
exclusively a linked artifact, so a single removal covers the project.
It runs only for a full-project install — a workspace-filtered or
`--workspaces=false` install is skipped, because out-of-scope workspaces
may still link into the store.
`#cleanOrphanedTopLevelLinks` (run only under linked) additionally
removes stale real package directories — a directory containing a
`package.json` that is not in the ideal tree's valid top-level set — and
prunes an emptied `@scope` directory afterward. Non-package real
directories and symlinks pointing outside the project are still
preserved.
The valid-top-level collection in `#cleanOrphanedStoreEntries` no longer
skips non-link nodes, so the root's bundled dependencies — materialized
as real top-level directories under linked — are recorded as valid and
never swept as stale.
## References
Fixes#9615
Part of #9608
@pullpullBot locked and limited conversation to collaborators Jun 25, 2026
@pull
pullBot merged commit ca92323 into LadyK-21:latestJun 25, 2026
9 of 13 checks passed
@LadyK-21

Copy link
Copy Markdown
Owner

⚠️Snyk checks are incomplete.

StatusScan Engine Critical High Medium LowTotal (2)
⚠️Open Source Security1100 See details

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@LadyK-21@manzoorwanijk
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
[pull] latest from npm:latest by pull[bot] · Pull Request #223 · LadyK-21/cli · GitHub
Skip to content

[pull] latest from npm:latest - #223

Merged
pull[bot] merged 5 commits into
LadyK-21:latestfrom
npm:latest
Jun 25, 2026
Merged

[pull] latest from npm:latest#223
pull[bot] merged 5 commits into
LadyK-21:latestfrom
npm:latest

Conversation

@pull

@pullpullBot commented Jun 25, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

…ategy (#9628)
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`, if a top-level `node_modules/<dep>`
symlink points to a store key that exists on disk but is the **wrong
version**, re-running `npm install` does not repair it. npm reports
success and leaves the dependency resolving to the wrong version. This
is the state an interrupted update leaves behind: the new store key is
extracted but the symlink has not yet been repointed.
A symlink pointing at a **non-existent** target is already repaired on
reinstall; only a wrong-but-existing target slips through, because
cleanup validates the link name, not its target.
## Why
For linked installs, `#buildLinkedActualForDiff` synthesizes the
"actual" tree the diff compares against from the **ideal** children,
never reading the real on-disk symlink target. So a link whose on-disk
target is a valid-but-wrong store key looks identical to the ideal node,
the diff reports no change, and the symlink is left untouched. The
hoisted strategy is unaffected because it self-heals the analogous
corruption.
## How
In `#buildLinkedActualForDiff`, when an existing link's resolved on-disk
target differs from its ideal target, skip creating a synthetic actual
entry for it. With no actual entry to match, the diff treats the link as
an `ADD`, and `#reifyNode` removes the old symlink and recreates it
pointing at the correct store key. A new `#linkTargetMismatch` helper
compares the two resolved targets; it runs only after the existing
`existsSync` guards, so both paths are known to exist.
This repairs both the top-level symlink and wrong transitive/sibling
links inside the store, and leaves already-correct trees untouched (no
spurious relinking on an idempotent reinstall).
## References
Fixes#9611
)
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`, several common installs failed with
`npm error invalid filterNode: outside idealTree/actualTree`, with no
workaround besides dropping the linked strategy. Hoisted handled all of
them. This fixes two distinct paths that both produced that error.
## Why
A linked reify diffs the ideal tree against a synthesized actual wrapper
(`#linkedActualForDiff`) rather than `this.actualTree`. `Diff.calculate`
rejects any filter node whose root is neither the ideal nor the actual
it was given, so a filter node taken from the real `this.actualTree` is
"outside" the diff and throws.
Two places fed it such nodes:
- `--workspaces=false` and `-w <ws> --include-workspace-root` go through
the `includeRootDeps` branch of `_diffTrees()`, which collected root-dep
edge targets from both `this.idealTree` and `this.actualTree`. The
actual-side targets are rooted at the real actual tree, not the wrapper,
so they tripped the guard. The sibling `includeWorkspaces` branch
already accounted for this; the root-dep branch did not.
- A global install with a per-call `installStrategy: 'linked'`
re-engaged the linked path even though the constructor normalizes global
installs to `shallow` (the linked layout is unsupported for globals).
Re-installing an already-present global package then hit the global
explicit-request branch, which pushes actual-side nodes, and tripped the
same guard. Suppressing the crash there was worse: the isolated reifier
does not materialize the global layout and removed the package instead.
## How
`_diffTrees()` now iterates only the ideal tree for root-dep filter
nodes when the linked wrapper is in use, matching the existing
workspace-node handling. The ideal-side nodes are sufficient to scope
the diff, and the post-reify orphan sweep continues to prune deps
removed from the manifest.
`reify()` now honors the constructor's global-to-shallow normalization
when deriving the `linked` flag, so a global install never engages the
linked path regardless of a per-call `installStrategy`. Global installs
fall back to shallow, which materializes and upgrades packages
correctly. No change to the global explicit-request branch is needed
once global is never linked.
## References
Fixes#9614
Part of #9608
Restores the global 100% coverage gate on `latest`, which broke after
#9626.
`filterLinkedStrategyEdges` in `lib/commands/ls.js` skips dev edges on
non-root packages — a guard added in #9095 to suppress false `UNMET
DEPENDENCY` output in the linked strategy. #9626 fixed the root cause
for store packages (they no longer load `devDependencies` as required
edges), so the store-based test no longer produces a dev edge, leaving
that branch unexercised.
Rather than ignore the line, this adds a regression test that genuinely
exercises the guard: arborist still loads dev edges for a `file:`-linked
transitive package, so listing one with `--all` under the linked
strategy reaches the guard at depth > 0 and confirms its devDependency
is suppressed instead of reported as `UNMET DEPENDENCY`. The full test
suite passes at 100%.
This is the `latest` counterpart of #9636, which restored coverage on
`release/v11` via an `istanbul ignore`.
## References
Follows up #9626
…egy (#9639)
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`, `npm exec -w <ws> -- <bin>` ignored a
workspace-local bin (provided by a sibling workspace dependency) and
fell through to the registry, producing a spurious `E404`. The hoisted
strategy ran the local bin correctly.
## Why
For a workspace exec, the command computed the local bin directory as
`resolve(this.npm.localDir, name, 'node_modules', '.bin')`, i.e.
`<root>/node_modules/<name>/node_modules/.bin`. That path only resolves
when the workspace is symlinked into the root `node_modules` as
`<name>`, which is how the hoisted strategy lays workspaces out. The
linked strategy does not hoist workspaces into the root `node_modules`;
the workspace's real bin lives at `<workspace>/node_modules/.bin`.
libnpmexec walks up from the given bin directory looking for
`node_modules/.bin/<bin>`, so starting from the nonexistent hoisted path
never reached the workspace's actual bin and the lookup fell back to the
registry.
## How
Base the local bin directory on the workspace's own path (`runPath`)
instead of the hoisted `localDir/<name>` location. This is correct under
both strategies: linked finds the bin in the workspace's
`node_modules/.bin`, and hoisted still finds the root-hoisted bin
because the walk-up continues from the workspace directory to the root
`node_modules/.bin`.
## References
Fixes#9616
…tch (#9647)
In continuation of our exploration of using `install-strategy=linked` in
the [Gutenberg
monorepo](WordPress/gutenberg#75814), which
powers the WordPress Block Editor.
Switching `install-strategy` in the same project directory left behind
the previous strategy's layout. Going hoisted → linked kept the stale
real top-level transitive directories alongside the new `.store/` and
symlinks; going linked → hoisted kept the entire `node_modules/.store/`
directory. A fresh install of either strategy was already clean — only
the switch was affected.
## Why
Under the linked strategy the actual tree the diff compares against is
synthesized from the ideal tree (`#buildLinkedActualForDiff`), so real
directories left over from a prior hoisted layout are never seen, and
`#cleanOrphanedTopLevelLinks` only removed symlinks. In the other
direction `load-actual` ignores dot-directories, so the hoisted diff
never sees `node_modules/.store` and never removes it.
## How
`reify.js` now removes the leftover `.store` on a non-linked reify via
`#removeStaleStoreDir`. The store lives only at the project root and is
exclusively a linked artifact, so a single removal covers the project.
It runs only for a full-project install — a workspace-filtered or
`--workspaces=false` install is skipped, because out-of-scope workspaces
may still link into the store.
`#cleanOrphanedTopLevelLinks` (run only under linked) additionally
removes stale real package directories — a directory containing a
`package.json` that is not in the ideal tree's valid top-level set — and
prunes an emptied `@scope` directory afterward. Non-package real
directories and symlinks pointing outside the project are still
preserved.
The valid-top-level collection in `#cleanOrphanedStoreEntries` no longer
skips non-link nodes, so the root's bundled dependencies — materialized
as real top-level directories under linked — are recorded as valid and
never swept as stale.
## References
Fixes#9615
Part of #9608
@pullpullBot locked and limited conversation to collaborators Jun 25, 2026
@pull
pullBot merged commit ca92323 into LadyK-21:latestJun 25, 2026
9 of 13 checks passed
@LadyK-21

Copy link
Copy Markdown
Owner

⚠️Snyk checks are incomplete.

StatusScan Engine Critical High Medium LowTotal (2)
⚠️Open Source Security1100 See details

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@LadyK-21@manzoorwanijk
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [pull] latest from npm:latest by pull[bot] · Pull Request #223 · LadyK-21/cli · GitHub
Skip to content

[pull] latest from npm:latest - #223

Merged
pull[bot] merged 5 commits into
LadyK-21:latestfrom
npm:latest
Jun 25, 2026
Merged

[pull] latest from npm:latest#223
pull[bot] merged 5 commits into
LadyK-21:latestfrom
npm:latest

Conversation

@pull

@pullpullBot commented Jun 25, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

…ategy (#9628)
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`, if a top-level `node_modules/<dep>`
symlink points to a store key that exists on disk but is the **wrong
version**, re-running `npm install` does not repair it. npm reports
success and leaves the dependency resolving to the wrong version. This
is the state an interrupted update leaves behind: the new store key is
extracted but the symlink has not yet been repointed.
A symlink pointing at a **non-existent** target is already repaired on
reinstall; only a wrong-but-existing target slips through, because
cleanup validates the link name, not its target.
## Why
For linked installs, `#buildLinkedActualForDiff` synthesizes the
"actual" tree the diff compares against from the **ideal** children,
never reading the real on-disk symlink target. So a link whose on-disk
target is a valid-but-wrong store key looks identical to the ideal node,
the diff reports no change, and the symlink is left untouched. The
hoisted strategy is unaffected because it self-heals the analogous
corruption.
## How
In `#buildLinkedActualForDiff`, when an existing link's resolved on-disk
target differs from its ideal target, skip creating a synthetic actual
entry for it. With no actual entry to match, the diff treats the link as
an `ADD`, and `#reifyNode` removes the old symlink and recreates it
pointing at the correct store key. A new `#linkTargetMismatch` helper
compares the two resolved targets; it runs only after the existing
`existsSync` guards, so both paths are known to exist.
This repairs both the top-level symlink and wrong transitive/sibling
links inside the store, and leaves already-correct trees untouched (no
spurious relinking on an idempotent reinstall).
## References
Fixes#9611
)
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`, several common installs failed with
`npm error invalid filterNode: outside idealTree/actualTree`, with no
workaround besides dropping the linked strategy. Hoisted handled all of
them. This fixes two distinct paths that both produced that error.
## Why
A linked reify diffs the ideal tree against a synthesized actual wrapper
(`#linkedActualForDiff`) rather than `this.actualTree`. `Diff.calculate`
rejects any filter node whose root is neither the ideal nor the actual
it was given, so a filter node taken from the real `this.actualTree` is
"outside" the diff and throws.
Two places fed it such nodes:
- `--workspaces=false` and `-w <ws> --include-workspace-root` go through
the `includeRootDeps` branch of `_diffTrees()`, which collected root-dep
edge targets from both `this.idealTree` and `this.actualTree`. The
actual-side targets are rooted at the real actual tree, not the wrapper,
so they tripped the guard. The sibling `includeWorkspaces` branch
already accounted for this; the root-dep branch did not.
- A global install with a per-call `installStrategy: 'linked'`
re-engaged the linked path even though the constructor normalizes global
installs to `shallow` (the linked layout is unsupported for globals).
Re-installing an already-present global package then hit the global
explicit-request branch, which pushes actual-side nodes, and tripped the
same guard. Suppressing the crash there was worse: the isolated reifier
does not materialize the global layout and removed the package instead.
## How
`_diffTrees()` now iterates only the ideal tree for root-dep filter
nodes when the linked wrapper is in use, matching the existing
workspace-node handling. The ideal-side nodes are sufficient to scope
the diff, and the post-reify orphan sweep continues to prune deps
removed from the manifest.
`reify()` now honors the constructor's global-to-shallow normalization
when deriving the `linked` flag, so a global install never engages the
linked path regardless of a per-call `installStrategy`. Global installs
fall back to shallow, which materializes and upgrades packages
correctly. No change to the global explicit-request branch is needed
once global is never linked.
## References
Fixes#9614
Part of #9608
Restores the global 100% coverage gate on `latest`, which broke after
#9626.
`filterLinkedStrategyEdges` in `lib/commands/ls.js` skips dev edges on
non-root packages — a guard added in #9095 to suppress false `UNMET
DEPENDENCY` output in the linked strategy. #9626 fixed the root cause
for store packages (they no longer load `devDependencies` as required
edges), so the store-based test no longer produces a dev edge, leaving
that branch unexercised.
Rather than ignore the line, this adds a regression test that genuinely
exercises the guard: arborist still loads dev edges for a `file:`-linked
transitive package, so listing one with `--all` under the linked
strategy reaches the guard at depth > 0 and confirms its devDependency
is suppressed instead of reported as `UNMET DEPENDENCY`. The full test
suite passes at 100%.
This is the `latest` counterpart of #9636, which restored coverage on
`release/v11` via an `istanbul ignore`.
## References
Follows up #9626
…egy (#9639)
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`, `npm exec -w <ws> -- <bin>` ignored a
workspace-local bin (provided by a sibling workspace dependency) and
fell through to the registry, producing a spurious `E404`. The hoisted
strategy ran the local bin correctly.
## Why
For a workspace exec, the command computed the local bin directory as
`resolve(this.npm.localDir, name, 'node_modules', '.bin')`, i.e.
`<root>/node_modules/<name>/node_modules/.bin`. That path only resolves
when the workspace is symlinked into the root `node_modules` as
`<name>`, which is how the hoisted strategy lays workspaces out. The
linked strategy does not hoist workspaces into the root `node_modules`;
the workspace's real bin lives at `<workspace>/node_modules/.bin`.
libnpmexec walks up from the given bin directory looking for
`node_modules/.bin/<bin>`, so starting from the nonexistent hoisted path
never reached the workspace's actual bin and the lookup fell back to the
registry.
## How
Base the local bin directory on the workspace's own path (`runPath`)
instead of the hoisted `localDir/<name>` location. This is correct under
both strategies: linked finds the bin in the workspace's
`node_modules/.bin`, and hoisted still finds the root-hoisted bin
because the walk-up continues from the workspace directory to the root
`node_modules/.bin`.
## References
Fixes#9616
…tch (#9647)
In continuation of our exploration of using `install-strategy=linked` in
the [Gutenberg
monorepo](WordPress/gutenberg#75814), which
powers the WordPress Block Editor.
Switching `install-strategy` in the same project directory left behind
the previous strategy's layout. Going hoisted → linked kept the stale
real top-level transitive directories alongside the new `.store/` and
symlinks; going linked → hoisted kept the entire `node_modules/.store/`
directory. A fresh install of either strategy was already clean — only
the switch was affected.
## Why
Under the linked strategy the actual tree the diff compares against is
synthesized from the ideal tree (`#buildLinkedActualForDiff`), so real
directories left over from a prior hoisted layout are never seen, and
`#cleanOrphanedTopLevelLinks` only removed symlinks. In the other
direction `load-actual` ignores dot-directories, so the hoisted diff
never sees `node_modules/.store` and never removes it.
## How
`reify.js` now removes the leftover `.store` on a non-linked reify via
`#removeStaleStoreDir`. The store lives only at the project root and is
exclusively a linked artifact, so a single removal covers the project.
It runs only for a full-project install — a workspace-filtered or
`--workspaces=false` install is skipped, because out-of-scope workspaces
may still link into the store.
`#cleanOrphanedTopLevelLinks` (run only under linked) additionally
removes stale real package directories — a directory containing a
`package.json` that is not in the ideal tree's valid top-level set — and
prunes an emptied `@scope` directory afterward. Non-package real
directories and symlinks pointing outside the project are still
preserved.
The valid-top-level collection in `#cleanOrphanedStoreEntries` no longer
skips non-link nodes, so the root's bundled dependencies — materialized
as real top-level directories under linked — are recorded as valid and
never swept as stale.
## References
Fixes#9615
Part of #9608
@pullpullBot locked and limited conversation to collaborators Jun 25, 2026
@pull
pullBot merged commit ca92323 into LadyK-21:latestJun 25, 2026
9 of 13 checks passed
@LadyK-21

Copy link
Copy Markdown
Owner

⚠️Snyk checks are incomplete.

StatusScan Engine Critical High Medium LowTotal (2)
⚠️Open Source Security1100 See details

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@LadyK-21@manzoorwanijk
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [pull] latest from npm:latest by pull[bot] · Pull Request #223 · LadyK-21/cli · GitHub
Skip to content

[pull] latest from npm:latest - #223

Merged
pull[bot] merged 5 commits into
LadyK-21:latestfrom
npm:latest
Jun 25, 2026
Merged

[pull] latest from npm:latest#223
pull[bot] merged 5 commits into
LadyK-21:latestfrom
npm:latest

Conversation

@pull

@pullpullBot commented Jun 25, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

…ategy (#9628)
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`, if a top-level `node_modules/<dep>`
symlink points to a store key that exists on disk but is the **wrong
version**, re-running `npm install` does not repair it. npm reports
success and leaves the dependency resolving to the wrong version. This
is the state an interrupted update leaves behind: the new store key is
extracted but the symlink has not yet been repointed.
A symlink pointing at a **non-existent** target is already repaired on
reinstall; only a wrong-but-existing target slips through, because
cleanup validates the link name, not its target.
## Why
For linked installs, `#buildLinkedActualForDiff` synthesizes the
"actual" tree the diff compares against from the **ideal** children,
never reading the real on-disk symlink target. So a link whose on-disk
target is a valid-but-wrong store key looks identical to the ideal node,
the diff reports no change, and the symlink is left untouched. The
hoisted strategy is unaffected because it self-heals the analogous
corruption.
## How
In `#buildLinkedActualForDiff`, when an existing link's resolved on-disk
target differs from its ideal target, skip creating a synthetic actual
entry for it. With no actual entry to match, the diff treats the link as
an `ADD`, and `#reifyNode` removes the old symlink and recreates it
pointing at the correct store key. A new `#linkTargetMismatch` helper
compares the two resolved targets; it runs only after the existing
`existsSync` guards, so both paths are known to exist.
This repairs both the top-level symlink and wrong transitive/sibling
links inside the store, and leaves already-correct trees untouched (no
spurious relinking on an idempotent reinstall).
## References
Fixes#9611
)
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`, several common installs failed with
`npm error invalid filterNode: outside idealTree/actualTree`, with no
workaround besides dropping the linked strategy. Hoisted handled all of
them. This fixes two distinct paths that both produced that error.
## Why
A linked reify diffs the ideal tree against a synthesized actual wrapper
(`#linkedActualForDiff`) rather than `this.actualTree`. `Diff.calculate`
rejects any filter node whose root is neither the ideal nor the actual
it was given, so a filter node taken from the real `this.actualTree` is
"outside" the diff and throws.
Two places fed it such nodes:
- `--workspaces=false` and `-w <ws> --include-workspace-root` go through
the `includeRootDeps` branch of `_diffTrees()`, which collected root-dep
edge targets from both `this.idealTree` and `this.actualTree`. The
actual-side targets are rooted at the real actual tree, not the wrapper,
so they tripped the guard. The sibling `includeWorkspaces` branch
already accounted for this; the root-dep branch did not.
- A global install with a per-call `installStrategy: 'linked'`
re-engaged the linked path even though the constructor normalizes global
installs to `shallow` (the linked layout is unsupported for globals).
Re-installing an already-present global package then hit the global
explicit-request branch, which pushes actual-side nodes, and tripped the
same guard. Suppressing the crash there was worse: the isolated reifier
does not materialize the global layout and removed the package instead.
## How
`_diffTrees()` now iterates only the ideal tree for root-dep filter
nodes when the linked wrapper is in use, matching the existing
workspace-node handling. The ideal-side nodes are sufficient to scope
the diff, and the post-reify orphan sweep continues to prune deps
removed from the manifest.
`reify()` now honors the constructor's global-to-shallow normalization
when deriving the `linked` flag, so a global install never engages the
linked path regardless of a per-call `installStrategy`. Global installs
fall back to shallow, which materializes and upgrades packages
correctly. No change to the global explicit-request branch is needed
once global is never linked.
## References
Fixes#9614
Part of #9608
Restores the global 100% coverage gate on `latest`, which broke after
#9626.
`filterLinkedStrategyEdges` in `lib/commands/ls.js` skips dev edges on
non-root packages — a guard added in #9095 to suppress false `UNMET
DEPENDENCY` output in the linked strategy. #9626 fixed the root cause
for store packages (they no longer load `devDependencies` as required
edges), so the store-based test no longer produces a dev edge, leaving
that branch unexercised.
Rather than ignore the line, this adds a regression test that genuinely
exercises the guard: arborist still loads dev edges for a `file:`-linked
transitive package, so listing one with `--all` under the linked
strategy reaches the guard at depth > 0 and confirms its devDependency
is suppressed instead of reported as `UNMET DEPENDENCY`. The full test
suite passes at 100%.
This is the `latest` counterpart of #9636, which restored coverage on
`release/v11` via an `istanbul ignore`.
## References
Follows up #9626
…egy (#9639)
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`, `npm exec -w <ws> -- <bin>` ignored a
workspace-local bin (provided by a sibling workspace dependency) and
fell through to the registry, producing a spurious `E404`. The hoisted
strategy ran the local bin correctly.
## Why
For a workspace exec, the command computed the local bin directory as
`resolve(this.npm.localDir, name, 'node_modules', '.bin')`, i.e.
`<root>/node_modules/<name>/node_modules/.bin`. That path only resolves
when the workspace is symlinked into the root `node_modules` as
`<name>`, which is how the hoisted strategy lays workspaces out. The
linked strategy does not hoist workspaces into the root `node_modules`;
the workspace's real bin lives at `<workspace>/node_modules/.bin`.
libnpmexec walks up from the given bin directory looking for
`node_modules/.bin/<bin>`, so starting from the nonexistent hoisted path
never reached the workspace's actual bin and the lookup fell back to the
registry.
## How
Base the local bin directory on the workspace's own path (`runPath`)
instead of the hoisted `localDir/<name>` location. This is correct under
both strategies: linked finds the bin in the workspace's
`node_modules/.bin`, and hoisted still finds the root-hoisted bin
because the walk-up continues from the workspace directory to the root
`node_modules/.bin`.
## References
Fixes#9616
…tch (#9647)
In continuation of our exploration of using `install-strategy=linked` in
the [Gutenberg
monorepo](WordPress/gutenberg#75814), which
powers the WordPress Block Editor.
Switching `install-strategy` in the same project directory left behind
the previous strategy's layout. Going hoisted → linked kept the stale
real top-level transitive directories alongside the new `.store/` and
symlinks; going linked → hoisted kept the entire `node_modules/.store/`
directory. A fresh install of either strategy was already clean — only
the switch was affected.
## Why
Under the linked strategy the actual tree the diff compares against is
synthesized from the ideal tree (`#buildLinkedActualForDiff`), so real
directories left over from a prior hoisted layout are never seen, and
`#cleanOrphanedTopLevelLinks` only removed symlinks. In the other
direction `load-actual` ignores dot-directories, so the hoisted diff
never sees `node_modules/.store` and never removes it.
## How
`reify.js` now removes the leftover `.store` on a non-linked reify via
`#removeStaleStoreDir`. The store lives only at the project root and is
exclusively a linked artifact, so a single removal covers the project.
It runs only for a full-project install — a workspace-filtered or
`--workspaces=false` install is skipped, because out-of-scope workspaces
may still link into the store.
`#cleanOrphanedTopLevelLinks` (run only under linked) additionally
removes stale real package directories — a directory containing a
`package.json` that is not in the ideal tree's valid top-level set — and
prunes an emptied `@scope` directory afterward. Non-package real
directories and symlinks pointing outside the project are still
preserved.
The valid-top-level collection in `#cleanOrphanedStoreEntries` no longer
skips non-link nodes, so the root's bundled dependencies — materialized
as real top-level directories under linked — are recorded as valid and
never swept as stale.
## References
Fixes#9615
Part of #9608
@pullpullBot locked and limited conversation to collaborators Jun 25, 2026
@pull
pullBot merged commit ca92323 into LadyK-21:latestJun 25, 2026
9 of 13 checks passed
@LadyK-21

Copy link
Copy Markdown
Owner

⚠️Snyk checks are incomplete.

StatusScan Engine Critical High Medium LowTotal (2)
⚠️Open Source Security1100 See details

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@LadyK-21@manzoorwanijk
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' [pull] latest from npm:latest by pull[bot] · Pull Request #223 · LadyK-21/cli · GitHub
Skip to content

[pull] latest from npm:latest - #223

Merged
pull[bot] merged 5 commits into
LadyK-21:latestfrom
npm:latest
Jun 25, 2026
Merged

[pull] latest from npm:latest#223
pull[bot] merged 5 commits into
LadyK-21:latestfrom
npm:latest

Conversation

@pull

@pullpullBot commented Jun 25, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

…ategy (#9628)
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`, if a top-level `node_modules/<dep>`
symlink points to a store key that exists on disk but is the **wrong
version**, re-running `npm install` does not repair it. npm reports
success and leaves the dependency resolving to the wrong version. This
is the state an interrupted update leaves behind: the new store key is
extracted but the symlink has not yet been repointed.
A symlink pointing at a **non-existent** target is already repaired on
reinstall; only a wrong-but-existing target slips through, because
cleanup validates the link name, not its target.
## Why
For linked installs, `#buildLinkedActualForDiff` synthesizes the
"actual" tree the diff compares against from the **ideal** children,
never reading the real on-disk symlink target. So a link whose on-disk
target is a valid-but-wrong store key looks identical to the ideal node,
the diff reports no change, and the symlink is left untouched. The
hoisted strategy is unaffected because it self-heals the analogous
corruption.
## How
In `#buildLinkedActualForDiff`, when an existing link's resolved on-disk
target differs from its ideal target, skip creating a synthetic actual
entry for it. With no actual entry to match, the diff treats the link as
an `ADD`, and `#reifyNode` removes the old symlink and recreates it
pointing at the correct store key. A new `#linkTargetMismatch` helper
compares the two resolved targets; it runs only after the existing
`existsSync` guards, so both paths are known to exist.
This repairs both the top-level symlink and wrong transitive/sibling
links inside the store, and leaves already-correct trees untouched (no
spurious relinking on an idempotent reinstall).
## References
Fixes#9611
)
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`, several common installs failed with
`npm error invalid filterNode: outside idealTree/actualTree`, with no
workaround besides dropping the linked strategy. Hoisted handled all of
them. This fixes two distinct paths that both produced that error.
## Why
A linked reify diffs the ideal tree against a synthesized actual wrapper
(`#linkedActualForDiff`) rather than `this.actualTree`. `Diff.calculate`
rejects any filter node whose root is neither the ideal nor the actual
it was given, so a filter node taken from the real `this.actualTree` is
"outside" the diff and throws.
Two places fed it such nodes:
- `--workspaces=false` and `-w <ws> --include-workspace-root` go through
the `includeRootDeps` branch of `_diffTrees()`, which collected root-dep
edge targets from both `this.idealTree` and `this.actualTree`. The
actual-side targets are rooted at the real actual tree, not the wrapper,
so they tripped the guard. The sibling `includeWorkspaces` branch
already accounted for this; the root-dep branch did not.
- A global install with a per-call `installStrategy: 'linked'`
re-engaged the linked path even though the constructor normalizes global
installs to `shallow` (the linked layout is unsupported for globals).
Re-installing an already-present global package then hit the global
explicit-request branch, which pushes actual-side nodes, and tripped the
same guard. Suppressing the crash there was worse: the isolated reifier
does not materialize the global layout and removed the package instead.
## How
`_diffTrees()` now iterates only the ideal tree for root-dep filter
nodes when the linked wrapper is in use, matching the existing
workspace-node handling. The ideal-side nodes are sufficient to scope
the diff, and the post-reify orphan sweep continues to prune deps
removed from the manifest.
`reify()` now honors the constructor's global-to-shallow normalization
when deriving the `linked` flag, so a global install never engages the
linked path regardless of a per-call `installStrategy`. Global installs
fall back to shallow, which materializes and upgrades packages
correctly. No change to the global explicit-request branch is needed
once global is never linked.
## References
Fixes#9614
Part of #9608
Restores the global 100% coverage gate on `latest`, which broke after
#9626.
`filterLinkedStrategyEdges` in `lib/commands/ls.js` skips dev edges on
non-root packages — a guard added in #9095 to suppress false `UNMET
DEPENDENCY` output in the linked strategy. #9626 fixed the root cause
for store packages (they no longer load `devDependencies` as required
edges), so the store-based test no longer produces a dev edge, leaving
that branch unexercised.
Rather than ignore the line, this adds a regression test that genuinely
exercises the guard: arborist still loads dev edges for a `file:`-linked
transitive package, so listing one with `--all` under the linked
strategy reaches the guard at depth > 0 and confirms its devDependency
is suppressed instead of reported as `UNMET DEPENDENCY`. The full test
suite passes at 100%.
This is the `latest` counterpart of #9636, which restored coverage on
`release/v11` via an `istanbul ignore`.
## References
Follows up #9626
…egy (#9639)
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`, `npm exec -w <ws> -- <bin>` ignored a
workspace-local bin (provided by a sibling workspace dependency) and
fell through to the registry, producing a spurious `E404`. The hoisted
strategy ran the local bin correctly.
## Why
For a workspace exec, the command computed the local bin directory as
`resolve(this.npm.localDir, name, 'node_modules', '.bin')`, i.e.
`<root>/node_modules/<name>/node_modules/.bin`. That path only resolves
when the workspace is symlinked into the root `node_modules` as
`<name>`, which is how the hoisted strategy lays workspaces out. The
linked strategy does not hoist workspaces into the root `node_modules`;
the workspace's real bin lives at `<workspace>/node_modules/.bin`.
libnpmexec walks up from the given bin directory looking for
`node_modules/.bin/<bin>`, so starting from the nonexistent hoisted path
never reached the workspace's actual bin and the lookup fell back to the
registry.
## How
Base the local bin directory on the workspace's own path (`runPath`)
instead of the hoisted `localDir/<name>` location. This is correct under
both strategies: linked finds the bin in the workspace's
`node_modules/.bin`, and hoisted still finds the root-hoisted bin
because the walk-up continues from the workspace directory to the root
`node_modules/.bin`.
## References
Fixes#9616
…tch (#9647)
In continuation of our exploration of using `install-strategy=linked` in
the [Gutenberg
monorepo](WordPress/gutenberg#75814), which
powers the WordPress Block Editor.
Switching `install-strategy` in the same project directory left behind
the previous strategy's layout. Going hoisted → linked kept the stale
real top-level transitive directories alongside the new `.store/` and
symlinks; going linked → hoisted kept the entire `node_modules/.store/`
directory. A fresh install of either strategy was already clean — only
the switch was affected.
## Why
Under the linked strategy the actual tree the diff compares against is
synthesized from the ideal tree (`#buildLinkedActualForDiff`), so real
directories left over from a prior hoisted layout are never seen, and
`#cleanOrphanedTopLevelLinks` only removed symlinks. In the other
direction `load-actual` ignores dot-directories, so the hoisted diff
never sees `node_modules/.store` and never removes it.
## How
`reify.js` now removes the leftover `.store` on a non-linked reify via
`#removeStaleStoreDir`. The store lives only at the project root and is
exclusively a linked artifact, so a single removal covers the project.
It runs only for a full-project install — a workspace-filtered or
`--workspaces=false` install is skipped, because out-of-scope workspaces
may still link into the store.
`#cleanOrphanedTopLevelLinks` (run only under linked) additionally
removes stale real package directories — a directory containing a
`package.json` that is not in the ideal tree's valid top-level set — and
prunes an emptied `@scope` directory afterward. Non-package real
directories and symlinks pointing outside the project are still
preserved.
The valid-top-level collection in `#cleanOrphanedStoreEntries` no longer
skips non-link nodes, so the root's bundled dependencies — materialized
as real top-level directories under linked — are recorded as valid and
never swept as stale.
## References
Fixes#9615
Part of #9608
@pullpullBot locked and limited conversation to collaborators Jun 25, 2026
@pull
pullBot merged commit ca92323 into LadyK-21:latestJun 25, 2026
9 of 13 checks passed
@LadyK-21

Copy link
Copy Markdown
Owner

⚠️Snyk checks are incomplete.

StatusScan Engine Critical High Medium LowTotal (2)
⚠️Open Source Security1100 See details

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@LadyK-21@manzoorwanijk
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [pull] latest from npm:latest by pull[bot] · Pull Request #223 · LadyK-21/cli · GitHub
Skip to content

[pull] latest from npm:latest - #223

Merged
pull[bot] merged 5 commits into
LadyK-21:latestfrom
npm:latest
Jun 25, 2026
Merged

[pull] latest from npm:latest#223
pull[bot] merged 5 commits into
LadyK-21:latestfrom
npm:latest

Conversation

@pull

@pullpullBot commented Jun 25, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

…ategy (#9628)
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`, if a top-level `node_modules/<dep>`
symlink points to a store key that exists on disk but is the **wrong
version**, re-running `npm install` does not repair it. npm reports
success and leaves the dependency resolving to the wrong version. This
is the state an interrupted update leaves behind: the new store key is
extracted but the symlink has not yet been repointed.
A symlink pointing at a **non-existent** target is already repaired on
reinstall; only a wrong-but-existing target slips through, because
cleanup validates the link name, not its target.
## Why
For linked installs, `#buildLinkedActualForDiff` synthesizes the
"actual" tree the diff compares against from the **ideal** children,
never reading the real on-disk symlink target. So a link whose on-disk
target is a valid-but-wrong store key looks identical to the ideal node,
the diff reports no change, and the symlink is left untouched. The
hoisted strategy is unaffected because it self-heals the analogous
corruption.
## How
In `#buildLinkedActualForDiff`, when an existing link's resolved on-disk
target differs from its ideal target, skip creating a synthetic actual
entry for it. With no actual entry to match, the diff treats the link as
an `ADD`, and `#reifyNode` removes the old symlink and recreates it
pointing at the correct store key. A new `#linkTargetMismatch` helper
compares the two resolved targets; it runs only after the existing
`existsSync` guards, so both paths are known to exist.
This repairs both the top-level symlink and wrong transitive/sibling
links inside the store, and leaves already-correct trees untouched (no
spurious relinking on an idempotent reinstall).
## References
Fixes#9611
)
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`, several common installs failed with
`npm error invalid filterNode: outside idealTree/actualTree`, with no
workaround besides dropping the linked strategy. Hoisted handled all of
them. This fixes two distinct paths that both produced that error.
## Why
A linked reify diffs the ideal tree against a synthesized actual wrapper
(`#linkedActualForDiff`) rather than `this.actualTree`. `Diff.calculate`
rejects any filter node whose root is neither the ideal nor the actual
it was given, so a filter node taken from the real `this.actualTree` is
"outside" the diff and throws.
Two places fed it such nodes:
- `--workspaces=false` and `-w <ws> --include-workspace-root` go through
the `includeRootDeps` branch of `_diffTrees()`, which collected root-dep
edge targets from both `this.idealTree` and `this.actualTree`. The
actual-side targets are rooted at the real actual tree, not the wrapper,
so they tripped the guard. The sibling `includeWorkspaces` branch
already accounted for this; the root-dep branch did not.
- A global install with a per-call `installStrategy: 'linked'`
re-engaged the linked path even though the constructor normalizes global
installs to `shallow` (the linked layout is unsupported for globals).
Re-installing an already-present global package then hit the global
explicit-request branch, which pushes actual-side nodes, and tripped the
same guard. Suppressing the crash there was worse: the isolated reifier
does not materialize the global layout and removed the package instead.
## How
`_diffTrees()` now iterates only the ideal tree for root-dep filter
nodes when the linked wrapper is in use, matching the existing
workspace-node handling. The ideal-side nodes are sufficient to scope
the diff, and the post-reify orphan sweep continues to prune deps
removed from the manifest.
`reify()` now honors the constructor's global-to-shallow normalization
when deriving the `linked` flag, so a global install never engages the
linked path regardless of a per-call `installStrategy`. Global installs
fall back to shallow, which materializes and upgrades packages
correctly. No change to the global explicit-request branch is needed
once global is never linked.
## References
Fixes#9614
Part of #9608
Restores the global 100% coverage gate on `latest`, which broke after
#9626.
`filterLinkedStrategyEdges` in `lib/commands/ls.js` skips dev edges on
non-root packages — a guard added in #9095 to suppress false `UNMET
DEPENDENCY` output in the linked strategy. #9626 fixed the root cause
for store packages (they no longer load `devDependencies` as required
edges), so the store-based test no longer produces a dev edge, leaving
that branch unexercised.
Rather than ignore the line, this adds a regression test that genuinely
exercises the guard: arborist still loads dev edges for a `file:`-linked
transitive package, so listing one with `--all` under the linked
strategy reaches the guard at depth > 0 and confirms its devDependency
is suppressed instead of reported as `UNMET DEPENDENCY`. The full test
suite passes at 100%.
This is the `latest` counterpart of #9636, which restored coverage on
`release/v11` via an `istanbul ignore`.
## References
Follows up #9626
…egy (#9639)
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`, `npm exec -w <ws> -- <bin>` ignored a
workspace-local bin (provided by a sibling workspace dependency) and
fell through to the registry, producing a spurious `E404`. The hoisted
strategy ran the local bin correctly.
## Why
For a workspace exec, the command computed the local bin directory as
`resolve(this.npm.localDir, name, 'node_modules', '.bin')`, i.e.
`<root>/node_modules/<name>/node_modules/.bin`. That path only resolves
when the workspace is symlinked into the root `node_modules` as
`<name>`, which is how the hoisted strategy lays workspaces out. The
linked strategy does not hoist workspaces into the root `node_modules`;
the workspace's real bin lives at `<workspace>/node_modules/.bin`.
libnpmexec walks up from the given bin directory looking for
`node_modules/.bin/<bin>`, so starting from the nonexistent hoisted path
never reached the workspace's actual bin and the lookup fell back to the
registry.
## How
Base the local bin directory on the workspace's own path (`runPath`)
instead of the hoisted `localDir/<name>` location. This is correct under
both strategies: linked finds the bin in the workspace's
`node_modules/.bin`, and hoisted still finds the root-hoisted bin
because the walk-up continues from the workspace directory to the root
`node_modules/.bin`.
## References
Fixes#9616
…tch (#9647)
In continuation of our exploration of using `install-strategy=linked` in
the [Gutenberg
monorepo](WordPress/gutenberg#75814), which
powers the WordPress Block Editor.
Switching `install-strategy` in the same project directory left behind
the previous strategy's layout. Going hoisted → linked kept the stale
real top-level transitive directories alongside the new `.store/` and
symlinks; going linked → hoisted kept the entire `node_modules/.store/`
directory. A fresh install of either strategy was already clean — only
the switch was affected.
## Why
Under the linked strategy the actual tree the diff compares against is
synthesized from the ideal tree (`#buildLinkedActualForDiff`), so real
directories left over from a prior hoisted layout are never seen, and
`#cleanOrphanedTopLevelLinks` only removed symlinks. In the other
direction `load-actual` ignores dot-directories, so the hoisted diff
never sees `node_modules/.store` and never removes it.
## How
`reify.js` now removes the leftover `.store` on a non-linked reify via
`#removeStaleStoreDir`. The store lives only at the project root and is
exclusively a linked artifact, so a single removal covers the project.
It runs only for a full-project install — a workspace-filtered or
`--workspaces=false` install is skipped, because out-of-scope workspaces
may still link into the store.
`#cleanOrphanedTopLevelLinks` (run only under linked) additionally
removes stale real package directories — a directory containing a
`package.json` that is not in the ideal tree's valid top-level set — and
prunes an emptied `@scope` directory afterward. Non-package real
directories and symlinks pointing outside the project are still
preserved.
The valid-top-level collection in `#cleanOrphanedStoreEntries` no longer
skips non-link nodes, so the root's bundled dependencies — materialized
as real top-level directories under linked — are recorded as valid and
never swept as stale.
## References
Fixes#9615
Part of #9608
@pullpullBot locked and limited conversation to collaborators Jun 25, 2026
@pull
pullBot merged commit ca92323 into LadyK-21:latestJun 25, 2026
9 of 13 checks passed
@LadyK-21

Copy link
Copy Markdown
Owner

⚠️Snyk checks are incomplete.

StatusScan Engine Critical High Medium LowTotal (2)
⚠️Open Source Security1100 See details

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@LadyK-21@manzoorwanijk
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [pull] latest from npm:latest by pull[bot] · Pull Request #223 · LadyK-21/cli · GitHub
Skip to content

[pull] latest from npm:latest - #223

Merged
pull[bot] merged 5 commits into
LadyK-21:latestfrom
npm:latest
Jun 25, 2026
Merged

[pull] latest from npm:latest#223
pull[bot] merged 5 commits into
LadyK-21:latestfrom
npm:latest

Conversation

@pull

@pullpullBot commented Jun 25, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

…ategy (#9628)
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`, if a top-level `node_modules/<dep>`
symlink points to a store key that exists on disk but is the **wrong
version**, re-running `npm install` does not repair it. npm reports
success and leaves the dependency resolving to the wrong version. This
is the state an interrupted update leaves behind: the new store key is
extracted but the symlink has not yet been repointed.
A symlink pointing at a **non-existent** target is already repaired on
reinstall; only a wrong-but-existing target slips through, because
cleanup validates the link name, not its target.
## Why
For linked installs, `#buildLinkedActualForDiff` synthesizes the
"actual" tree the diff compares against from the **ideal** children,
never reading the real on-disk symlink target. So a link whose on-disk
target is a valid-but-wrong store key looks identical to the ideal node,
the diff reports no change, and the symlink is left untouched. The
hoisted strategy is unaffected because it self-heals the analogous
corruption.
## How
In `#buildLinkedActualForDiff`, when an existing link's resolved on-disk
target differs from its ideal target, skip creating a synthetic actual
entry for it. With no actual entry to match, the diff treats the link as
an `ADD`, and `#reifyNode` removes the old symlink and recreates it
pointing at the correct store key. A new `#linkTargetMismatch` helper
compares the two resolved targets; it runs only after the existing
`existsSync` guards, so both paths are known to exist.
This repairs both the top-level symlink and wrong transitive/sibling
links inside the store, and leaves already-correct trees untouched (no
spurious relinking on an idempotent reinstall).
## References
Fixes#9611
)
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`, several common installs failed with
`npm error invalid filterNode: outside idealTree/actualTree`, with no
workaround besides dropping the linked strategy. Hoisted handled all of
them. This fixes two distinct paths that both produced that error.
## Why
A linked reify diffs the ideal tree against a synthesized actual wrapper
(`#linkedActualForDiff`) rather than `this.actualTree`. `Diff.calculate`
rejects any filter node whose root is neither the ideal nor the actual
it was given, so a filter node taken from the real `this.actualTree` is
"outside" the diff and throws.
Two places fed it such nodes:
- `--workspaces=false` and `-w <ws> --include-workspace-root` go through
the `includeRootDeps` branch of `_diffTrees()`, which collected root-dep
edge targets from both `this.idealTree` and `this.actualTree`. The
actual-side targets are rooted at the real actual tree, not the wrapper,
so they tripped the guard. The sibling `includeWorkspaces` branch
already accounted for this; the root-dep branch did not.
- A global install with a per-call `installStrategy: 'linked'`
re-engaged the linked path even though the constructor normalizes global
installs to `shallow` (the linked layout is unsupported for globals).
Re-installing an already-present global package then hit the global
explicit-request branch, which pushes actual-side nodes, and tripped the
same guard. Suppressing the crash there was worse: the isolated reifier
does not materialize the global layout and removed the package instead.
## How
`_diffTrees()` now iterates only the ideal tree for root-dep filter
nodes when the linked wrapper is in use, matching the existing
workspace-node handling. The ideal-side nodes are sufficient to scope
the diff, and the post-reify orphan sweep continues to prune deps
removed from the manifest.
`reify()` now honors the constructor's global-to-shallow normalization
when deriving the `linked` flag, so a global install never engages the
linked path regardless of a per-call `installStrategy`. Global installs
fall back to shallow, which materializes and upgrades packages
correctly. No change to the global explicit-request branch is needed
once global is never linked.
## References
Fixes#9614
Part of #9608
Restores the global 100% coverage gate on `latest`, which broke after
#9626.
`filterLinkedStrategyEdges` in `lib/commands/ls.js` skips dev edges on
non-root packages — a guard added in #9095 to suppress false `UNMET
DEPENDENCY` output in the linked strategy. #9626 fixed the root cause
for store packages (they no longer load `devDependencies` as required
edges), so the store-based test no longer produces a dev edge, leaving
that branch unexercised.
Rather than ignore the line, this adds a regression test that genuinely
exercises the guard: arborist still loads dev edges for a `file:`-linked
transitive package, so listing one with `--all` under the linked
strategy reaches the guard at depth > 0 and confirms its devDependency
is suppressed instead of reported as `UNMET DEPENDENCY`. The full test
suite passes at 100%.
This is the `latest` counterpart of #9636, which restored coverage on
`release/v11` via an `istanbul ignore`.
## References
Follows up #9626
…egy (#9639)
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`, `npm exec -w <ws> -- <bin>` ignored a
workspace-local bin (provided by a sibling workspace dependency) and
fell through to the registry, producing a spurious `E404`. The hoisted
strategy ran the local bin correctly.
## Why
For a workspace exec, the command computed the local bin directory as
`resolve(this.npm.localDir, name, 'node_modules', '.bin')`, i.e.
`<root>/node_modules/<name>/node_modules/.bin`. That path only resolves
when the workspace is symlinked into the root `node_modules` as
`<name>`, which is how the hoisted strategy lays workspaces out. The
linked strategy does not hoist workspaces into the root `node_modules`;
the workspace's real bin lives at `<workspace>/node_modules/.bin`.
libnpmexec walks up from the given bin directory looking for
`node_modules/.bin/<bin>`, so starting from the nonexistent hoisted path
never reached the workspace's actual bin and the lookup fell back to the
registry.
## How
Base the local bin directory on the workspace's own path (`runPath`)
instead of the hoisted `localDir/<name>` location. This is correct under
both strategies: linked finds the bin in the workspace's
`node_modules/.bin`, and hoisted still finds the root-hoisted bin
because the walk-up continues from the workspace directory to the root
`node_modules/.bin`.
## References
Fixes#9616
…tch (#9647)
In continuation of our exploration of using `install-strategy=linked` in
the [Gutenberg
monorepo](WordPress/gutenberg#75814), which
powers the WordPress Block Editor.
Switching `install-strategy` in the same project directory left behind
the previous strategy's layout. Going hoisted → linked kept the stale
real top-level transitive directories alongside the new `.store/` and
symlinks; going linked → hoisted kept the entire `node_modules/.store/`
directory. A fresh install of either strategy was already clean — only
the switch was affected.
## Why
Under the linked strategy the actual tree the diff compares against is
synthesized from the ideal tree (`#buildLinkedActualForDiff`), so real
directories left over from a prior hoisted layout are never seen, and
`#cleanOrphanedTopLevelLinks` only removed symlinks. In the other
direction `load-actual` ignores dot-directories, so the hoisted diff
never sees `node_modules/.store` and never removes it.
## How
`reify.js` now removes the leftover `.store` on a non-linked reify via
`#removeStaleStoreDir`. The store lives only at the project root and is
exclusively a linked artifact, so a single removal covers the project.
It runs only for a full-project install — a workspace-filtered or
`--workspaces=false` install is skipped, because out-of-scope workspaces
may still link into the store.
`#cleanOrphanedTopLevelLinks` (run only under linked) additionally
removes stale real package directories — a directory containing a
`package.json` that is not in the ideal tree's valid top-level set — and
prunes an emptied `@scope` directory afterward. Non-package real
directories and symlinks pointing outside the project are still
preserved.
The valid-top-level collection in `#cleanOrphanedStoreEntries` no longer
skips non-link nodes, so the root's bundled dependencies — materialized
as real top-level directories under linked — are recorded as valid and
never swept as stale.
## References
Fixes#9615
Part of #9608
@pullpullBot locked and limited conversation to collaborators Jun 25, 2026
@pull
pullBot merged commit ca92323 into LadyK-21:latestJun 25, 2026
9 of 13 checks passed
@LadyK-21

Copy link
Copy Markdown
Owner

⚠️Snyk checks are incomplete.

StatusScan Engine Critical High Medium LowTotal (2)
⚠️Open Source Security1100 See details

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@LadyK-21@manzoorwanijk
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); [pull] latest from npm:latest by pull[bot] · Pull Request #223 · LadyK-21/cli · GitHub
Skip to content

[pull] latest from npm:latest - #223

Merged
pull[bot] merged 5 commits into
LadyK-21:latestfrom
npm:latest
Jun 25, 2026
Merged

[pull] latest from npm:latest#223
pull[bot] merged 5 commits into
LadyK-21:latestfrom
npm:latest

Conversation

@pull

@pullpullBot commented Jun 25, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

…ategy (#9628)
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`, if a top-level `node_modules/<dep>`
symlink points to a store key that exists on disk but is the **wrong
version**, re-running `npm install` does not repair it. npm reports
success and leaves the dependency resolving to the wrong version. This
is the state an interrupted update leaves behind: the new store key is
extracted but the symlink has not yet been repointed.
A symlink pointing at a **non-existent** target is already repaired on
reinstall; only a wrong-but-existing target slips through, because
cleanup validates the link name, not its target.
## Why
For linked installs, `#buildLinkedActualForDiff` synthesizes the
"actual" tree the diff compares against from the **ideal** children,
never reading the real on-disk symlink target. So a link whose on-disk
target is a valid-but-wrong store key looks identical to the ideal node,
the diff reports no change, and the symlink is left untouched. The
hoisted strategy is unaffected because it self-heals the analogous
corruption.
## How
In `#buildLinkedActualForDiff`, when an existing link's resolved on-disk
target differs from its ideal target, skip creating a synthetic actual
entry for it. With no actual entry to match, the diff treats the link as
an `ADD`, and `#reifyNode` removes the old symlink and recreates it
pointing at the correct store key. A new `#linkTargetMismatch` helper
compares the two resolved targets; it runs only after the existing
`existsSync` guards, so both paths are known to exist.
This repairs both the top-level symlink and wrong transitive/sibling
links inside the store, and leaves already-correct trees untouched (no
spurious relinking on an idempotent reinstall).
## References
Fixes#9611
)
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`, several common installs failed with
`npm error invalid filterNode: outside idealTree/actualTree`, with no
workaround besides dropping the linked strategy. Hoisted handled all of
them. This fixes two distinct paths that both produced that error.
## Why
A linked reify diffs the ideal tree against a synthesized actual wrapper
(`#linkedActualForDiff`) rather than `this.actualTree`. `Diff.calculate`
rejects any filter node whose root is neither the ideal nor the actual
it was given, so a filter node taken from the real `this.actualTree` is
"outside" the diff and throws.
Two places fed it such nodes:
- `--workspaces=false` and `-w <ws> --include-workspace-root` go through
the `includeRootDeps` branch of `_diffTrees()`, which collected root-dep
edge targets from both `this.idealTree` and `this.actualTree`. The
actual-side targets are rooted at the real actual tree, not the wrapper,
so they tripped the guard. The sibling `includeWorkspaces` branch
already accounted for this; the root-dep branch did not.
- A global install with a per-call `installStrategy: 'linked'`
re-engaged the linked path even though the constructor normalizes global
installs to `shallow` (the linked layout is unsupported for globals).
Re-installing an already-present global package then hit the global
explicit-request branch, which pushes actual-side nodes, and tripped the
same guard. Suppressing the crash there was worse: the isolated reifier
does not materialize the global layout and removed the package instead.
## How
`_diffTrees()` now iterates only the ideal tree for root-dep filter
nodes when the linked wrapper is in use, matching the existing
workspace-node handling. The ideal-side nodes are sufficient to scope
the diff, and the post-reify orphan sweep continues to prune deps
removed from the manifest.
`reify()` now honors the constructor's global-to-shallow normalization
when deriving the `linked` flag, so a global install never engages the
linked path regardless of a per-call `installStrategy`. Global installs
fall back to shallow, which materializes and upgrades packages
correctly. No change to the global explicit-request branch is needed
once global is never linked.
## References
Fixes#9614
Part of #9608
Restores the global 100% coverage gate on `latest`, which broke after
#9626.
`filterLinkedStrategyEdges` in `lib/commands/ls.js` skips dev edges on
non-root packages — a guard added in #9095 to suppress false `UNMET
DEPENDENCY` output in the linked strategy. #9626 fixed the root cause
for store packages (they no longer load `devDependencies` as required
edges), so the store-based test no longer produces a dev edge, leaving
that branch unexercised.
Rather than ignore the line, this adds a regression test that genuinely
exercises the guard: arborist still loads dev edges for a `file:`-linked
transitive package, so listing one with `--all` under the linked
strategy reaches the guard at depth > 0 and confirms its devDependency
is suppressed instead of reported as `UNMET DEPENDENCY`. The full test
suite passes at 100%.
This is the `latest` counterpart of #9636, which restored coverage on
`release/v11` via an `istanbul ignore`.
## References
Follows up #9626
…egy (#9639)
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`, `npm exec -w <ws> -- <bin>` ignored a
workspace-local bin (provided by a sibling workspace dependency) and
fell through to the registry, producing a spurious `E404`. The hoisted
strategy ran the local bin correctly.
## Why
For a workspace exec, the command computed the local bin directory as
`resolve(this.npm.localDir, name, 'node_modules', '.bin')`, i.e.
`<root>/node_modules/<name>/node_modules/.bin`. That path only resolves
when the workspace is symlinked into the root `node_modules` as
`<name>`, which is how the hoisted strategy lays workspaces out. The
linked strategy does not hoist workspaces into the root `node_modules`;
the workspace's real bin lives at `<workspace>/node_modules/.bin`.
libnpmexec walks up from the given bin directory looking for
`node_modules/.bin/<bin>`, so starting from the nonexistent hoisted path
never reached the workspace's actual bin and the lookup fell back to the
registry.
## How
Base the local bin directory on the workspace's own path (`runPath`)
instead of the hoisted `localDir/<name>` location. This is correct under
both strategies: linked finds the bin in the workspace's
`node_modules/.bin`, and hoisted still finds the root-hoisted bin
because the walk-up continues from the workspace directory to the root
`node_modules/.bin`.
## References
Fixes#9616
…tch (#9647)
In continuation of our exploration of using `install-strategy=linked` in
the [Gutenberg
monorepo](WordPress/gutenberg#75814), which
powers the WordPress Block Editor.
Switching `install-strategy` in the same project directory left behind
the previous strategy's layout. Going hoisted → linked kept the stale
real top-level transitive directories alongside the new `.store/` and
symlinks; going linked → hoisted kept the entire `node_modules/.store/`
directory. A fresh install of either strategy was already clean — only
the switch was affected.
## Why
Under the linked strategy the actual tree the diff compares against is
synthesized from the ideal tree (`#buildLinkedActualForDiff`), so real
directories left over from a prior hoisted layout are never seen, and
`#cleanOrphanedTopLevelLinks` only removed symlinks. In the other
direction `load-actual` ignores dot-directories, so the hoisted diff
never sees `node_modules/.store` and never removes it.
## How
`reify.js` now removes the leftover `.store` on a non-linked reify via
`#removeStaleStoreDir`. The store lives only at the project root and is
exclusively a linked artifact, so a single removal covers the project.
It runs only for a full-project install — a workspace-filtered or
`--workspaces=false` install is skipped, because out-of-scope workspaces
may still link into the store.
`#cleanOrphanedTopLevelLinks` (run only under linked) additionally
removes stale real package directories — a directory containing a
`package.json` that is not in the ideal tree's valid top-level set — and
prunes an emptied `@scope` directory afterward. Non-package real
directories and symlinks pointing outside the project are still
preserved.
The valid-top-level collection in `#cleanOrphanedStoreEntries` no longer
skips non-link nodes, so the root's bundled dependencies — materialized
as real top-level directories under linked — are recorded as valid and
never swept as stale.
## References
Fixes#9615
Part of #9608
@pullpullBot locked and limited conversation to collaborators Jun 25, 2026
@pull
pullBot merged commit ca92323 into LadyK-21:latestJun 25, 2026
9 of 13 checks passed
@LadyK-21

Copy link
Copy Markdown
Owner

⚠️Snyk checks are incomplete.

StatusScan Engine Critical High Medium LowTotal (2)
⚠️Open Source Security1100 See details

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@LadyK-21@manzoorwanijk