blindspot / frontend /scripts /gen-types.mjs
yashwrites's picture
Blindspot: initial Space deploy
54f0281 verified
Raw History Blame Contribute Delete
2.44 kB
// 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)`);