// Generate frontend/src/types/contracts.ts from shared/schemas/*.json (contract source of truth). import { compile } from 'json-schema-to-typescript'; import { readFileSync, readdirSync, writeFileSync } from 'node:fs'; import { resolve, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; const here = dirname(fileURLToPath(import.meta.url)); const schemasDir = resolve(here, '../../shared/schemas'); const outFile = resolve(here, '../src/types/contracts.ts'); const files = readdirSync(schemasDir).filter((f) => f.endsWith('.json')).sort(); const docs = Object.fromEntries(files.map((f) => [f, JSON.parse(readFileSync(resolve(schemasDir, f), 'utf8'))])); const byId = Object.fromEntries(Object.values(docs).map((d) => [d.$id, d])); const opts = { bannerComment: '', cwd: schemasDir, declareExternallyReferenced: true, additionalProperties: false, $refOptions: { resolve: { file: { read: (file) => readFileSync(file.url, 'utf8') }, local: { order: 1, canRead: /^https:\/\/blindspot\.local\/schemas\//, read: (file) => JSON.stringify(byId[file.url.split('#')[0]] ?? docs[file.url.split('/').pop()]), }, }, }, }; // Units to compile: every top-level schema, plus each $def of api.json as its own named type. const units = []; for (const f of files) { const doc = docs[f]; if (f === 'api.json') { for (const [name, def] of Object.entries(doc.$defs)) { units.push({ title: name, schema: { ...def, title: name, $defs: doc.$defs, $id: `${doc.$id}#${name}` } }); } } else { units.push({ title: doc.title, schema: doc }); } } const seen = new Map(); const order = []; for (const u of units) { const ts = await compile(u.schema, u.title, opts); // Split into declaration blocks; keep the first declaration of each name. const blocks = ts.split(/\n(?=(?:\/\*\*[\s\S]*?\*\/\n)?export )/); for (const raw of blocks) { const block = raw.trim(); const m = block.match(/export (?:interface|type) (\w+)/); if (!m) continue; const name = m[1]; if (seen.has(name)) continue; seen.set(name, block); order.push(name); } } let out = '/* eslint-disable */\n/* AUTO-GENERATED by scripts/gen-types.mjs from shared/schemas — do not edit. Run `npm run gen:types`. */\n\n'; out += order.map((n) => seen.get(n)).join('\n\n') + '\n'; writeFileSync(outFile, out); console.log(`wrote ${outFile} (${order.length} types)`);