Is there an existing issue for this?
This issue exists in the latest npm version
This is not just a request to bump a dependency for a CVE
Current Behavior
When a direct dependency and one of its transitive dependencies expose the same
bin name, npm can link the transitive dependency's executable into
node_modules/.bin. The winner appears to be determined by the lexical order
of the installed package paths rather than dependency depth.
This is the npm equivalent of yarnpkg/berry#7215, but Yarn's winner depends on
dependency-tree order while npm's depends on lexical package name:
yarnpkg/berry#7215
This affects the documented setup for running TypeScript 6 and TypeScript 7
side by side. In the reproduction below:
@typescript/z-native is a direct alias for TypeScript 7 and exposes tsc.
typescript is a direct alias for @typescript/typescript6.
@typescript/typescript6 transitively installs @typescript/old, which also
exposes tsc.
npm links node_modules/.bin/tsc to the transitive @typescript/old package,
so invoking tsc unexpectedly runs TypeScript 6.
Actual Behavior
node_modules/.bin/tsc points to node_modules/@typescript/old/bin/tsc, and
invoking it reports TypeScript 6.0.3.
Renaming only the direct alias from @typescript/z-native to
@typescript/a-native, then reinstalling, makes the same bin point to the
direct TypeScript 7 package. This suggests that lexical package-name ordering,
rather than directness, decides the collision.
Expected Behavior
For a bin-name collision, an executable from a direct dependency should take
precedence over one from a transitive dependency. npm exec tsc -- --version
should therefore run TypeScript 7.0.2.
Package alias names should not change which dependency supplies the executable.
Steps To Reproduce
Create this package.json in an empty directory:
{
"name": "npm-alias-bin-repro",
"version": "1.0.0",
"private": true,
"devDependencies": {
"@typescript/z-native": "npm:typescript@~7.0.2",
"typescript": "npm:@typescript/typescript6@~6.0.2"
}
}
Then run:
npm install
npm exec tsc -- --version
Actual output:
On Unix, the selected target can also be seen with:
readlink -f node_modules/.bin/tsc
It resolves to:
<repro>/node_modules/@typescript/old/bin/tsc
As a control, change @typescript/z-native to @typescript/a-native, remove
node_modules and package-lock.json, and run npm install again. Now:
npm exec tsc -- --version
reports:
even though the dependency graph is otherwise equivalent.
Environment
Reproduced with:
Node.js: v22.18.0
npm: 11.19.0
OS: Windows
Is there an existing issue for this?
This issue exists in the latest npm version
This is not just a request to bump a dependency for a CVE
Current Behavior
When a direct dependency and one of its transitive dependencies expose the same
bin name, npm can link the transitive dependency's executable into
node_modules/.bin. The winner appears to be determined by the lexical orderof the installed package paths rather than dependency depth.
This is the npm equivalent of yarnpkg/berry#7215, but Yarn's winner depends on
dependency-tree order while npm's depends on lexical package name:
yarnpkg/berry#7215
This affects the documented setup for running TypeScript 6 and TypeScript 7
side by side. In the reproduction below:
@typescript/z-nativeis a direct alias for TypeScript 7 and exposestsc.typescriptis a direct alias for@typescript/typescript6.@typescript/typescript6transitively installs@typescript/old, which alsoexposes
tsc.npm links
node_modules/.bin/tscto the transitive@typescript/oldpackage,so invoking
tscunexpectedly runs TypeScript 6.Actual Behavior
node_modules/.bin/tscpoints tonode_modules/@typescript/old/bin/tsc, andinvoking it reports TypeScript 6.0.3.
Renaming only the direct alias from
@typescript/z-nativeto@typescript/a-native, then reinstalling, makes the same bin point to thedirect TypeScript 7 package. This suggests that lexical package-name ordering,
rather than directness, decides the collision.
Expected Behavior
For a bin-name collision, an executable from a direct dependency should take
precedence over one from a transitive dependency.
npm exec tsc -- --versionshould therefore run TypeScript 7.0.2.
Package alias names should not change which dependency supplies the executable.
Steps To Reproduce
Create this
package.jsonin an empty directory:{ "name": "npm-alias-bin-repro", "version": "1.0.0", "private": true, "devDependencies": { "@typescript/z-native": "npm:typescript@~7.0.2", "typescript": "npm:@typescript/typescript6@~6.0.2" } }Then run:
npm install npm exec tsc -- --versionActual output:
On Unix, the selected target can also be seen with:
It resolves to:
As a control, change
@typescript/z-nativeto@typescript/a-native, removenode_modulesandpackage-lock.json, and runnpm installagain. Now:npm exec tsc -- --versionreports:
even though the dependency graph is otherwise equivalent.
Environment
Reproduced with: