If you subscribe to the philosophy of Semantic Versioning (you should!) and use npm, there is a certain behaviour that might trip you up. If you don't want to waste a full hour like I did, this might be helpful.

I use Eleventy, and it hasn't hit v1.0 yet. It is, therefore, implicitly signalled by the developer to not be ready for production.

The precursor to the v1.0 release - v0.11.0 - was published recently.

As I was on v0.10.0, the latest update should have been simple to upgrade to, right? Just using the update option on npm and specifying the package should have done, like so:

npm upgrade @11ty/eleventy

Well, turns out, this didn't work! 😭

Time to explore.

How is a dependency specified in package.json by default?

First, what happens when you npm install something?

npm install @11ty/eleventy

By default, it saves the package as a dependency in your project's package.json file using the caret syntax for the version scheme:

"dependencies": {
"@11ty/eleventy": "^0.10.0"

The caret syntax almost always means: if there is a [minor] version update or a [patch] update, but not a [major] update, download and save the new package version.

Ok, so what?

Well, let's demystify the almost always above, and you'll see some interesting phenomenon.

For a pre v1.0 release, also called a pre-release, the rule I mentioned above stands void -- and that's why the npm upgrade command, by design, is not meant to work in my case.

It follows a different version matching algorithm. What is that? Look at npm's documentation on semver:

Allows changes that do not modify the left-most non-zero digit in the [major, minor, patch] tuple. In other words, this allows patch and minor updates for versions 1.0.0 and above, patch updates for versions 0.X >=0.1.0, and no updates for versions 0.0.X.

Many authors treat a 0.x version as if the x were the major “breaking-change” indicator.

Caret ranges are ideal when an author may make breaking changes between 0.2.4 and 0.3.0 releases, which is a common practice. However, it presumes that there will not be breaking changes between 0.2.4 and 0.2.5. It allows for changes that are presumed to be additive (but non-breaking), according to commonly observed practices.

This means if Eleventy had a v0.10.1 release, I would have had no trouble upgrading. There is no breaking change expected in this release.

If Eleventy had versioned all its releases so far using just the patch field, then no upgrade would have happened between, for example, v0.0.10 and v0.0.11. Since only the patch field is being used, every release is presumed to be a breaking change.

A similar behaviour applies for pre-release updates to production (v1.0+) packages. If a developer has released v1.1-alpha.1, it will be ignored by npm upgrade. An alpha release, of course, is not ready for production.

How did I finally upgrade to v0.11.0?

Turns out you have to do it the old-fashioned way. Go ahead and edit the package.json file, and specify the version you need. Then install the dependencies again.

This is usually frowned upon for good reason - this, however, is one exception.