DEV Community

Henry Williams
Henry Williams

Posted on

Don't trust SemVersioning in NPM Modules

Problem

{
    "dependencies": {
        "some_module": "^0.3.8",
        "some_other_module": "~0.1.3",
        "dont_do_this": "*"
    }
}
Enter fullscreen mode Exit fullscreen mode

Do any of those patterns look familiar to you? Yes? Then I sure hope you know whether all of your installed packages follow semantic versioning (semver)! Otherwise these could be ticking time bombs waiting to blow up in your future deployment.

Some packages follow semver better than others, but, from my experience, the ones you should be most cautious with are the sub-1.0 packages because they definitely don't follow semver; they typically use the second digit (0.x) as the major version and the third digit (0.1.x) as the patch or feature version! So the next time you run your build for a package in version 0.x you might be in for a nasty surprise.

For a more concrete example, let's use knex, which is currently in version 0.16.3. Now, if you specify in your package.json to install version 0.x, then this means that the next time you run a deployment you could be installing version 0.17.0, which might contain breaking changes that impact your app!

Solution

At this point, you might be wondering how you could solve this problem. Luckily for us, the solution is pretty simple: lock down all of the package versions! Your package.json should look more like the one below to avoid any surprises.

{
    "dependencies": {
        "some_module": "0.3.8",
        "some_other_module": "0.1.3",
    }
}
Enter fullscreen mode Exit fullscreen mode

Of course, you can also relax the rules for any packages that you know follow semver. For instance, you're probably safe to trust packages like react (v16.8.2) and restify (v7.7.0) since they're both pretty popular projects with many major releases.

Edit

As mentioned in the discussion, this issue can also be mitigated by keeping your package-lock.json up to date. This works because once a version is specified in the package-lock.json, it won't be updated if it meets the required version specified in package.json. For instance, 0.24.0 fulfills the requirement for 0.x, so even if 0.25.0 is released, npm will continue to install and use 0.24.0.

References

https://docs.npmjs.com/about-semantic-versioning
https://docs.npmjs.com/files/package-locks

Discussion (20)

Collapse
turnerj profile image
James Turner • Edited on

Yep, what you've stated is #4 of the official specification for Semantic Versioning:

4. Major version zero (0.y.z) is for initial development. Anything may change at any time. The public API should not be considered stable.

This issue isn't exclusive to NPM either, packages via Composer or Nuget are the same if they follow the specification.

Collapse
henryjw profile image
Henry Williams Author

Thanks for pointing this out. I wasn't aware that the official spec allows for a major version to be 0 for initial development. Although, I have seen many major packages (knex, soap, axios) remain in 'initial development' phases for years. So I think this could still cause problems for unsuspecting developers.

Collapse
ptejada profile image
Pablo Tejada

So is not really and issue but a feature. Great!

Collapse
tobiassn profile image
Tobias SN

It’s just another case of “Don’t get too comfortable doing this.”.

Collapse
graingert profile image
Thomas Grainger

Or just use the automatic package-lock.json like everyone else...

Collapse
henryjw profile image
Henry Williams Author

Good point. I was under the impression that when a new version of the package is released, the dependency tree would be regenerated for the newest version. But after running some tests, it looks like that's not the case; once a version is specified in the package-lock.json, it won't be updated if it fulfills the required version; E.g., 0.24.0 fulfills the requirement for 0.x, so even if 0.25.0 is released, npm will continue to install and use 0.24.0.

Collapse
trusktr profile image
Joe Pea

That hasn't always been the case, and if that is what you want that is why they recommend using npm ci instead of npm install so that package-lock.json will absolutely dictate which dependencies you install.

Thread Thread
trusktr profile image
Joe Pea

I mean,even if in theory npm install installs based on package-lock.json, it hasn't always been the case, and has varied from version to version of npm, and once you have a valid package-lock.json, npm ci is THE way to guarantee you are installing based on it.

No good, I know!

Collapse
graingert profile image
Thomas Grainger

Yep, otherwise package-lock.json would be totally useless

Collapse
qm3ster profile image
Mihail Malo

For projects that stay in major 0 for too long, use ~0.x.y instead of ^0.x.y.
Locking them down to 0.x.y is basically always overkill until something literally breaks.
And, to just parrot everyone else in the comments, shrinkwrap and have tests :)

Collapse
denissmuhla profile image
Deniss Muhļa

Agree this can be very frustrating. There was few times when a developer can't reproduce behavior which can see other developer because of this. Locked versions long time ago in our projects. We use npm upgrade tools for controlled packages upgrade...

Collapse
abraham profile image
Abraham Williams

My personal preference is to have good test coverage and then auto-update with a tool like renovate.

Collapse
johannestegner profile image
Johannes

I remember a case at one of my earlier workplaces, the first workplace where I used node.js (think it was v0.6 back then). We where 5 people using the same code-base and three out of us could not start the application, it blew up in a module which we all had at the same version!

After hours of debugging and trying to figure out what was wrong, we noticed that one of the dependencies of the dependencies dependency had a patch version added (It went from like... 1.2.3 to 1.2.3-1), a patch which broke the API totally...

I no longer trust peoples versioning and always try to stick to using SEMVER on my own projects...

:P

Collapse
vanuan profile image
John Yani • Edited on

Since when has npm implemented lock files? I've been waiting years before switching to yarn.

Collapse
qm3ster profile image
Mihail Malo

Are you still not on pnpm? 😲😲

Collapse
henryjw profile image
Henry Williams Author

For some time now. It's been the default for at least a year.

Collapse
vanuan profile image
John Yani

Not for long then. Well, too late anyway.

Collapse
tinmanjk profile image
Ivan Petrov

I am just wondering - is there a downside to doing this locking down of dependencies by default?

Collapse
obsessiveprogrammer profile image
Tom Bonanno

Yes. You cannot use npm update to automatically update based on the semver. I prefer using package-lock.json to lock versions.

Collapse
tinmanjk profile image
Ivan Petrov

Thanks. Will look more into it.