repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
scripts/compatibility.ts
Rawimport { 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<string, string> | 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<string, string>;
};
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();