Steps to reproduce
Create a monorepo-style project with the following structure:
/package.json
{
"name": "monorepo",
"dependencies": {
"app": "file:./app"
}
}
/app/package.json
{
"name": "app",
"dependencies": {
"lib": "file:../lib"
}
}
/lib/package.json
There's a root package that depends on ./app which in turn depends on ../lib.
Now run npm install. That will produce a node_modules folder with two symlinks:
app -> ../app
lib -> ../lib
and a lockfile with this content:
{
"name": "monorepo",
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"app": {
"version": "file:app",
"requires": {
"lib": "file:lib"
}
},
"lib": {
"version": "file:lib"
}
}
}
Notice an important fact: the dependencies.app.requires.lib field, with value file:lib, contains a path relative to the root of the tree. However, in package.json, the same dependency is specified relative to the requesting package's directory, i.e., file:../lib. We'll later see that this difference between package-lock.json and package.json formats is not correctly handled.
Now, with the lockfile present, delete the node_modules directory, and run npm install again. This time, it runs with no node_modules (i.e., empty currentTree), and installs from lockfile (unlike the first run)
Expected result:
The node_modules folder is the same as in the previous run, i.e., contains two symlinks to ../app and ../lib.
Actual result:
The node_modules folder contains only the app -> ../app symlink. The lib -> ../lib one is not present at all. npm install incorrectly determined that it's extraneous (not required by anyone) and prunes it (in pruneIdealTree)
See also:
To see a real-world example in a widely used project, just check out the lerna repo and run npm install there. Then, running npm ls reports a lot of missing package links:
npm ERR! missing: @lerna/cli@file:../../core/cli, required by @lerna-test/command-runner@0.0.0-test-only
npm ERR! missing: @lerna/package@file:../../core/package, required by @lerna-test/pkg-matchers@0.0.0-test-only
npm ERR! missing: @lerna/get-packed@file:../get-packed, required by @lerna/pack-directory@3.10.5
[...]
Steps to reproduce
Create a monorepo-style project with the following structure:
/package.json
/app/package.json
/lib/package.json
There's a root package that depends on
./appwhich in turn depends on../lib.Now run
npm install. That will produce anode_modulesfolder with two symlinks:and a lockfile with this content:
Notice an important fact: the
dependencies.app.requires.libfield, with valuefile:lib, contains a path relative to the root of the tree. However, inpackage.json, the same dependency is specified relative to the requesting package's directory, i.e.,file:../lib. We'll later see that this difference betweenpackage-lock.jsonandpackage.jsonformats is not correctly handled.Now, with the lockfile present, delete the
node_modulesdirectory, and runnpm installagain. This time, it runs with nonode_modules(i.e., emptycurrentTree), and installs from lockfile (unlike the first run)Expected result:
The
node_modulesfolder is the same as in the previous run, i.e., contains two symlinks to../appand../lib.Actual result:
The
node_modulesfolder contains only theapp -> ../appsymlink. Thelib -> ../libone is not present at all.npm installincorrectly determined that it's extraneous (not required by anyone) and prunes it (inpruneIdealTree)See also:
To see a real-world example in a widely used project, just check out the
lernarepo and runnpm installthere. Then, runningnpm lsreports a lot of missing package links: