Update 3: As other answers point out as well, the
npm ci command got introduced in npm 5.7.0 as additional way to achieve fast and reproducible builds in the CI context. See the documentation and npm blog for further information.
Update 2: The issue to update and clarify the documentation is GitHub issue #18103.
Update 1: The behaviour that was described below got fixed in npm 5.4.2: the currently intended behaviour is outlined in GitHub issue #17979.
Original answer: The behaviour of
package-lock.json was changed in npm 5.1.0 as discussed in issue #16866. The behaviour that you observe is apparently intended by npm as of version 5.1.0.
That means that
package.json can override
package-lock.json whenever a newer version is found for a dependency in
package.json. If you want to pin your dependencies effectively, you now must specify the versions without a prefix, e.g., you need to write them as
1.2.0 instead of
^1.2.0. Then the combination of
package-lock.json will yield reproducible builds. To be clear:
package-lock.json alone no longer locks the root level dependencies!
Whether this design decision was good or not is arguable, there is an ongoing discussion resulting from this confusion on GitHub in issue #17979. (In my eyes it is a questionable decision; at least the name
lock doesn’t hold true any longer.)
One more side note: there is also a restriction for registries that don’t support immutable packages, such as when you pull packages directly from GitHub instead of npmjs.org. See this documentation of package locks for further explanation.