Files
EpicNext-Cms/scripts/check-node-toolchain.mjs
Simo fec8dd3c5d
CI / check (push) Failing after 53s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped
fix(docker): enforce Node version alignment across build stages
2026-09-11 00:11:18 +02:00

57 lines
1.5 KiB
JavaScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const readProjectFile = (path) =>
readFileSync(resolve(projectRoot, path), "utf8");
const pinnedVersion = readProjectFile(".nvmrc").trim();
assert.match(
pinnedVersion,
/^\d+\.\d+\.\d+$/,
".nvmrc must pin an exact Node.js version",
);
const major = Number.parseInt(pinnedVersion.split(".")[0], 10);
const packageJson = JSON.parse(readProjectFile("package.json"));
assert.equal(
packageJson.engines?.node,
`>=${pinnedVersion} <${major + 1}`,
"package.json engines.node must match the .nvmrc release line",
);
assert.equal(
Number.parseInt(packageJson.devDependencies?.["@types/node"], 10),
major,
"@types/node must match the pinned Node.js major",
);
const nodeImages = [
...readProjectFile("Dockerfile").matchAll(
/^FROM\s+(?:--platform=\S+\s+)?node:([^\s]+)\s*/gim,
),
].map((match) => match[1]);
assert.ok(
nodeImages.length > 0,
"Dockerfile must declare a pinned Node.js base image",
);
for (const image of nodeImages) {
assert.equal(
image,
`${pinnedVersion}-alpine`,
"every Docker Node.js stage must match .nvmrc",
);
}
if (!process.argv.includes("--static")) {
assert.equal(
process.versions.node,
pinnedVersion,
"the active Node.js runtime must match .nvmrc",
);
}
console.log(`Node.js toolchain is aligned on ${pinnedVersion}`);