124 lines
3.4 KiB
JavaScript
124 lines
3.4 KiB
JavaScript
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();
|
|
}
|