typescript 56 lines · 9 steps

Parsing and comparing semver strings in TypeScript

A regex-backed parser and a spec-faithful comparator that ranks version strings the way semver defines.

Explained by highlit
1type Semver = {
2 major: number;
3 minor: number;
4 patch: number;
5 prerelease: string[];
6 build: string[];
7};
8 
9const SEMVER_RE =
10 /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+([0-9A-Za-z.-]+))?$/;
11 
12export function parseSemver(input: string): Semver {
13 const match = SEMVER_RE.exec(input.trim());
14 if (!match) {
15 throw new Error(`Invalid semver string: "${input}"`);
16 }
17 const [, major, minor, patch, prerelease, build] = match;
18 return {
19 major: Number(major),
20 minor: Number(minor),
21 patch: Number(patch),
22 prerelease: prerelease ? prerelease.split(".") : [],
23 build: build ? build.split(".") : [],
24 };
25}
26 
27function comparePrerelease(a: string[], b: string[]): number {
28 if (a.length === 0 && b.length > 0) return 1;
29 if (a.length > 0 && b.length === 0) return -1;
30 
31 const len = Math.min(a.length, b.length);
32 for (let i = 0; i < len; i++) {
33 const x = a[i];
34 const y = b[i];
35 const xn = /^\d+$/.test(x);
36 const yn = /^\d+$/.test(y);
37 if (xn && yn) {
38 const diff = Number(x) - Number(y);
39 if (diff !== 0) return Math.sign(diff);
40 } else if (xn !== yn) {
41 return xn ? -1 : 1;
42 } else if (x !== y) {
43 return x < y ? -1 : 1;
44 }
45 }
46 return Math.sign(a.length - b.length);
47}
48 
49export function compareSemver(a: string, b: string): -1 | 0 | 1 {
50 const va = parseSemver(a);
51 const vb = parseSemver(b);
52 for (const key of ["major", "minor", "patch"] as const) {
53 if (va[key] !== vb[key]) return va[key] < vb[key] ? -1 : 1;
54 }
55 return comparePrerelease(va.prerelease, vb.prerelease) as -1 | 0 | 1;
56}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Capture groups map cleanly onto a structured record via array destructuring.
  2. 2Semver comparison is lexicographic on numeric fields, then a special-cased prerelease ordering.
  3. 3A version without a prerelease outranks one that has it, and numeric identifiers compare numerically.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Parsing and comparing semver strings in TypeScript — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code