npm Transitive OS-Specific Shrinkwrap Dependency Error
This repository demonstrates a bug in the npm ci
command which
produces an error when running the command in a package that publishes a shrinkwrap,
and that has a transitive, optional
dependency with an OS constraint.
lib1.shrinkwrap
- This is a package published with a shrinkwrap file, with a transitive, OS-specific dependency. The dependency, in this case, ismocha
, which depends onchokidar
, which optionally depends onfsevents
, which has a constraint to thedarwin
(macOS) platform.lib1.lock
- This is essentially the the same package as above, but using apackage-lock.json
, published without a shrinkwrap file.app1
- This is a root package that depends onlib1.shrinkwrap
.app2
- This is a root package that depends onlib1.lock
.
The bug was reproduced using npm 10.8.1, the latest at the time of this writing.
If you're on a Linux or Windows platform, simply descend into the app1
directory and run npm ci
. This should
produce an error like
app1 % npm ci
npm error code EBADPLATFORM
npm error notsup Unsupported platform for [email protected]: wanted {"os":"darwin"} (current: {"os":"linux"})
npm error notsup Valid os: darwin
npm error notsup Actual os: linux
npm error A complete log of this run can be found in: /root/.npm/_logs/2024-07-02T13_12_08_752Z-debug-0.log
If you're on macOS, you can use a node docker image something like the following, from the root of this repository.
docker run -it --entrypoint /bin/bash -v $(pwd):/shrinkwrap_optional_dep node:20-slim
cd /shrinkwrap_optional_dep/app1
npm ci
Note that the path to the OS-constrained dependency, fsevents
, also runs through a devDependencies
dependency, so
npm should not be attempting to install fsevents
due to that condition, let alone the OS constraint and optional
dependency status.
Observe that the app1
package-lock contains an entry for the transitive, optional
fsevents
dependency. However, that entry does not contain the "optional": true
key-value, as does the
lib.shrinkwrap
shrinkwrap file.
"node_modules/@restjohn/issues.transitive-optional-dep.lib1.shrinkwrap/node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"extraneous": true,
"hasInstallScript": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
If you manually add "optional": true
to the above block in the app1
package-lock, npm ci
for the app1
package
on a non-Darwin platform succeeds.
Running npm ci
in the app2
package produces no error, because app2
depends on the published lib1.lock
package, which does not use npm-shrinkwrap.json
as lib1.shrinkwrap
does.
Running npm install
in the app1
package on a non-Darwin platform produces no error.
Installing lib1.shrinkwrap
from a package tarball produces no error. You can test this from your macOS platform
easiest by using the Docker method described above. From the root repository directory in a macOS shell, execute the
following commands.
npm pack ./lib1.shrinkwrap
cd app1
npm i ../restjohn-issues.transitive-optional-dep.lib1.shrinkwrap-1.0.0.tgz
cd ..
Then, use a Docker container to test npm ci
.
docker run -it --entrypoint /bin/bash -v $(pwd):/working node:20-slim
cd /working/app1
npm ci
Installing lib1.shrinkwrap
in app1
as a remote tarball produces no error from npm ci
. You can test this easily
by using the http-server
package. From the repository root directory in
your macOS shell, execute the following commands.
npm pack ./lib1.shrinkwrap
npm i -g http-server
http-server -a 127.0.0.1 .
http-server
is now running in the foreground in that shell. Open another shell window and execute the following
commands from the root repository directory.
cd app1
npm i http://127.0.0.1:8080/restjohn-issues.transitive-optional-dep.lib1.shrinkwrap-1.0.0.tgz
cd ..
docker run -it --entrypoint /bin/bash -v $(pwd):/working node:20-slim
cd /working/app1
npm ci