import { readFileSync } from "node:fs"; import { expect, it } from "vitest"; // Four machines build or check this app, and for months they did not agree on which Node they were. // // On 2026-09-20 the server gate had been red for 73 of its last 90 frontend runs while the local gate // was green, and nothing told anyone: the dev machine ran Node 24, the CI container `node:22-bookworm`, // the prod build image `node:22-alpine`. The three failures were all engine splits, not code bugs — // `Intl.NumberFormat({notation:"compact", maximumFractionDigits:1})` renders "$160.0M" on 22 and // "$160M" on 24 (the minimum is the engine's to choose when you only give it a maximum), and // `Response.blob()` under jsdom hands back node's own Blob class on 22 but jsdom's on 24, so // `toBeInstanceOf(Blob)` asserts a realm rather than a type. Both are the engine showing through the // test — and the first one the engine showing through to the USER, since the browser that renders the // money string splits along the same line the build image does. // // So the version is a contract, not a coincidence, and this test is where it is held. `.nvmrc` is the // single source of truth; everything below must agree with it, including the engine actually running // this test. That last assertion is the one that pays: it turns "the local gate and the shipping image // have drifted apart" from something discovered months later in a CI log nobody reads into a red lane // on the very first run after the drift. // // To move the version: edit `.nvmrc`, then follow this test's failures. Every place it names is a place // that would otherwise have been forgotten. const readRoot = (rel: string) => readFileSync(new URL(`../../../${rel}`, import.meta.url), "utf8"); function majorIn(source: string, pattern: RegExp, where: string): number { const m = pattern.exec(source); // A rename or a moved line lands here. Re-point the test — the contract did not go away with it. expect(m, `${pattern} not found in ${where}`).not.toBeNull(); return Number(m![1]); } const pinned = majorIn(readRoot(".nvmrc"), /^v?(\d+)\b/, ".nvmrc"); const pkg = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8")) as { engines?: { node?: string }; devDependencies: Record; }; it("pins a plausible Node major", () => { // Guards against a typo'd `.nvmrc` silently agreeing with nothing (`Number("v24.x") === NaN`). expect(pinned).toBeGreaterThanOrEqual(24); }); it("builds the shipped bundle on the pinned engine", () => { // The image that compiles the SPA our users download. expect( majorIn(readRoot("Dockerfile"), /^FROM node:(\d+)-alpine AS frontend$/m, "Dockerfile"), ).toBe(pinned); }); it("runs the server gate on the pinned engine", () => { // The frontend job only — the backend job's node runs the checkout action, never our code, so it is // deliberately not part of this contract (see the note in check.yml). expect( majorIn( readRoot(".gitea/workflows/check.yml"), /^\s*container: node:(\d+)-bookworm$/m, "check.yml", ), ).toBe(pinned); }); it("declares the pinned engine to npm and to tsc", () => { expect(pkg.engines?.node).toBe(`${pinned}.x`); // Types a major ahead of the runtime make tsc green over APIs the shipped engine does not have — // the same defect as the gate split, just moved to compile time. const types = pkg.devDependencies["@types/node"]; expect(types, "@types/node is gone from devDependencies").toBeTruthy(); expect(majorIn(types!, /^\D*(\d+)\./, "@types/node")).toBe(pinned); }); it("is itself running on the pinned engine", () => { const running = Number(process.versions.node.split(".")[0]); expect( running, `this machine runs Node ${process.versions.node}, but the gate and the prod image ship Node ` + `${pinned}.x. Install it (\`nvm use\` reads .nvmrc) — a green gate on another major does not ` + `mean the build is green.`, ).toBe(pinned); });