Cli: [BUG] npm 7 makes incorrect suggestion when encountering peer deps issues

Created on 5 Nov 2020  路  7Comments  路  Source: npm/cli

Current Behavior:

When npm install fails due to peer dep incompatibilities, something like this is printed:

npm ERR! Found: [email protected]
npm ERR! node_modules/webpack
npm ERR!   dev webpack@"^5.4.0" from the root project
npm ERR!   peer webpack@">=4.43.0 <6.0.0" from @pmmmwh/[email protected]
npm ERR!   node_modules/@pmmmwh/react-refresh-webpack-plugin
npm ERR!     dev @pmmmwh/react-refresh-webpack-plugin@"^0.4.3" from the root project
npm ERR!   7 more (babel-loader, file-loader, html-webpack-plugin, ...)
npm ERR! 
npm ERR! Could not resolve dependency:
npm ERR! peer webpack@"^4.0.0" from [email protected]
npm ERR! node_modules/webpack-dev-server/node_modules/webpack-dev-middleware
npm ERR!   webpack-dev-middleware@"^3.7.2" from [email protected]
npm ERR!   node_modules/webpack-dev-server
npm ERR!     dev webpack-dev-server@"^3.11.0" from the root project
npm ERR!     1 more (@pmmmwh/react-refresh-webpack-plugin)
npm ERR! 
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.

The suggestion to "retry this command with --force" doesn't appear to work. npm install --force prints exactly the same output, with only the addition of:

npm WARN using --force Recommended protections disabled.

Expected Behavior:

I would expect the suggested command to work, or I would expect it not to be suggested.

Steps To Reproduce:

  1. Run npm install webpack@5 webpack-dev-server@3.
  2. Run npm install --force webpack@5 webpack-dev-server@3, as suggested by the output of the first command.
  3. The second command fails with the same error, and the same suggestion to retry with --force.

Environment:

  • OS: macOS 10.15.7
  • Node: 14.14.0
  • npm: 7.0.8
Bug Needs Triage Release 7.x

All 7 comments

Reverting back to npm 6.14.8 solved this for me for now using npm install -g npm@latest

This isn鈥檛 a bug; it鈥檚 a correct warning because your dependency graph is invalid.

you have webpack 5, but v3.7.2 of webpack-dev-middleware requires webpack 4. Your choices are to downgrade to webpack 4, upgrade to a version (of available) of webpack-dev-middleware that supports webpack 5, or remove webpack-dev-middleware entirely.

The message telling you to use 鈥攆orce is misleading, yes, because this isn鈥檛 programmatically fixable.

This isn鈥檛 a bug; it鈥檚 a correct warning because your dependency graph is invalid.

@ljharb I think you misread the issue. I'm not claiming that either the failed installation or the warning message is a bug. See https://github.com/npm/cli/issues/2119#issuecomment-722086100, from which I borrowed to use as an example of an install that contains a peer dependency incompatibility.

This issue is entirely about the --force suggestion that npm makes, which as you said is somewhere between misleading and incorrect. The error states: "retry this command with --force, or --legacy-peer-deps to accept an incorrect (and potentially broken) dependency resolution." There's a subject ambiguity here about whether --force will accept the incorrect resolution or whether that only applies to --legacy-peer-deps. But either way that you read this, it suggests that rerunning the command with --force will produce a different output. This isn't true.

It's not exactly clear to me what the --force modifier actually does on install, but IMO it's not unreasonable to guess that it might mean "ignore peer dependency incompatibilities wrt exit behavior", mirroring the npm@6 behavior. (I know that you hate the thought of this. 馃檪) If that's the intention, then there's a bug in the implementation. Or, more likely, this is _not_ the intention in which case it seems that npm shouldn't be making this suggestion at all.

I don鈥檛 think any option should suppress the warnings, but i agree that if the suggestion won鈥檛 work, it shouldn鈥檛 be phrased as if it will :-)

Well, yeah, this is a bug. It should either not tell you to use --force or (ideally) --force should provide an override that makes it produce a mostly (but not entirely) correct package tree.

Rather than roll back to npm v6, I'd suggest using --legacy-peer-deps or putting legacy-peer-deps = true in your project-level .npmrc file. There are a lot of other advantages to using v7.

Here's the minimal reproduction package.json file that I'm using:

{
  "devDependencies": {
    "webpack-dev-server": "^3.11.0",
    "@pmmmwh/react-refresh-webpack-plugin": "^0.4.3"
  }
}

IMO it's not unreasonable to guess that it might mean "ignore peer dependency incompatibilities wrt exit behavior", mirroring the npm@6 behavior.

The npm@6 behavior is "ignore peerDependencies entirely". That's what --legacy-peer-deps does.

--force is either slightly better or much worse in this sort of case. It attempts to infer a reasonable best guess at what version _should_ be placed there, based on prioritizing non-peer dependencies, and failing that, accepting invalid peer deps and moving on.

You've found a case where we don't do the right thing (or at least, the _intended_ thing, fraught though it may be) in the --force case.

That bit where it says "recommended protections disabled", this is one of those "recommended protections". npm v7 tries to not ever give you a tree that violates the stated dependency contracts of any packages.

--force is either slightly better or much worse in this sort of case. It attempts to infer a reasonable best guess at what version should be placed there, based on prioritizing non-peer dependencies, and failing that, accepting invalid peer deps and moving on.

Ah, that's what I had originally expected it to do! I like this behavior, because --legacy-peer-deps doesn't work if you've come to rely on npm@7's automatic installation of peer deps for other peer deps, unrelated to the one(s) that are incompatible.

For example, if you have something like this, where @babel/cli peer-depends on @babel/core:

{
  "scripts": {
    "build": "babel index.js"
  },
  "devDependencies": {
    "@babel/cli": "^7.12.0",
    "webpack-dev-server": "^3.11.0",
    "webpack": "^5.0.0"
  }
}

Using --legacy-peer-deps would ignore the webpack incompatibility, but it would fail to install @babel/core, so npm run build would fail. Whereas, if I understand the intention of --force correctly, it would install @babel/core while also "ignoring" the webpack incompatibility.

(I understand that this is all danger zone territory, but it does help to understand the intention of --force in the rare case where it's needed. For example: when you're waiting on a third-party package to update a peer dep and you want to test whether it happens to work locally in the meantime. It's nice to have a method to force npm to complete the install rather than bailing from populating node_modules entirely.)

Was this page helpful?
0 / 5 - 0 ratings