Semantic versioning, or semver, is a set of rules for version numbers that makes each number say what kind of change a release brings. A version is three numbers, MAJOR.MINOR.PATCH, such as 4.17.21. npm packages are versioned this way, so when a package's authors follow the rules, its version number tells you what to expect from an upgrade before you read the changelog.
| Release | Example | What it signals |
|---|---|---|
| Patch | 1.4.2 to 1.4.3 | Bug fixes only. Code that worked keeps working. |
| Minor | 1.4.3 to 1.5.0 | New features, added without breaking anything. The patch number goes back to 0. |
| Major | 1.5.0 to 2.0.0 | A breaking change: code that worked may need changing. Minor and patch go back to 0. |
| Pre-release | 2.0.0-beta.3 | A test version of 2.0.0, published before the release itself. |
A pre-release tag goes after a -. It marks a version as not ready yet, and it comes before the release it leads to: 2.0.0-beta.3 is lower than 2.0.0. It's still higher than every 1.x.x version, because the three numbers are compared first.
Each part is a whole number, compared as a number. 1.10.0 is minor version ten, so it's newer than 1.9.0, minor version nine. Compared as text, character by character, it looks older:
"1.10.0" > "1.9.0"; // false: as text, "1" comes before "9"
["1.9.0", "1.10.0", "1.2.0"].sort(); // ["1.10.0", "1.2.0", "1.9.0"]: wrong for versions
You meet semver in package.json, where version ranges rely on its promises: ^1.4.2 accepts any version from 1.4.2 up to, but not including, 2.0.0, and ~1.4.2 accepts only patch releases, from 1.4.2 up to, but not including, 1.5.0. Versions below 1.0.0 are for early development, when anything may change.
Write compareVersions(a, b). It returns -1 when version a is lower than b, 1 when it's higher, and 0 when they're equal, so it works as a sort comparator: versions.sort(compareVersions).
A version is numbers joined by dots, like 1.10.2, optionally followed by - and a pre-release tag, like 2.0.0-beta.3. The tag is everything after the first -.
Compare the numbers from left to right, as numbers rather than text: 1.10.0 is higher than 1.9.0. A missing number counts as 0, so 1.2 equals 1.2.0. The first difference decides.
Tags matter only when all the numbers are equal. Then a version without a tag is higher than one with a tag: 1.0.0-rc.1 comes before 1.0.0.
Two tags are compared identifier by identifier, splitting them at dots. Two identifiers made only of digits compare as numbers (beta.2 is lower than beta.10). Two other identifiers compare by character code, the way < compares strings (alpha is lower than beta, and Beta is lower than beta). A digits-only identifier is lower than any other (1.0.0-1 is lower than 1.0.0-alpha).
If every identifier matches but one tag has fewer, that tag is lower (alpha is lower than alpha.1).
compareVersions("1.10.0", "1.9.0"); // 1
compareVersions("1.2", "1.2.0"); // 0
compareVersions("2.0.0-rc.1", "2.0.0"); // -1
compareVersions("1.0.0-beta.2", "1.0.0-beta.10"); // -1
["1.0.0", "1.0.0-rc.1", "0.9.12", "1.0.0-alpha.1", "1.0.0-alpha"].sort(compareVersions);
// ["0.9.12", "1.0.0-alpha", "1.0.0-alpha.1", "1.0.0-rc.1", "1.0.0"]
expect(compareVersions("1.2.3", "1.2.3")).toBe(0);
expect(compareVersions("0.0.0", "0.0.0")).toBe(0);
expect(compareVersions("1.0.0-alpha.1", "1.0.0-alpha.1")).toBe(0);
expect(compareVersions("1.10.0", "1.9.0")).toBe(1);
expect(compareVersions("1.2.3", "1.2.10")).toBe(-1);
expect(compareVersions("2.0.0", "10.0.0")).toBe(-1);
expect(compareVersions("1.0.5", "1.0.2")).toBe(1);
expect(compareVersions("3.0.0", "2.9.9")).toBe(1);
expect(compareVersions("1.2", "1.2.0")).toBe(0);
expect(compareVersions("1", "1.0.1")).toBe(-1);
expect(compareVersions("1.0.0.1", "1")).toBe(1);
expect(compareVersions("1.0.0-alpha", "1.0.0")).toBe(-1);
expect(compareVersions("1.0.0", "1.0.0-rc.1")).toBe(1);
expect(compareVersions("1.0.0-rc.1", "0.9.9")).toBe(1);
expect(compareVersions("1.0.0-beta.2", "1.0.0-beta.10")).toBe(-1);
expect(compareVersions("1.0.0-11", "1.0.0-9")).toBe(1);
expect(compareVersions("1.0.0-alpha", "1.0.0-beta")).toBe(-1);
expect(compareVersions("1.0.0-beta", "1.0.0-Beta")).toBe(1);
expect(compareVersions("1.0.0-1", "1.0.0-alpha")).toBe(-1);
expect(compareVersions("1.0.0-alpha.beta", "1.0.0-alpha.1")).toBe(1);
expect(compareVersions("1.0.0-alpha", "1.0.0-alpha.1")).toBe(-1);
expect(compareVersions("1.0.0-alpha.1.2", "1.0.0-alpha.1")).toBe(1);
const versions = ["1.0.0", "1.0.0-rc.1", "1.0.0-beta.11", "1.0.0-beta.2", "1.0.0-beta", "1.0.0-alpha.beta", "1.0.0-alpha.1", "1.0.0-alpha", "0.9.0"];
expect([...versions].sort(compareVersions)).toEqual(["0.9.0", "1.0.0-alpha", "1.0.0-alpha.1", "1.0.0-alpha.beta", "1.0.0-beta", "1.0.0-beta.2", "1.0.0-beta.11", "1.0.0-rc.1", "1.0.0"]);
Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.
Ctrl + Enter runs the tests (⌘ + Enter on a Mac).
I'm here to help with Compare versions. I give hints, never the solution, so you get to solve it yourself. Ask for a hint, select code to ask about it, or press Ask Tuto why on a failing test.
Your first 3 answers need no account. for free to keep asking.