import { mkdir, readFile, writeFile } from 'node:fs/promises'; import { dirname, resolve } from 'node:path'; import process from 'node:process'; import { fileURLToPath, pathToFileURL } from 'node:url'; const scriptDir = dirname(fileURLToPath(import.meta.url)); const webRoot = resolve(scriptDir, '..'); const repoRoot = resolve(webRoot, '..'); const inputPath = resolve(repoRoot, 'ErsatzTV/wwwroot/openapi/v1.json'); const outputPath = resolve(webRoot, 'src/api/generated/v1.d.ts'); function schemaNameFromRef(ref) { return ref.replace('#/components/schemas/', ''); } function formatDescription(description, indent = '') { if (!description) { return ''; } return `${indent}/** ${String(description).replaceAll('*/', '* /')} */\n`; } function typeFromSchema(schema) { if (!schema) { return 'unknown'; } if (schema.$ref) { return `components["schemas"]["${schemaNameFromRef(schema.$ref)}"]`; } if (schema.oneOf || schema.anyOf) { return (schema.oneOf ?? schema.anyOf).map(typeFromSchema).join(' | '); } if (schema.allOf) { return schema.allOf.map(typeFromSchema).join(' & '); } if (Array.isArray(schema.enum)) { return schema.enum.map((value) => JSON.stringify(value)).join(' | ') || 'never'; } if (Array.isArray(schema.type)) { return schema.type.map((type) => typeFromSchema({ ...schema, type })).join(' | '); } switch (schema.type) { case 'array': return `Array<${typeFromSchema(schema.items)}>`; case 'boolean': return 'boolean'; case 'integer': case 'number': return 'number'; case 'null': return 'null'; case 'object': return objectTypeFromSchema(schema); case 'string': return 'string'; default: if (schema.properties || schema.additionalProperties) { return objectTypeFromSchema(schema); } return 'unknown'; } } function objectTypeFromSchema(schema) { const properties = schema.properties ?? {}; const required = new Set(schema.required ?? []); const lines = ['{']; for (const [name, propertySchema] of Object.entries(properties)) { const optional = required.has(name) ? '' : '?'; lines.push(`${formatDescription(propertySchema.description, ' ')} ${JSON.stringify(name)}${optional}: ${typeFromSchema(propertySchema)};`); } if (schema.additionalProperties) { const valueType = schema.additionalProperties === true ? 'unknown' : typeFromSchema(schema.additionalProperties); lines.push(` [key: string]: ${valueType};`); } lines.push(' }'); return lines.join('\n'); } export function generateTypes(document) { const schemas = document.components?.schemas ?? {}; const lines = [ '// This file is generated by web/scripts/generate-openapi-types.mjs.', '// Do not edit by hand.', '', 'export interface components {', ' schemas: {' ]; for (const [name, schema] of Object.entries(schemas).sort(([a], [b]) => a.localeCompare(b))) { lines.push(`${formatDescription(schema.description, ' ')} ${JSON.stringify(name)}: ${typeFromSchema(schema)};`); } lines.push(' };'); lines.push('}'); lines.push(''); return `${lines.join('\n')}\n`; } async function main() { const document = JSON.parse(await readFile(inputPath, 'utf8')); await mkdir(dirname(outputPath), { recursive: true }); await writeFile(outputPath, generateTypes(document)); } if (import.meta.url === pathToFileURL(process.argv[1]).href) { await main(); }