import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; const here = path.dirname(fileURLToPath(import.meta.url)); const webRoot = path.resolve(here, ".."); const contractPath = path.resolve(webRoot, "..", "api", "app", "contracts", "measurement_contract.v1.json"); const tsPath = path.resolve(webRoot, "src", "lib", "measurementContract.ts"); const contract = JSON.parse(fs.readFileSync(contractPath, "utf8")); const source = fs.readFileSync(tsPath, "utf8"); const mappings = { sourceKinds: "MEASUREMENT_SOURCE_KINDS", constructs: "MEASUREMENT_CONSTRUCTS", perspectives: "MEASUREMENT_PERSPECTIVES", measurementStatuses: "MEASUREMENT_STATUSES", instrumentKinds: "MEASUREMENT_INSTRUMENT_KINDS", aiViews: "MEASUREMENT_AI_VIEWS", modelRunStatuses: "MODEL_RUN_STATUSES", allianceDimensions: "ALLIANCE_DIMENSIONS", allianceCheckpoints: "ALLIANCE_CHECKPOINTS", }; function readTsArray(name) { const pattern = new RegExp(`export const ${name} = \\[([\\s\\S]*?)\\] as const;`); const match = source.match(pattern); if (!match) throw new Error(`missing TypeScript contract array: ${name}`); return Array.from(match[1].matchAll(/"([^"]+)"/g), (item) => item[1]); } for (const [jsonKey, tsName] of Object.entries(mappings)) { const expected = contract.enums[jsonKey]; const actual = readTsArray(tsName); if (JSON.stringify(actual) !== JSON.stringify(expected)) { throw new Error(`${tsName} drift: expected=${JSON.stringify(expected)} actual=${JSON.stringify(actual)}`); } } const eventRequired = contract.$defs.MeasurementEvent.required ?? []; for (const field of eventRequired) { if (!new RegExp(`\\b${field}[?]?:`).test(source)) { throw new Error(`MeasurementEvent TypeScript interface missing required field: ${field}`); } } console.log( JSON.stringify({ ok: true, schema: contract.$id, sourceKinds: contract.enums.sourceKinds.length, constructs: contract.enums.constructs.length, eventRequiredFields: eventRequired.length, }), );