singularity-forge/scripts/check-versioned-json.mjs

135 lines
3.4 KiB
JavaScript

#!/usr/bin/env node
/**
* Enforce valid JSON everywhere and schemaVersion markers on SF-owned contracts.
*
* Ecosystem JSON such as package.json, tsconfig.json, lockfiles, and extension
* manifests are parsed for validity but are not treated as SF data contracts.
* Their `version` fields belong to their owning tools or component release
* lifecycle. SF-owned runtime/data contracts use `schemaVersion` for shape
* compatibility.
*/
import { execFileSync } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
const CONTRACT_EXACT_PATHS = new Set([
"src/resources/extensions/sf/workflow-templates/registry.json",
]);
const CONTRACT_PREFIXES = ["src/resources/extensions/sf/learning/data/"];
function trackedJsonFiles() {
try {
const out = execFileSync("git", ["ls-files", "*.json"], {
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
});
return out
.split("\n")
.map((line) => line.trim())
.filter((line) => line && existsSync(line));
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`failed to list tracked JSON files: ${message}`);
}
}
export function isSfOwnedJsonContract(path) {
return (
CONTRACT_EXACT_PATHS.has(path) ||
CONTRACT_PREFIXES.some((prefix) => path.startsWith(prefix))
);
}
export function hasOwn(object, key) {
return Object.hasOwn(object, key);
}
export function getSchemaVersion(parsed) {
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
return false;
if (hasOwn(parsed, "schemaVersion")) return parsed.schemaVersion;
const meta = parsed._meta;
if (
meta &&
typeof meta === "object" &&
!Array.isArray(meta) &&
hasOwn(meta, "schemaVersion")
) {
return meta.schemaVersion;
}
return undefined;
}
export function hasValidSchemaVersion(parsed) {
const schemaVersion = getSchemaVersion(parsed);
return (
typeof schemaVersion === "number" &&
Number.isInteger(schemaVersion) &&
schemaVersion >= 1
);
}
export function checkJsonPolicy(paths, readText) {
const failures = [];
let contractsChecked = 0;
let filesParsed = 0;
for (const path of paths) {
filesParsed++;
let parsed;
try {
parsed = JSON.parse(readText(path));
} catch (error) {
if (
error &&
typeof error === "object" &&
"code" in error &&
error.code === "ENOENT"
) {
filesParsed--;
continue;
}
const message = error instanceof Error ? error.message : String(error);
failures.push(`${path}: invalid JSON (${message})`);
continue;
}
if (!isSfOwnedJsonContract(path)) continue;
contractsChecked++;
if (!hasValidSchemaVersion(parsed)) {
failures.push(
`${path}: missing numeric schemaVersion marker (top-level or _meta)`,
);
}
}
return { failures, filesParsed, contractsChecked };
}
export function run() {
const result = checkJsonPolicy(trackedJsonFiles(), (path) =>
readFileSync(path, "utf8"),
);
if (result.failures.length > 0) {
console.error("Versioned JSON check failed:");
for (const failure of result.failures) {
console.error(` - ${failure}`);
}
process.exit(1);
}
console.log(
`Versioned JSON check passed (${result.filesParsed} JSON file${result.filesParsed === 1 ? "" : "s"} parsed, ` +
`${result.contractsChecked} SF contract${result.contractsChecked === 1 ? "" : "s"} checked).`,
);
}
if (import.meta.url === `file://${process.argv[1]}`) {
run();
}