The Gitea Actions gate repeated, after the merge, what a release already runs before it, and its runner shared the git host's container: one run there starved Gitea of memory until it stopped answering. The lanes now live in scripts/check.sh, run the same by the release dispatcher and by hand, and the gate-contract test holds its lines (the ruff and pip-audit checks read it instead of the workflow). The two audits only CI ran join the gate: pip-audit over the lock (pinned in the test image, blocking at any severity) and npm audit of the production dependencies. The engine contracts drop the CI containers; the register records that pushes have no server-side gate.
69 lines
3.5 KiB
TypeScript
69 lines
3.5 KiB
TypeScript
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<string, string>;
|
|
};
|
|
|
|
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("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);
|
|
});
|