Impossible to update single package without updating its dependencies

cli

(Felix Becker) #1

What I Wanted to Do

I wanted to update one of our private packages (lets call it pkg-a) to a new patch version to include a bugfix we just released. The new patch version had no new dependency version requirements. I expected it to bump the version of only that package in the lockfile.

Tree before:

└┬ pkg-a@^1.0.0 locked at 1.0.0 (latest: 1.0.1)
 └─ pkg-b@^1.0.0 locked at 1.0.0 (latest: 1.1.0)

Expected tree afterwards:

└┬ pkg-a@^1.0.0 locked at 1.0.1 (latest: 1.0.1)
 └─ pkg-b@^1.0.0 locked at 1.0.0 (latest: 1.0.1)

What Happened Instead

It did not just update that package, but also all its dependencies, including sanitize-html. The update of pkg was safe, the change was very small. However, the update to sanitize-html actually had a critical bug in it. The update was not even noticed in code review since package-lock files are hidden in GitHub diffs by default.

Actual tree afterwards:

└┬ pkg-a@^1.0.0 locked at 1.0.1 (latest: 1.0.1)
 └─ pkg-b@^1.0.0 locked at 1.0.1 (latest: 1.0.1)

Reproduction Steps

I have setup a setup as described above in a GitHub repo https://github.com/felixfbecker/npm-shallow-update-repro

You can also generate one yourself like this:

npm init
npm install sanitize-html@1.18.2 # just to lock the old version
npm install metascraper@3.11.7   # not the latest, so we can update it
npm rm sanitize-html             # make sanitize-html an *indirect* dependency

Now your tree looks like this:

> npm ls sanitize-html
npm-shallow-update-repro@1.0.0 /Users/felix/src/github.com/felixfbecker/npm-shallow-update-repro
└─┬ metascraper@3.11.7
  └── sanitize-html@1.18.2

Now run npm update metascraper. The tree changed to:

> npm ls sanitize-html
npm-shallow-update-repro@1.0.0 /Users/felix/src/github.com/felixfbecker/npm-shallow-update-repro
└─┬ metascraper@3.11.8
  └── sanitize-html@1.18.4

Even though this would have been expected:

> npm ls sanitize-html
npm-shallow-update-repro@1.0.0 /Users/felix/src/github.com/felixfbecker/npm-shallow-update-repro
└─┬ metascraper@3.11.8
  └── sanitize-html@1.18.2

because 1.18.2 is still in-range of ~1.18.2, which is the unchanged requirement: https://github.com/microlinkhq/metascraper/blob/v3.11.8/packages/metascraper/package.json#L75

If you git reset --hard HEAD && npm install, then try with npm update sanitize-html --depth 1 or --depth 0 the same happens.

Details

I tried a bunch of variations of npm update to achieve a “safe” update without indirect dependencies, passing in --depth 0 or --depth 1. But even with that flag, it would still update the indirect dependency.

I personally think that a “shallow” update should be the default for npm update (except when the new version has updated requirements of course, then update only the packages with new requirements), while a “reinstall” ala npm install pkg-a@latest would update everything. Otherwise, what’s the point of having two commands?

But I would also be okay if this behaviour could simply be controlled with --depth. In some contexts a “safe” update is more important than in others. In this one, we certainly always want to do shallow updates of pkg-a, because we release new versions every day, but don’t want to update the indirect dependencies every day. Other dependencies we update once per month, where it’s fine if it updates the indirect dependencies too (and I could just use a “reinstall” for that).

Especially automatic dependency updates through Renovate I would always expect to be shallow, because every package update is supposed to be tested in isolation on a branch.

I would expect npm to provide some way to do a shallow update.

Platform Info

$ npm --versions
{ npm: '6.3.0',
  ares: '1.14.0',
  cldr: '33.0',
  http_parser: '2.8.0',
  icu: '61.1',
  modules: '64',
  napi: '3',
  nghttp2: '1.32.0',
  node: '10.6.0',
  openssl: '1.1.0h',
  tz: '2018c',
  unicode: '10.0',
  uv: '1.21.0',
  v8: '6.7.288.46-node.13',
  zlib: '1.2.11' }
$ node -p process.platform
darwin

(Kat Marchán) #2

I’m moving this to #ideas because it’s a feature request in disguise. update has never been intended to do this. There are definitely ways to do targeted updates right now (and I’m pretty sure npm audit fix does this!), but they’re not exposed in a straightforward way right now.

This is, though, definitely an interesting behavior change and has overlap with other plans we have for update. Would you be interested in writing up an RFC with desired ux and behavior so we can get the conversation and design going? We review RFCs weekly and they’re a very important part of how we consider big changes to the CLI. :sparkles:


(Felix Becker) #3

Interesting, how is it exposed currently? I know Renovate is looking for any workaround that would allow this.

I would write an RFC, once I’m back from vacation. Is changing the default behavour a possibility? Or making —depth do what I thought it may do?


(Kat Marchán) #4

You have to feed the npm installer a modified tree and trick it a bit, basically. There’s some approaches that involve editing the lockfile but that’s not always gonna work very well.


(Nick Snyder) #5

I was bitten by the real-world scenario that Felix is reporting, and it was painful.

I found myself wishing that npm had Minimal Version Selection.


(Nick Snyder) #6

Here is more detailed account of the real world pain this causes:


(Felix Becker) #7

@zkat I wrote up an RFC: npm/rfcs/pull/21


(system) #8

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.