/** * Generate mock route files from an OpenAPI 3 spec (json or yaml). * * npm run openapi -- path/to/openapi.yaml [outDir] * * For each path × method it writes a route file whose body is the response * example (or a synthesized one from the schema). Non-200 success codes land * in the filename (e.g. users.post.201.json). */ import { readFileSync, mkdirSync, writeFileSync } from 'node:fs' import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' import YAML from 'yaml' const __dirname = dirname(fileURLToPath(import.meta.url)) const specPath = process.argv[2] const outDir = process.argv[3] ?? join(__dirname, '..', 'routes') if (!specPath) { console.error('usage: npm run openapi -- [outDir]') process.exit(1) } const text = readFileSync(specPath, 'utf8') const spec: any = specPath.endsWith('.json') ? JSON.parse(text) : YAML.parse(text) function resolveRef(ref: string): any { // #/components/schemas/Foo const parts = ref.replace(/^#\//, '').split('/') return parts.reduce((o, k) => o?.[k], spec) } function example(schema: any, depth = 0): any { if (!schema || depth > 6) return null if (schema.$ref) return example(resolveRef(schema.$ref), depth + 1) if (schema.example !== undefined) return schema.example if (schema.default !== undefined) return schema.default if (schema.enum) return schema.enum[0] if (schema.allOf) return Object.assign({}, ...schema.allOf.map((s: any) => example(s, depth + 1))) if (schema.oneOf || schema.anyOf) return example((schema.oneOf ?? schema.anyOf)[0], depth + 1) switch (schema.type) { case 'object': { const out: Record = {} for (const [k, v] of Object.entries(schema.properties ?? {})) out[k] = example(v, depth + 1) return out } case 'array': return [example(schema.items, depth + 1)] case 'integer': case 'number': return schema.format === 'float' ? 1.5 : 1 case 'boolean': return true case 'string': if (schema.format === 'date-time') return '2026-01-01T00:00:00Z' if (schema.format === 'email') return 'user@example.com' if (schema.format === 'uuid') return '00000000-0000-0000-0000-000000000000' return 'string' default: return schema.properties ? example({ ...schema, type: 'object' }, depth) : null } } function pickResponse(op: any): { status: number; body: unknown } | null { const responses = op.responses ?? {} const code = Object.keys(responses).find((c) => /^2\d\d$/.test(c)) ?? Object.keys(responses)[0] if (!code) return null const r = responses[code] const json = r?.content?.['application/json'] let body: unknown = {} if (json?.example !== undefined) body = json.example else if (json?.examples) body = (Object.values(json.examples)[0] as any)?.value ?? {} else if (json?.schema) body = example(json.schema) return { status: Number(code) || 200, body } } function fileFor(path: string, method: string, status: number): string { // /users/{id} -> users/[id] const segs = path.split('/').filter(Boolean).map((s) => s.replace(/^\{(.+)\}$/, '[$1]')) const base = segs.length ? segs.join('/') : 'index' const parts = [base] if (method !== 'get') parts.push(method) if (status !== 200 && status !== 0) parts.push(String(status)) return join(outDir, parts.join('.') + '.json') } let count = 0 for (const [path, item] of Object.entries(spec.paths ?? {})) { for (const method of ['get', 'post', 'put', 'patch', 'delete']) { const op = item[method] if (!op) continue const picked = pickResponse(op) if (!picked) continue const file = fileFor(path, method, picked.status) mkdirSync(dirname(file), { recursive: true }) writeFileSync(file, JSON.stringify(picked.body, null, 2) + '\n') count++ console.log(`+ ${method.toUpperCase().padEnd(6)} ${path} -> ${file.replace(outDir + '/', '')}`) } } console.log(`\n${count} route(s) generated into ${outDir}`)