npm prune --production (after npm ci only) erroneously uninstalls regular dependency


(Jacob Page) #1

What I Wanted to Do

Produce a production-only build with only dev dependencies pruned.

What Happened Instead

One of the dependencies is erroneously removed despite it not being a dev dependency.

Reproduction Steps

Our CICD pipeline runs the following commands:

npm ci
npm prune --production

For some reason, one of the packages which is not flagged as a dev dependency in package-lock.json is erroneously removed. It’s not a devDependency in package.json either, and nothing has it as a dependency other than the root package.json. Strangely, if you do this sequence of events:

npm install
npm prune --production

…the package is not erroneously removed. This leads me to speculate that the version of the package.json for the dependency has an impact on things, since as far as I can tell that’s the only file difference produced between npm install and npm ci.


The entry in package-lock.json looks like this:

"@gasket/mocha-plugin": {
  "version": "1.1.1",
  "resolved": "<url elided>/@gasket/mocha-plugin/-/@gasket/mocha-plugin-1.1.1.tgz",
  "integrity": "sha1-p4xEaJEKxREiBT5wp+UZEMkRjRc="

…with a package.json entry like:

"dependencies": {
  "@gasket/mocha-plugin": "latest"

Platform Info

$ npm --versions

    { 'seechange-pwa': '0.0.0',
      npm: '6.7.0',
      ares: '1.15.0',
      cldr: '33.1',
      http_parser: '2.8.0',
      icu: '62.1',
      modules: '64',
      napi: '3',
      nghttp2: '1.34.0',
      node: '10.15.0',
      openssl: '1.1.0j',
      tz: '2018e',
      unicode: '11.0',
      uv: '1.23.2',
      v8: '',
      zlib: '1.2.11' }

$ node -p process.platform


(Jacob Page) #2

Also strange; a very similar package named @gasket/lint-plugin which is also a regular dependency does not get installed with npm prune --production. The _ contents tacked on in the dependency’s package.json look the same, so maybe it’s something more than just package.json breaking things :confused:

Is there something caused by the name of the package itself, maybe? I do know that it’s npm prune that’s removing the file because doing npm prune --production --dry-run --json shows that it intends to remove the package. This is the only one that’s being erroneously removed.

What criteria are used when selecting packages for removal? Anything undocumented that could be accounting for this?

(Jacob Page) #3

Another clue. Doing an npm ls after an npm ci shows:

npm ERR! invalid: @gasket/mocha-plugin@1.1.1 /Users/jpage/Code/seechange-pwa/node_modules/@gasket/mocha-plugin

Is there any way to get details on why a package is flagged as invalid?

(Kat Marchán) #4

Because you’re using latest. Try putting * in there instead.

(Jacob Page) #5

OK, I think I found the difference. If package.json has:

"dependencies": {
  "@gasket/mocha-plugin": "latest"

…then the package is flagged as invalid and gets removed. If it contains a version instead:

"dependencies": {
  "@gasket/mocha-plugin": "^1.1.1"

…then it is not. It appears that the node_modules/@gasket/mocha-plugin/package.json is generated differently when doing an npm ci:

"_from": "@gasket/mocha-plugin@1.1.1"

…versus npm i:

"_from": "@gasket/mocha-plugin@latest"

The mismatch between _from and the root package.json seems to be the cause for the package being flagged as invalid and its removal.

I’m guessing this should count as a bug in npm ci.

(Kat Marchán) #6

yup. Might be pretty straightforward to fix, too.

(Brandon Slinkard) #7

I actually just ran into this issue as well when using dev tags in my dependencies. It seems like this may be an issue with npm ci, npm prune --production and tags. My team is currently developing several libraries and services with node and the result is sometimes we are using: "somePackage": "dev" to keep up to date with the frequent changes until we can get to a point where we have a stable release.


// package.json - "somePackage": "dev"
npm i
npm prune --production


// package.json - "somePackage": "dev"
npm ci


// package.json - "somePackage": "0.1.1-dev.0"
npm ci
npm prune --production

Not working:

// package.json - "somePackage": "dev"
npm ci
npm prune --production

(Lars Willighagen) #8

I do not know if this is the right way to go (that condition must have been there for a reason), and if it is it should probably include pinned (version) deps: