Skip to content

[BUG] npm install incorrectly prunes unhoisted transitive optional dependencies when using workspaces #8536

@jenseng

Description

@jenseng

Is there an existing issue for this?

  • I have searched the existing issues

This issue exists in the latest npm version

  • I am using the latest npm

Current Behavior

If you:

  • have multiple versions of transitive optional dependencies in a workspace (e.g. vite -> esbuild -> @esbuild/darwin-arm64, and vitest -> vite -> esbuild -> @esbuild/darwin-arm64)
  • have dependency in the root package that also has those transitive optional dependencies via a peer dependency

Then:

  • running npm install will prune the unhoisted versions, and possibly fail. esbuild in particular uses a postinstall script to do introspection of its expected optional dependencies, and it will blow up if they are missing/wrong

Expected Behavior

Running npm install should not prune transitive optional dependencies. This works correctly on npm 11.4.2 and below.

Steps To Reproduce

  1. Clone https://github.com/jenseng/npm-install-loses-transitive-peer-deps
  2. Run npm install
  3. See an error like:
npm error code 1
npm error path /path/to/npm-install-loses-transitive-peer-deps/node_modules/vitest/node_modules/esbuild
npm error command failed
npm error command sh -c node install.js
npm error /path/to/npm-install-loses-transitive-peer-deps/node_modules/vitest/node_modules/esbuild/install.js:133
npm error     throw new Error(`Expected ${JSON.stringify(versionFromPackageJSON)} but got ${JSON.stringify(stdout)}`);
npm error           ^
npm error
npm error Error: Expected "0.21.5" but got "0.18.20"
npm error     at validateBinaryVersion (/path/to/npm-install-loses-transitive-peer-deps/node_modules/vitest/node_modules/esbuild/install.js:133:11)
npm error     at /path/to/npm-install-loses-transitive-peer-deps/node_modules/vitest/node_modules/esbuild/install.js:283:5
npm error
npm error Node.js v22.18.0

This is because esbuild's postinstall script does some sanity checks to ensure its platform-specific optional dependency is there. Because of the bug, 0.21.5 is not installed, so it finds the hoisted version and throws the error.

Alternative repro:

  1. Run npm install --ignore-scripts
  2. While it will "succeed", note that package-lock.json has now lost the unhoisted versions of all those optional dependencies, and they are not installed

Additional notes:

I tried to distill this down to a simpler repro, perhaps it could be taken further. There do seem to be at least a few conditions to trigger the bug:

  • You have to be using workspaces
  • The workspaces need to have two different versions of some transitive optional dependencies
  • The root package needs a dependency, that has a peer dependency, that also has the transitive optional dependencies (unclear if the version matters)

I've confirmed the bug is present in npm 11.5.0 onward, and is not present in npm 11.4.2 or earlier. So perhaps this PR is the culprit.

In my repro repo, the root package dependency uses a file URL to point at a tarball in the same repo, but the bug also happens for packages fetched from the registry (that's where I first encountered it)

Environment

  • npm: 11.5.2
  • Node.js: 22.18.0
  • OS Name: Mac OS Sequoia 15.6
  • System Model Name: Macbook Pro
  • npm config:
; copy and paste output from `npm config ls` here

Metadata

Metadata

Assignees

No one assigned

    Labels

    Bugthing that needs fixingNeeds Triageneeds review for next steps

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions