DEV Community

Bruno Pinheiro
Bruno Pinheiro

Posted on

Semantic Versioning: simple but powerful

If you have started any project, even if you didn’t publish or finish it, I bet you saw some versioning for it, for the project itself, or the dependencies.

For example, when you start a new project in Angular, you should see something like this.

screenshot of the package.json file inside an Angular project

Mobile games are good for you to see this. They usually have their version number on the bottom of their login screen.

There are many ways to give a version number to something. Actually, it doesn’t even have to be a number. You can work on a project and give it version A, B, C, etc. The important part is that you can differentiate each version.

When it comes to versioning, there is not a hard rule. Like I said, you can use numbers, letters, or anything you want. As long as you can differentiate the versions you're fine.

On one hand, this is good because of the freedom you have. On the other hand, this is bad because it creates a lack of pattern, so each project could have its own versioning system, and you would have to understand each one separately.

Versioning systems are supposed to help with that issue. They provide a clear and logical pattern for the versions.

There are quite a few options for this, but the one we’re talking about today is Semantic Versioning. I really like this one because it's simple to understand and powerful enough to represent everything we need to know.

The basic idea of Semantic Versioning is that it represents the version with three digits, separated by a point, like x.y.z.

It may not look like it, but this simple representation can tell us what type of change was made, helping us to decide if we can safely upgrade a dependency for example.


How, you may ask? Let’s delve into this.

Each digit represents something. The first one represents the ‘Major’ version, the second ‘Minor’ version and the third represents the ‘Patch’ version.

  • Patch: you should increase the patch number if there are one or more bug fixes or corrections. No new stuff, just correction.

Eg: if the current version of your project is 1.0.1 you should advance to 1.0.2.

  • Minor: you should increase the minor version if there are one or more backward-compatible implementations or new features. When you increase the minor, you should reset the patch to 0.

Eg: If you are on version 1.0.2 the next one should be 1.1.0.

  • Major: this should be increased when you have backward-incompatible changes. You can have minor and/or patch changes as well, but if something changes in an incompatible way, you should increase the major version number. When you increment the major, you should reset the minor and patch to 0.

Eg: if the current version is 1.1.0 the next one should be 2.0.0

What you must remember about the resets, is that whenever you increase one of the numbers, you reset the others to the right. So, when you increase the major you reset the minor and patch. When you increase the minor you reset only the patch.

Resetting the numbers to 0 is necessary to correctly tell what is in the new version.

Let’s take the same example as before, you are on version 1.1.0. This means that major version 1 had one minor version released. When we increase the major to 2 we must reset the other numbers because this new major version doesn’t have any minor or patch versions released.

Besides using those three digits, you can also say that a version is a pre-release one, and you can also attach some metadata if you want. Let’s talk about that later on.

That’s the idea behind the semantic versioning.

Now, let’s talk about its 11 rules. (don’t worry, they’re not hard, and some of them you already know).


Rules of Semantic Versioning

  1. Public API: must have a public API. It can be on the documentation or the code, but it must have it, and it should be clear and concise.

  2. Version number format: the version number must be in the format x.y.z. All digits must be non-negative integers and it must not start with zero.

  3. Immutable versions: once a version is released it should not be modified in any circumstance. If it needs a correction or modification then you should release a new version.

  4. Initial development: when you first start working on a project you must start its major version with 0.y.z. That’s because during the initial development, many things will change and the public API should NOT be considered stable at this point.

  5. Version 1.0.0: a public API will be considered as defined and stable after the release of version 1.0.0. All other versions from now on should be based on this one.

  6. Patch version: the patch version indicates that one or more bugs were fixed and the code remains compatible with the previous version. This means that version 1.0.1 fixes a problem with version 1.0.0 and it’s compatible with it.

  7. Minor version: the minor version can only be increased under two circumstances: when new features are being added, but still compatible with the previous version, or to deprecate some published feature. When you update the minor version you should reset the patch version to 0. For example, a project with version 1.0.6 just had a new minor version released. The next version should be 1.1.0, not 1.1.6.

  8. Major version: when new changes that are incompatible with previous versions of the public API are released, you should increase the major version, and reset minor and patch to 0.

  9. Pre-release version: you can indicate a pre-release version by adding a hyphen and a series of point-separated identifiers after the patch version. The identifiers should contain only alphanumeric ASCII characters, and hyphens (0-9A-Za-z-). Also, the identifiers should not be empty or have trailing zeros.

Eg: 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-beta

  1. Build metadata: similar to the pre-release version, you may add build metadata information by adding a plus sign after the patch version followed by point-separated identifiers. The identifiers should be only alphanumeric characters and hyphens (0-9A-Za-z-). You can add information like the timestamp of the build or the hash code for the commit for example.

Eg: 1.0.0+20240229072000

  1. Version precedence: this defines how each version should be compared to others.

You start by separating the numbers. Separate the major, minor, patch and, pre-release. The build metadata info is not used to compare the versions.

Start comparing each number individually, from left to right until you find a difference.

Eg 1.0.0 < 2.0.0, 1.1.1 < 1.1.2

If major, minor and patch are the same, you check the pre-release info. If there is pre-release information, then it has a lower precedence.

Eg: 1.0.0-beta < 1.0.0

This is the idea behind Semantic Versioning. It’s quite simple but really powerful. You have a lot of information for such a small versioning number.


Let’s recap the main topics.

3 digits separated by a point, x.y.z

They are called, major, minor, and patch.

You increase the major when you have changes that are incompatible with the previous version. When you do that you should reset minor and patch versions to 0.

You increase minor to deprecate something or to add changes that are compatible with the previous version. When you do that you should reset the patch to 0.

You increase the patch when you have one or more bug fixes and the code remains compatible with the previous version.

You can also have a pre-release version, for that, you add an identifier after the patch version separated by a hyphen, like 1.0.0-alpha.


If you want to get a more in-depth look at this topic I highly recommend going to the source material.

semver.org

Top comments (0)