Why does "npm install" rewrite package-lock.json?

Viewed 365989

I just recently upgraded to npm@5. I now have a package-lock.json file with everything from package.json. I would expect that, when I run npm install that the dependency versions would be pulled from the lock file to determine what should be installed in my node_modules directory. What's strange is that it actually ends up modifying and rewriting my package-lock.json file.

For example, the lock file had typescript specified to be at version 2.1.6. Then, after the npm install command, the version was changed to 2.4.1. That seems to defeat the whole purpose of a lock file.

What am I missing? How do I get npm to actually respect my lock file?

12 Answers

Short Answer:

  • npm install honors package-lock.json only if it satisfies the requirements of package.json.
  • If it doesn't satisfy those requirements, packages are updated & package-lock is overwritten.
  • If you want the install to fail instead of overwriting package-lock when this happens, use npm ci.

Here is a scenario that might explain things (Verified with NPM 6.3.0)

You declare a dependency in package.json like:

"depA": "^1.0.0"

Then you do, npm install which will generate a package-lock.json with:

"depA": "1.0.0"

Few days later, a newer minor version of "depA" is released, say "1.1.0", then the following holds true:

npm ci       # respects only package-lock.json and installs 1.0.0

npm install  # also, respects the package-lock version and keeps 1.0.0 installed 
             # (i.e. when package-lock.json exists, it overrules package.json)

Next, you manually update your package.json to:

"depA": "^1.1.0"

Then rerun:

npm ci      # will try to honor package-lock which says 1.0.0
            # but that does not satisfy package.json requirement of "^1.1.0" 
            # so it would throw an error 

npm install # installs "1.1.0" (as required by the updated package.json)
            # also rewrites package-lock.json version to "1.1.0"
            # (i.e. when package.json is modified, it overrules the package-lock.json)

Use the newly introduced

npm ci

npm ci promises the most benefit to large teams. Giving developers the ability to “sign off” on a package lock promotes more efficient collaboration across large teams, and the ability to install exactly what is in a lockfile has the potential to save tens if not hundreds of developer hours a month, freeing teams up to spend more time building and shipping amazing things.

Introducing npm ci for faster, more reliable builds

Use the npm ci command instead of npm install.

"ci" stands for "clean install".

It will install the project dependencies based on the package-lock.json file instead of the lenient package.json file dependencies.

It will produce identical builds to your team mates and it is also much faster.

You can read more about it in this blog post: https://blog.npmjs.org/post/171556855892/introducing-npm-ci-for-faster-more-reliable

In the future, you will be able to use a --from-lock-file (or similar) flag to install only from the package-lock.json without modifying it.

This will be useful for CI, etc. environments where reproducible builds are important.

See https://github.com/npm/npm/issues/18286 for tracking of the feature.

Probably you should use something like this

npm ci

Instead of using npm install if you don't want to change the version of your package.

According to the official documentation, both npm install and npm ci install the dependencies which are needed for the project.

The main difference is, npm install does install the packages taking packge.json as a reference. Where in the case of npm ci, it does install the packages taking package-lock.json as a reference, making sure every time the exact package is installed.

EDIT: the name "lock" is a tricky one, its NPM trying to catch up with Yarn. It isn't a locked file whatsoever. package.json is a user-fixed file, that once "installed" will generate node_modules folder tree and that tree will then be written in package-lock.json. So you see, its the other way around - dependency versions will be pulled from package.json as always, and package-lock.json should be called package-tree.json

(hope this made my answer clearer, after so many downvotes)


A simplistic answer: package.json have your dependencies as usual, while package-lock.json is "an exact, and more importantly reproducible node_modules tree" (taken from npm docs itself).

As for the tricky name, its NPM trying to catch up with Yarn.

Npm install detects any changes made to package.json file to reflect the dependency list accordingly.

Ex. If user added or removed a new dependency, the build will download or remove the dependencies in the local computer. We can compare this to .m2 repository in java where maven keeps track of pom.xml file constantly to update the dependencies.

package-lock.json is a replica of package.json used at run-time by internal processes, only difference is package-lock.json is read-only to user.

Related