Visitar URL original
fix(arborist): preserve explicit npm alias target identities by myevolve · Pull Request #10090 · npm/cli · GitHub
Skip to content

fix(arborist): preserve explicit npm alias target identities - #10090

Open
myevolve wants to merge 3 commits into
npm:latestfrom
myevolve:fix/explicit-npm-alias-identity
Open

myevolve wants to merge 3 commits into
npm:latestfrom
myevolve:fix/explicit-npm-alias-identity

Conversation

@myevolve

@myevolve myevolve commented Oct 7, 2026 •

Copy link
Copy Markdown

Problem

An explicit npm alias can currently be satisfied by an unrelated package with the same installation name and version. On npm 12.2.0 / Node 24.21.0, a workspace declaring ms: npm:is-odd@2.0.0 resolves ordinary ms@2.0.0 hoisted for debug@2.6.9. Both install and npm ci succeed, but the workspace receives a duration formatter instead of its requested package.

Fix

Arborist unwrapped an alias into its version/tag sub-spec without checking the canonical package identity. Require child.package?.name === requested.subSpec.name in the alias branch before the existing recursive validation.

Ordinary range/version/tag remapping and non-registry file/git/remote behavior are unchanged. Related to #3154, but this deliberately does not change or claim to resolve the ordinary-edge remapping semantics discussed there.

Reproducer

In an empty temporary directory, with npm 12.2.0:

node <<'NODE'
const fs = require('node:fs');
const path = require('node:path');
const files = {
  "package.json": { "name": "npm-alias-collision-probe", "version": "1.0.0", "private": true, "workspaces": ["packages/example", "packages/other"], "dependencies": { "debug": "2.6.9" } },
  "packages/example/package.json": { "name": "collision-workspace", "version": "1.0.0", "dependencies": { "ms": "npm:is-number@1.0.0" } },
  "packages/other/package.json": { "name": "non-target-workspace", "version": "1.0.0", "dependencies": { "ms": "npm:is-odd@2.0.0" } }
};
for (const [file, contents] of Object.entries(files)) {
  fs.mkdirSync(path.dirname(file), { recursive: true });
  fs.writeFileSync(file, JSON.stringify(contents, null, 2) + '\n');
}
NODE
npm install --package-lock-only --ignore-scripts --no-audit --no-fund --registry=https://registry.npmjs.org
npm ci --ignore-scripts --no-audit --no-fund --registry=https://registry.npmjs.org
node <<'NODE'
const { createRequire } = require('node:module');
const path = require('node:path');
const req = createRequire(path.resolve('packages/other/package.json'));
const pkg = req('ms/package.json');
console.log(pkg.name, pkg.version, req('ms')(2), req('ms')(3));
NODE
  • Before: ms 2.0.0 2ms 3ms.
  • After: is-odd 2.0.0 false true.
  • Ordinary root ms@2.0.0 and the other workspace’s is-number@1.0.0 remain intact.

Verification

  • npm run test --workspace=@npmcli/arborist --ignore-scripts -- --no-coverage test/dep-valid.js: 142 assertions across 10 subtests, all passing. Targeted file only; the complete repository suite was not run.
  • ESLint passed for both changed JavaScript files.
  • Actual patched npm 12.2.0 fresh installs and lock-only → ci runs, followed by createRequire from each workspace and package-name/version/function assertions. The published validator was checked byte-for-byte against the checkout base before replacing that one source file.
  • Different explicit targets sharing a name/version, scoped installation names, scoped registry targets, missing identity, version/range/wildcard/tag specs, and unchanged ordinary/non-registry behavior are covered.
  • Manifest bytes and immediate ci lock bytes remained unchanged in the native controls. No overrides, constraint rewrites, or dependency additions to this repository.

Deliberate boundary

This is an explicit-alias validity fix, not canonical-target isolation across workspaces. In additional native probes, npm update ms --workspace=packages/example can update both workspaces’ aliases after the identity fix. That separate selection behavior is not presented as solved or recommended as a target-only updater workaround.

Implementation and review were AI-assisted; the stated test and runtime results were executed, not inferred.

Current-base verification

Synchronized with latest at b317f16c80df02ea3628cfa77170d5ae9b59720c without rewriting published history. Current head: 737460c3aea542b42464cd05421b5c615c38dc4e. The review diff now contains only the three intended Arborist files, with no release-bookkeeping changes.

  • Full Arborist suite on Linux/Node24.21.0: 59 files, 5,225 assertions passed, three platform-specific skips; 100% statements, branches, functions, and lines.
  • ESLint passed for the changed source and test.
  • Actual npm12.2.0 source CLI install/ci/module checks kept ordinary ms@2.0.0, alias is-number@2.0.0, and alias is-odd@2.0.0 distinct despite identical installation names and overlapping versions.
  • Composition with fix(arborist): scope named updates to selected workspaces #10091 passed again on current upstream: only the selected is-number alias advanced; ordinary root ms and the independent is-odd workspace stayed locked. Manifest and ci lockfile bytes were unchanged.

npm test --workspace=@npmcli/arborist --ignore-scripts -- --reporter=terse

Hosted npm workflows are action_required and need maintainer approval. Local verification does not substitute for that approval or claim the hosted matrix has run.

@myevolve

myevolve commented Oct 7, 2026

Copy link
Copy Markdown
Author

The independent named-workspace-update scoping defect is now addressed in #10091 (separate branch based on npm 12.2.0, no changes from this PR included).

Verified the final composition through the actual npm 12.2.0 CLI: npm update ms --workspace=packages/a --package-lock-only, then npm ci and real module calls. Root debug → ms@2.0.0 stayed unchanged; selected ms → npm:is-number advanced 1.0.0 → 7.0.0; unselected ms → npm:is-odd@2.0.0 stayed unchanged. All package identities matched, manifests were unchanged, and ci preserved lockfile bytes.

These are complementary fixes: this PR protects alias identity; #10091 scopes eager named updates, including ordinary non-alias packages. Neither claims to freeze necessary shared-dependency/peer repairs or redesign bare update-all.

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.

1 participant