89 lines
2.4 KiB
JavaScript
89 lines
2.4 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Enforce schema/version markers on SF-owned JSON contracts.
|
|
*
|
|
* This intentionally does not scan ecosystem configuration files such as
|
|
* tsconfig.json, package.json, Biome config, or lockfiles. Those files are
|
|
* versioned by their owning tools. This check covers JSON that SF owns as
|
|
* runtime data, persisted contracts, or generated artifact templates.
|
|
*/
|
|
|
|
import { execFileSync } from "node:child_process";
|
|
import { readFileSync } from "node:fs";
|
|
|
|
const REQUIRED_PREFIXES = ["src/resources/extensions/sf/"];
|
|
const EXEMPT_SUFFIXES = ["/package.json"];
|
|
const VERSION_KEYS = ["schemaVersion", "version"];
|
|
|
|
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(Boolean);
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
throw new Error(`failed to list tracked JSON files: ${message}`);
|
|
}
|
|
}
|
|
|
|
function shouldCheck(path) {
|
|
return (
|
|
REQUIRED_PREFIXES.some((prefix) => path.startsWith(prefix)) &&
|
|
!EXEMPT_SUFFIXES.some((suffix) => path.endsWith(suffix))
|
|
);
|
|
}
|
|
|
|
function hasOwn(object, key) {
|
|
return Object.prototype.hasOwnProperty.call(object, key);
|
|
}
|
|
|
|
function hasVersionMarker(parsed) {
|
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return false;
|
|
if (VERSION_KEYS.some((key) => hasOwn(parsed, key))) return true;
|
|
|
|
const meta = parsed._meta;
|
|
return Boolean(
|
|
meta &&
|
|
typeof meta === "object" &&
|
|
!Array.isArray(meta) &&
|
|
VERSION_KEYS.some((key) => hasOwn(meta, key)),
|
|
);
|
|
}
|
|
|
|
const failures = [];
|
|
let checked = 0;
|
|
|
|
for (const path of trackedJsonFiles()) {
|
|
if (!shouldCheck(path)) continue;
|
|
checked++;
|
|
|
|
let parsed;
|
|
try {
|
|
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
failures.push(`${path}: invalid JSON (${message})`);
|
|
continue;
|
|
}
|
|
|
|
if (!hasVersionMarker(parsed)) {
|
|
failures.push(
|
|
`${path}: missing schemaVersion/version marker (top-level or _meta)`,
|
|
);
|
|
}
|
|
}
|
|
|
|
if (failures.length > 0) {
|
|
console.error("Versioned JSON check failed:");
|
|
for (const failure of failures) {
|
|
console.error(` - ${failure}`);
|
|
}
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(`Versioned JSON check passed (${checked} file${checked === 1 ? "" : "s"}).`);
|