116 lines
4.8 KiB
JavaScript
116 lines
4.8 KiB
JavaScript
import { readFile, writeFile } from "node:fs/promises";
|
|
import { dirname, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
import Ajv2020 from "ajv/dist/2020.js";
|
|
import standaloneCode from "ajv/dist/standalone/index.js";
|
|
|
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
const schemaDirectory = resolve(packageRoot, "protocol/schemas");
|
|
const outputPath = resolve(packageRoot, "src/protocol/generated/schema-bundle.ts");
|
|
const validatorsOutputPath = resolve(
|
|
packageRoot,
|
|
"src/protocol/generated/standalone-validators.ts",
|
|
);
|
|
const schemaNames = [
|
|
"identity",
|
|
"capabilities",
|
|
"capabilities-v2",
|
|
"command",
|
|
"command-v2",
|
|
"provider-descriptor",
|
|
"provider-event",
|
|
"workspace-diff",
|
|
"workspace-file-reference",
|
|
"semantic-tool",
|
|
"usage",
|
|
"stop-reason",
|
|
"terminal",
|
|
"question-set",
|
|
"question-response",
|
|
"question-adapter-fixture",
|
|
"request",
|
|
"result",
|
|
"event",
|
|
"event-v2",
|
|
"session-goal",
|
|
"fixture",
|
|
];
|
|
|
|
const schemas = await Promise.all(
|
|
schemaNames.map(async (name) => ({
|
|
name,
|
|
value: JSON.parse(await readFile(resolve(schemaDirectory, `${name}.schema.json`), "utf8")),
|
|
})),
|
|
);
|
|
|
|
const identifier = (name) => name.replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase());
|
|
const declarations = schemas
|
|
.map(({ name, value }) => `export const ${identifier(name)}Schema = ${JSON.stringify(value, null, 2)} as const;`)
|
|
.join("\n\n");
|
|
const bundle = schemaNames.map((name) => ` ${JSON.stringify(name)}: ${identifier(name)}Schema,`).join("\n");
|
|
const generated = `// Generated by scripts/generate-protocol-schema-module.mjs. Do not edit.\n\n${declarations}\n\nexport const prpSchemaBundle = {\n${bundle}\n} as const;\n`;
|
|
|
|
const schemaByName = Object.fromEntries(schemas.map(({ name, value }) => [name, value]));
|
|
const ajv = new Ajv2020({
|
|
allErrors: true,
|
|
strict: true,
|
|
strictRequired: false,
|
|
code: { esm: true, source: true },
|
|
formats: {
|
|
"date-time": /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/,
|
|
},
|
|
});
|
|
for (const { value } of schemas) ajv.addSchema(value);
|
|
const standaloneValidators = standaloneCode(ajv, {
|
|
fixtureValidator: schemaByName.fixture.$id,
|
|
eventValidator: schemaByName.event.$id,
|
|
eventV2Validator: schemaByName["event-v2"].$id,
|
|
resultValidator: schemaByName.result.$id,
|
|
});
|
|
const ucs2RuntimePattern = /const (func\d+) = require\("ajv\/dist\/runtime\/ucs2length"\)\.default;/;
|
|
if (!ucs2RuntimePattern.test(standaloneValidators)) {
|
|
throw new Error("Ajv standalone output no longer has the expected ucs2length runtime binding");
|
|
}
|
|
const equalRuntimePattern = /const (func\d+) = require\("ajv\/dist\/runtime\/equal"\)\.default;/;
|
|
if (!equalRuntimePattern.test(standaloneValidators)) {
|
|
throw new Error("Ajv standalone output no longer has the expected equal runtime binding");
|
|
}
|
|
const validatorsWithRuntimeImports = standaloneValidators.replace(
|
|
ucs2RuntimePattern,
|
|
"const $1 = typeof ucs2LengthModule === \"function\" ? ucs2LengthModule : ucs2LengthModule.default;",
|
|
).replace(
|
|
equalRuntimePattern,
|
|
"const $1 = typeof equalModule === \"function\" ? equalModule : equalModule.default;",
|
|
);
|
|
const validatorExportPattern = /export const (fixtureValidator|eventValidator|resultValidator) =/g;
|
|
const validatorExports = [...validatorsWithRuntimeImports.matchAll(validatorExportPattern)];
|
|
if (validatorExports.length !== 3) {
|
|
throw new Error("Ajv standalone output no longer has the expected validator exports");
|
|
}
|
|
// Ajv's generated functions are intentionally unchecked runtime JavaScript.
|
|
// Bound their exported types so TypeScript does not infer the full generated
|
|
// implementation graph. replay-contract.ts applies the checked public types.
|
|
const validators = validatorsWithRuntimeImports.replace(
|
|
validatorExportPattern,
|
|
"export const $1: unknown =",
|
|
);
|
|
const generatedValidators = `// Generated by scripts/generate-protocol-schema-module.mjs. Do not edit.\n// @ts-nocheck -- Ajv standalone output is JavaScript compiled from the checked-in schemas.\n\nimport equalModule from "ajv/dist/runtime/equal.js";\nimport ucs2LengthModule from "ajv/dist/runtime/ucs2length.js";\n\n${validators}\n`;
|
|
|
|
if (process.argv.includes("--check")) {
|
|
const current = await readFile(outputPath, "utf8").catch(() => "");
|
|
const currentValidators = await readFile(validatorsOutputPath, "utf8").catch(() => "");
|
|
if (current !== generated || currentValidators !== generatedValidators) {
|
|
process.stderr.write("Generated PRP schema modules are stale. Run pnpm generate:protocol-types.\n");
|
|
process.exitCode = 1;
|
|
} else {
|
|
process.stdout.write("Generated PRP schema modules match the JSON Schema sources.\n");
|
|
}
|
|
} else {
|
|
await Promise.all([
|
|
writeFile(outputPath, generated),
|
|
writeFile(validatorsOutputPath, generatedValidators),
|
|
]);
|
|
process.stdout.write("Generated PRP schema and standalone validator modules.\n");
|
|
}
|