import { readFileSync } from 'node:fs' import { join } from 'node:path' import { createOpenAPI } from 'fumadocs-openapi/server' export const openapi = createOpenAPI({ input: ['./openapi.json'], }) interface OpenAPIOperation { path: string method: string } function resolveRef(ref: string, spec: Record): unknown { const parts = ref.replace('#/', '').split('/') let current: unknown = spec for (const part of parts) { if (current && typeof current === 'object') { current = (current as Record)[part] } else { return undefined } } return current } function resolveRefs(obj: unknown, spec: Record, depth = 0): unknown { if (depth > 10) return obj if (Array.isArray(obj)) { return obj.map((item) => resolveRefs(item, spec, depth + 1)) } if (obj && typeof obj === 'object') { const record = obj as Record if ('$ref' in record && typeof record.$ref === 'string') { const resolved = resolveRef(record.$ref, spec) return resolveRefs(resolved, spec, depth + 1) } const result: Record = {} for (const [key, value] of Object.entries(record)) { result[key] = resolveRefs(value, spec, depth + 1) } return result } return obj } function formatSchema(schema: unknown): string { return JSON.stringify(schema, null, 2) } let cachedSpec: Record | null = null function getSpec(): Record { if (!cachedSpec) { const specPath = join(process.cwd(), 'openapi.json') cachedSpec = JSON.parse(readFileSync(specPath, 'utf8')) as Record } return cachedSpec } export function getApiSpecContent( title: string, description: string | undefined, operations: OpenAPIOperation[] ): string { const spec = getSpec() if (!operations || operations.length === 0) { return `# ${title}\n\n${description || ''}` } const op = operations[0] const method = op.method.toUpperCase() const pathObj = (spec.paths as Record>)?.[op.path] const operation = pathObj?.[op.method.toLowerCase()] as Record | undefined if (!operation) { return `# ${title}\n\n${description || ''}` } const resolved = resolveRefs(operation, spec) as Record const lines: string[] = [] lines.push(`# ${title}`) lines.push(`\`${method} ${op.path}\``) if (resolved.description) { lines.push(`## Description\n${resolved.description}`) } const parameters = resolved.parameters as Array> | undefined if (parameters && parameters.length > 0) { lines.push('## Parameters') for (const param of parameters) { const required = param.required ? ' (required)' : '' const schemaType = param.schema ? ` — \`${(param.schema as Record).type || 'string'}\`` : '' lines.push( `- **${param.name}** (${param.in})${required}${schemaType}: ${param.description || ''}` ) } } const requestBody = resolved.requestBody as Record | undefined if (requestBody) { lines.push('## Request Body') if (requestBody.description) { lines.push(String(requestBody.description)) } const content = requestBody.content as Record> | undefined const jsonContent = content?.['application/json'] if (jsonContent?.schema) { lines.push(`\`\`\`json\n${formatSchema(jsonContent.schema)}\n\`\`\``) } } const responses = resolved.responses as Record> | undefined if (responses) { lines.push('## Responses') for (const [status, response] of Object.entries(responses)) { lines.push(`### ${status} — ${response.description || ''}`) const content = response.content as Record> | undefined const jsonContent = content?.['application/json'] if (jsonContent?.schema) { lines.push(`\`\`\`json\n${formatSchema(jsonContent.schema)}\n\`\`\``) } } } return lines.join('\n\n') }