Files
playbook/cangjie-skill/website/scripts/validate-registry.mjs
T
2026-08-31 05:00:46 +08:00

89 lines
3.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { readFile, readdir } from "node:fs/promises";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import Ajv2020 from "ajv/dist/2020.js";
import addFormats from "ajv-formats";
import { load as loadYaml } from "js-yaml";
const here = dirname(fileURLToPath(import.meta.url));
const root = resolve(here, "../..");
const registryDir = join(root, "registry");
const schemasDir = join(root, "schemas");
const loadSchema = async (name) => JSON.parse(await readFile(join(schemasDir, name), "utf8"));
const ajv = new Ajv2020({ allErrors: true, strict: false });
addFormats(ajv);
// dispatcher 通过 $id 引用 v1/v2,必须先注册子 schemaADR-004
ajv.addSchema(await loadSchema("registry-entry-v1.schema.json"));
ajv.addSchema(await loadSchema("registry-entry-v2.schema.json"));
const validate = ajv.compile(await loadSchema("registry-entry.schema.json"));
const folders = (await readdir(registryDir, { withFileTypes: true }))
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name)
.sort();
const errors = [];
const slugs = new Set();
let legacyPacks = 0;
let legacySkills = 0;
let v2Packs = 0;
let v2Entrypoints = 0;
let v2Capabilities = 0;
for (const folder of folders) {
const entryPath = join(registryDir, folder, "entry.yaml");
let entry;
try {
entry = loadYaml(await readFile(entryPath, "utf8"));
} catch (error) {
errors.push(`${folder}: cannot read entry.yaml (${error.message})`);
continue;
}
if (!validate(entry)) {
for (const issue of validate.errors ?? []) {
errors.push(`${folder}${issue.instancePath || "/"}: ${issue.message}`);
}
}
if (entry?.slug !== folder) {
errors.push(`${folder}: folder name must equal slug "${entry?.slug ?? "missing"}"`);
}
if (slugs.has(entry?.slug)) {
errors.push(`${folder}: duplicate slug "${entry.slug}"`);
}
slugs.add(entry?.slug);
if (entry?.schema_version === 2) {
// v2 不变量:skill_count 是兼容别名,必须等于 entrypoint_count
if (entry.skill_count !== entry.entrypoint_count) {
errors.push(`${folder}: v2 requires skill_count (${entry.skill_count}) === entrypoint_count (${entry.entrypoint_count})`);
}
if (entry.capability_count < entry.entrypoint_count && entry.output_mode !== "legacy-pack") {
errors.push(`${folder}: capability_count (${entry.capability_count}) must be >= entrypoint_count (${entry.entrypoint_count})`);
}
v2Packs += 1;
v2Entrypoints += Number(entry.entrypoint_count ?? 0);
v2Capabilities += Number(entry.capability_count ?? 0);
} else {
legacyPacks += 1;
legacySkills += Number(entry?.skill_count ?? 0);
}
}
if (folders.length === 0) errors.push("registry: no entries found");
if (errors.length > 0) {
console.error(`Registry validation failed with ${errors.length} error(s):`);
for (const error of errors) console.error(`- ${error}`);
process.exit(1);
}
console.log(
`Registry valid: ${folders.length} packs — legacy ${legacyPacks} packs / ${legacySkills} skills; ` +
`v2 ${v2Packs} packs / ${v2Entrypoints} entrypoints / ${v2Capabilities} capabilities.`,
);