DEV Community 👩‍💻👨‍💻

Gajus Kuizinas
Gajus Kuizinas

Posted on

semver: v1 vs v1000

It's something that keeps coming up in GitHub issues on my various projects... they point at my project version, which could be v7, v28, v33, and then say "This project versioning is bad." or "This project API is unstable.", etc. and then compare them to projects that are v1, as if that somehow proves their point.

Here is the thing: any change that may break someone's project by merely upgrading your dependency is a breaking change and requires that you bump the major version. This includes:

  • dropping Node.js version support
  • removing Node.js version from test matrix (because there are no longer guarantees that the platform is supported)
  • fixing a bug that changes how an existing feature has been working (because someone might be relying on the erroneous behavior without realizing it)
  • fixing a type definition (because it might break their CI/CD)
  • updating dependencies, when either of dependency updates is a major version (because of any of the above)

...and anything else that may break projects that depend on your package.

Many projects do not consider many of these breaking changes because of the stigma about often updating major version of the project. They will say things like "only a breaking change if", etc. to diminish the significance of the change.

Personally, I would rather use software that is v1000 that properly follows semver and has a proper CHANGELOG, than v1 software that has "stable API" and makes "soft breaking changes".

The easiest way to enforce proper semver is by using tools like semantic-release and force major version bump based on keyword detection in the commit log (like "breaking change").

Important exchanges from comments:

But if someone ask you "I see you have released version 2.0.0, what's new there?" And you tell them 2.0.0 contains changes to the spelling errors [in the API]...How ridiculous would that be?

That's literally what the major version is for:

Major version X (X.y.z | X > 0) MUST be incremented if any backwards incompatible changes are introduced to the public API

That being said, you would typically not make this a breaking change by:

  • correct the API
  • create an alias using the erroneous API method and deprecate it

This way it would be a minor change, and major change would happen only once you drop the alias. Some libraries choose to keep them for years, until they batch them and do a single major version bump.

Top comments (8)

Collapse
jayjeckel profile image
Jay Jeckel

they point at my project version, which could be v7, v28, v33, and then say "This project versioning is bad." or "This project API is unstable."

Personally I'd stop listening to those people. They obviously still put some kind of cargo cult significance to versions and aren't making decisions on objective criteria.

That said, some of those version numbers are a bit excessive. That last one has had 33 major versions over 7 years; that is just about 5 major versions a year. I don't know the exact circumstances of that project, but that many breaking changes is in the ballpark of being unstable, in my opinion.

If you're changing the public api of your project that often, then you might want to consider longer design phase, so the api can be solidified before it's put into code; or if that isn't possible, maybe consider a longer pre-release/release-candidate stage so the official api can be worked out through use without affecting the major version number. Having a higher version number doesn't mean anything, but if I have to fixing breaking changes nearly half a dozen times a year, I might get upset over that.

Here is the thing: any change that may break someone's project by merely upgrading your dependency is a breaking change and requires that you bump the major version.

Maybe this is the confusion. That isn't at all the rule for major version bumps. The rule is Major version X (X.y.z | X > 0) MUST be incremented if any backwards incompatible changes are introduced to the public API. None of the items in your list are changes to the public api, except maybe the forth one about changing a type definition.

To go through them. Forgive me if some details are off, web frameworked projects aren't my main areas.

Changing the version of the supported platform isn't a breaking change, because it doesn't change the public api. To use conventional commits terms, it is a build type change, not an api type change.

Tests aren't part of the public api, so changes to them are test type changes, not api type changes, therefor they aren't breaking, regardless of what implications that has about what the main library does or doesn't support.

Fixing a bug is only a breaking change if it changes the public api. Even if people use the erroneous behavior, if fixing it doesn't require a public api change, then it isn't a breaking change.

If the type is part of the public api and you change that types definition, then, yea, that's a breaking change and will require a major version bump.

Updating dependencies is a build type change and is only breaking if changes in their api forces changes in your api. Otherwise, not an api or breaking change.

This has nothing to do with any stigmas about versions. The semver spec is clear enough that it is entirely concerned with the public api of a project, how that public api changes, and how those changes affect the version number. That's it, nothing more or less.

The two relevant quotes I can find in the the spec both say the same thing.

From the introduction:

Bug fixes not affecting the API increment the patch version, backwards compatible API additions/changes increment the minor version, and backwards incompatible API changes increment the major version.

From the actual spec rules:

8. Major version X (X.y.z | X > 0) MUST be incremented if any backwards incompatible changes are introduced to the public API

Of course, use version numbers however you want, but semver is very clear; it doesn't care if your update breaks other projects, it only cares if you break the public api.

Collapse
gajus profile image
Gajus Kuizinas Author • Edited on

MUST be incremented if any backwards incompatible changes are introduced to the public API. None of the items in your list are changes to the public api, except maybe the forth one about changing a type definition.

All of the changes that I've listed introduce backwards non-compatible changes, e.g.

dropping Node.js version support

While whatever I typed in the codebase may not change the API as it appears in the source code, the transpiled version of the code now includes API changes that are no longer incompatible with my program, i.e. If you were running a jay-keckel-app that depends on my package slonik@28.0.0 and upgraded to slonik@28.1.0 you would most certainly not expect that to break your app, and if dropping platform version is not considered a breaking change, then it most certainly will. It is that simple.

To use conventional commits terms, it is a build type change, not an api type change.

This would be true only if JavaScript was a compiled language.

Tests aren't part of the public api

They are part of the contract that guarantees the integrity of the API, i.e. If tests are not passing on the platform for which the package was released originally, that's a breaking change.

  1. Major version X (X.y.z | X > 0) MUST be incremented if any backwards incompatible changes are introduced to the public API

FYI, we are not the first to have this debate. There is a long, ongoing debate in the semver repository on exactly this subject.

github.com/semver/semver/issues/716

Collapse
jayjeckel profile image
Jay Jeckel

All of the changes that I've listed introduce backwards non-compatible changes

Sure, but they don't all introduce public api changes and that's the point, semver only speaks to the public api.

While whatever I typed in the codebase may not change the API as it appears in the source code, the transpiled version of the code now includes API changes

Ah, as I said, node/javascript ecosystem is not my main area of expertise, so fair enough, my bad.

They are part of the contract that guarantees the integrity of the API

Ok, but that isn't the same thing as being part of the public api.

FYI, we are not the first to have this debate. There is a long, ongoing debate in the semver repository on exactly this subject.

Thanks for the link, it's an interesting debate, but it's ultimately moot. The spec says what the spec says and no where does the spec mention supported platforms being part of the public api, so it isn't part of the public api. Maybe that will change in semver 3.0.0, but as it stands, it is what it is or isn't what it isn't, as the case may be.

Collapse
amiamigo profile image
Ami Amigo

Nah! I disagree. Semver is at fault there.

People already associate major releases with something significant. So you can't just say any change that breaks a previous build should be a major change. Sometimes it's something as minor as a typo and you want me to jump from ver 1.1.2 to version 2.0.0

We need a new versioning system that is forgiving of these errors. And No! No one wants to see version 879 in less than a year!

Collapse
gajus profile image
Gajus Kuizinas Author

How do you imagine this "new system" working?

It is either a breaking change or it is not, it is that simple.

People already associate major releases with something significant.

That's mostly because semver is misused, which is the point of this article.

Collapse
amiamigo profile image
Ami Amigo

That is a great question. But we can all agree that semver is missing some crucial points. I am also a victim of this. I released a CSS framework (amigocss.com) a few months ago. Soon I realized I did make a typo of a few classes...i.e border-radious instead of border-radius. Semver now requires me to do another push and name it as 2.0.0 almost a few hours after releasing 1.0.0. That's a problem! A big problem! Yes it's a breaking change...But if someone ask you "I see you have released version 2.0.0, what's new there?" And you tell them 2.0.0 contains changes to the spelling errors...How ridiculous would that be?

Or an alternative to that is waiting a few months till you have added enough changes just to justify the 2.0.0. Meanwhile, the bug (spelling error) is still living in people's projects...and sometimes they may think that error was a feature...and when you correct it later...it's now an inconvenience.

So people misuse semver because some decisions it makes doesn't make sense. I also did butcher semver purposely because I didn't want to push my release as 2.0.0 from 1.0.0 just because of a spelling error.

I recommend a system that is forgiving. A system that understands some breaking changes aren't major releases. How about percentages? As an example...may be less than 10% should not be a major changes, etc.

Thread Thread
gajus profile image
Gajus Kuizinas Author

But if someone ask you "I see you have released version 2.0.0, what's new there?" And you tell them 2.0.0 contains changes to the spelling errors...How ridiculous would that be?

As others have quoted, that's literally what the major version is for:

  1. Major version X (X.y.z | X > 0) MUST be incremented if any backwards incompatible changes are introduced to the public API

That being said, you would typically not make this a breaking change by:

  • correcting the API
  • creating alias using the erroneous API method and deprecate it

This way it would be a minor change, and major change would happen only once you drop the alias. Some libraries choose to keep them for years.

Collapse
610470416 profile image
NotFound404

Semver is the most disgraceful thing happened in softeare development.
Which makes versions change logs.

Head to your account's Settings to...

🌚 Enable dark mode
🔠 Change your default font
📚 Adjust your experience level to see more relevant content