113 lines
3.3 KiB
JavaScript
113 lines
3.3 KiB
JavaScript
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { pathToFileURL } from "node:url";
|
|
|
|
const governance = JSON.parse(
|
|
await readFile("config/contracts/registry-governance.json", "utf8"),
|
|
);
|
|
const failures = [];
|
|
const owners = new Map();
|
|
const snapshots = [];
|
|
|
|
for (const specification of governance.registries) {
|
|
if (owners.has(specification.registryId)) {
|
|
failures.push(`duplicate owner for ${specification.registryId}`);
|
|
}
|
|
owners.set(specification.registryId, specification.owner);
|
|
|
|
let rows = specification.declaredRows;
|
|
try {
|
|
await access(specification.path);
|
|
const module = await import(
|
|
`${pathToFileURL(path.resolve(specification.path)).href}?registry-check=${Date.now()}`
|
|
);
|
|
rows = module[specification.exportName];
|
|
} catch {
|
|
if (!rows) failures.push(`missing registry source ${specification.path}`);
|
|
}
|
|
|
|
if (!rows || typeof rows !== "object" || Array.isArray(rows)) {
|
|
failures.push(`${specification.registryId} is not an object registry`);
|
|
continue;
|
|
}
|
|
|
|
for (const [rowName, row] of Object.entries(rows)) {
|
|
if (!row || typeof row !== "object" || Array.isArray(row)) {
|
|
failures.push(`${specification.registryId}.${rowName} is not an object`);
|
|
continue;
|
|
}
|
|
for (const field of specification.requiredFields) {
|
|
if (!(field in row)) {
|
|
failures.push(`${specification.registryId}.${rowName} missing ${field}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
snapshots.push({
|
|
registryId: specification.registryId,
|
|
owner: specification.owner,
|
|
source: specification.path,
|
|
rowCount: Object.keys(rows).length,
|
|
rows,
|
|
});
|
|
}
|
|
|
|
const sourceFiles = [
|
|
"src/application",
|
|
"src/presentation",
|
|
"src/domain",
|
|
];
|
|
const adHocPatterns = [
|
|
{ name: "direct fetch", expression: /\bfetch\s*\(/ },
|
|
{ name: "direct localStorage", expression: /\blocalStorage\.(?:get|set|remove)Item/ },
|
|
{ name: "direct import.meta.env", expression: /\bimport\.meta\.env\./ },
|
|
{ name: "raw API path", expression: /["']\/api\// },
|
|
];
|
|
|
|
/** @param {string} directory */
|
|
async function scanDirectory(directory) {
|
|
const entries = await import("node:fs/promises").then(({ readdir }) =>
|
|
readdir(directory, { withFileTypes: true }),
|
|
);
|
|
for (const entry of entries) {
|
|
const target = path.join(directory, entry.name);
|
|
if (entry.isDirectory()) {
|
|
await scanDirectory(target);
|
|
continue;
|
|
}
|
|
if (!/\.(js|jsx|mjs)$/.test(entry.name)) continue;
|
|
const content = await readFile(target, "utf8");
|
|
for (const pattern of adHocPatterns) {
|
|
if (pattern.expression.test(content)) {
|
|
failures.push(`ad-hoc ${pattern.name} in ${target}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const sourceDirectory of sourceFiles) {
|
|
await scanDirectory(sourceDirectory);
|
|
}
|
|
|
|
await mkdir("artifacts/quality", { recursive: true });
|
|
await writeFile(
|
|
"artifacts/quality/registries.json",
|
|
`${JSON.stringify(
|
|
{
|
|
schemaVersion: 1,
|
|
generatedAt: new Date().toISOString(),
|
|
compatibilityImpact: governance.compatibilityImpact.current,
|
|
failures,
|
|
registries: snapshots,
|
|
},
|
|
null,
|
|
2,
|
|
)}\n`,
|
|
);
|
|
|
|
if (failures.length > 0) {
|
|
process.stderr.write(`Registry governance failed:\n${failures.join("\n")}\n`);
|
|
process.exit(1);
|
|
}
|
|
process.stdout.write(`Registry governance: ${snapshots.length} registries PASS\n`);
|