Files
horizon-airdef/website/src/versions.js
T
Ludovic Marie fde383c6e8 Website: read 0.2 tags (v0.2.0, launcher@0.2.0) as newer than the dated ones
From 0.2 the game is tagged vX.Y.Z and the launcher launcher@X.Y.Z (docs/versioning.md). The site
took only vYYYY.MM.DD as a game release and launcher-v as a launcher, so it would keep offering
launcher 0.16 and showing the last dated patch notes.

src/versions.js holds the tag test and order (every 0.2 tag above every tag from before, semver
within, a pre-release before its release), shared by build.mjs and main.js; the download button
picks the newest launcher by tag, the notes the newest stable game releases, and the launcher
lookup falls back to launcher@ tags before launcher-v. Nothing shows "0.2" until a 0.2 release
exists. `npm test` runs its tests (node --test).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 02:13:33 +02:00

60 lines
2.4 KiB
JavaScript

// Horizon's release tags (docs/versioning.md). From 0.2 the game is vX.Y.Z and the launcher launcher@X.Y.Z;
// before, vYYYY.MM.DD[.N] and launcher-vX.Y.Z. Every 0.2 tag is newer than every tag from before. Shared by
// build.mjs and main.js, and the same order as the launcher's and tools/release_train.py's.
const SEMVER = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/;
// Semver's majors stay below this; a date tag's first number is a year.
const FIRST_YEAR = 1000;
const semver = (version) => {
const m = SEMVER.exec(version);
return m && Number(m[1]) < FIRST_YEAR ? m : null;
};
export const isGame = (tag) => /^v\d{4}\.\d{1,2}\.\d{1,2}(\.\d+)?$/.test(tag) || (tag.startsWith("v") && !!semver(tag.slice(1)));
// The version in a launcher tag (launcher@0.2.0, launcher-v0.16.0), or null.
export function launcherVersion(tag) {
if (tag.startsWith("launcher@") && semver(tag.slice(9))) return tag.slice(9);
const old = /^launcher-v(\d+\.\d+\.\d+)$/.exec(tag);
return old ? old[1] : null;
}
export const isLauncher = (tag) => launcherVersion(tag) !== null;
// [line, numbers, pre-release]: tags from before 0.2 by their numbers, then 0.2 tags in semver's order.
function key(tag) {
const version = tag.startsWith("launcher@") ? tag.slice(9) : tag.startsWith("v") ? tag.slice(1) : null;
const m = version !== null && semver(version);
if (!m) return [0, (tag.match(/\d+/g) || []).map(Number), []];
const pre = m[4] ? m[4].split(".").map((id) => (/^\d+$/.test(id) ? [0, Number(id)] : [1, id])) : null;
return [1, [m[1], m[2], m[3]].map(Number), pre];
}
const cmpList = (a, b, cmp) => {
for (let i = 0; i < Math.min(a.length, b.length); i++) {
const d = cmp(a[i], b[i]);
if (d) return d;
}
return a.length - b.length;
};
const cmpNumbers = (a, b) => {
const strip = (l) => { const c = [...l]; while (c.length && c[c.length - 1] === 0) c.pop(); return c; };
return cmpList(strip(a), strip(b), (x, y) => x - y);
};
const cmpId = ([ka, va], [kb, vb]) => ka - kb || (ka ? (va < vb ? -1 : va > vb ? 1 : 0) : va - vb);
// Negative when tag a is older than tag b.
export function compareTags(a, b) {
const [la, na, pa] = key(a);
const [lb, nb, pb] = key(b);
if (la !== lb) return la - lb;
const d = cmpNumbers(na, nb);
if (d || !la) return d;
// A release follows its pre-releases.
if (!pa || !pb) return (pa ? -1 : 0) - (pb ? -1 : 0);
return cmpList(pa, pb, cmpId);
}
export const newestFirst = (a, b) => compareTags(b, a);