import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; import { ROOT } from "./build/build.ts"; /** * Enforce one source of truth for the minimum supported Pi version: * `pi-ext.minimumPi` in the root manifest must equal the lowest Pi version in * the release compatibility matrix in `.ci/release.kdl`. * Root Pi dependencies stay unpinned; the lockfile records verified versions. */ const PI_SCOPE = "@earendil-works/"; function piRanges(deps: Record | undefined): string[] { return Object.entries(deps ?? {}) .filter(([name]) => name.startsWith(PI_SCOPE)) .map(([, range]) => range); } function parseMatrixPiAxis(kdl: string): string[] { const matrix = /matrix\s*\{([\s\S]*?)\}/.exec(kdl)?.[1] ?? ""; const line = /\bpi\b([^\n]*)/.exec(matrix)?.[1] ?? ""; return [...line.matchAll(/"([^"]+)"/g)].map((match) => match[1] as string); } function compareSemver(a: string, b: string): number { const pa = a.split(".").map(Number); const pb = b.split(".").map(Number); for (let i = 0; i < 3; i++) if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0); return 0; } function main(): void { const errors: string[] = []; const rootPkg = JSON.parse( readFileSync(join(ROOT, "package.json"), "utf8"), ) as { "pi-ext"?: { minimumPi?: string }; devDependencies?: Record; }; const minimumPi = rootPkg["pi-ext"]?.minimumPi; if (!minimumPi) { console.error("root package.json is missing pi-ext.minimumPi"); process.exit(1); } for (const range of piRanges(rootPkg.devDependencies)) if (range !== "*") errors.push(`root devDependency Pi range ${range} must be unpinned (*)`); const releaseKdl = join(ROOT, ".ci", "release.kdl"); if (!existsSync(releaseKdl)) errors.push(".ci/release.kdl is missing (release compatibility matrix)"); else { const axis = parseMatrixPiAxis(readFileSync(releaseKdl, "utf8")); if (axis.length === 0) errors.push(".ci/release.kdl matrix declares no pi axis"); else { const lowest = [...axis].sort(compareSemver)[0]; if (lowest !== minimumPi) errors.push( `release matrix lowest Pi ${lowest} disagrees with declared minimum ${minimumPi}`, ); } } if (errors.length) { for (const error of errors) console.error(`ERROR compatibility: ${error}`); process.exit(1); } console.log(`OK compatibility: minimum Pi ${minimumPi} consistent`); } main();