u
This commit is contained in:
@@ -393,7 +393,7 @@ function renderReadme(operations) {
|
||||
lines.push(`| [\`routes/${op.file}\`](routes/${op.file}) | \`${op.method.toUpperCase()} ${op.path}\` |`);
|
||||
}
|
||||
lines.push('');
|
||||
lines.push('Machine-readable spec: [`openapi.json`](openapi.json).');
|
||||
lines.push('Route overview: [`routes.md`](routes.md).');
|
||||
lines.push('');
|
||||
lines.push('## Signals (SSE)');
|
||||
lines.push('');
|
||||
@@ -436,13 +436,127 @@ function renderReadme(operations) {
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function summarizeSchemaCompact(schema) {
|
||||
if (!schema) return '';
|
||||
if (schema.$ref) return schemaRefName(schema);
|
||||
if (schema.type === 'array') {
|
||||
const item = summarizeSchemaCompact(schema.items) || 'any';
|
||||
return `${item}[]`;
|
||||
}
|
||||
if (schema.type === 'object' || schema.properties) {
|
||||
const props = Object.keys(schema.properties || {});
|
||||
if (props.length === 0) return 'object';
|
||||
if (props.length === 1) {
|
||||
const k = props[0];
|
||||
const v = summarizeSchemaCompact(schema.properties[k]);
|
||||
return v ? `{ ${k}: ${v} }` : `{ ${k} }`;
|
||||
}
|
||||
if (props.length <= 3) return `{ ${props.join(', ')} }`;
|
||||
return `{ ${props.slice(0, 3).join(', ')}, … }`;
|
||||
}
|
||||
if (schema.type) return schema.type;
|
||||
return '';
|
||||
}
|
||||
|
||||
function summarizeOperationParams(operation) {
|
||||
const parts = [];
|
||||
const params = operation.parameters || [];
|
||||
for (const p of params) {
|
||||
if (p.in === 'header') continue;
|
||||
const prefix = p.in === 'query' ? '?' : '';
|
||||
const type = p.schema?.type ? `:${p.schema.type}` : '';
|
||||
parts.push(`${prefix}${p.name}${type}`);
|
||||
}
|
||||
|
||||
if (operation.requestBody?.content) {
|
||||
const content = operation.requestBody.content;
|
||||
if (content['application/json']?.schema) {
|
||||
const s = content['application/json'].schema;
|
||||
const ref = schemaRefName(s);
|
||||
if (ref) {
|
||||
parts.push(`body: ${ref}`);
|
||||
} else {
|
||||
const sub = summarizeSchemaCompact(s);
|
||||
parts.push(sub ? `body: ${sub}` : 'body: json');
|
||||
}
|
||||
} else if (content['multipart/form-data']) {
|
||||
const props = Object.keys(content['multipart/form-data'].schema?.properties || {});
|
||||
parts.push(props.length ? `form: { ${props.join(', ')} }` : 'form-data');
|
||||
} else {
|
||||
const ct = Object.keys(content)[0];
|
||||
parts.push(ct);
|
||||
}
|
||||
}
|
||||
|
||||
return parts.length ? parts.join(', ') : '—';
|
||||
}
|
||||
|
||||
function summarizeOperationReturns(operation) {
|
||||
const responses = operation.responses || {};
|
||||
const successCodes = Object.keys(responses)
|
||||
.filter((c) => c.startsWith('2'))
|
||||
.sort();
|
||||
if (!successCodes.length) return '—';
|
||||
|
||||
const parts = [];
|
||||
for (const code of successCodes) {
|
||||
const resp = responses[code];
|
||||
if (code === '204') {
|
||||
parts.push('204 void');
|
||||
continue;
|
||||
}
|
||||
if (!resp?.content) {
|
||||
parts.push(code);
|
||||
continue;
|
||||
}
|
||||
const ct = Object.keys(resp.content)[0];
|
||||
if (ct === 'text/event-stream') {
|
||||
parts.push(`${code} SSE`);
|
||||
continue;
|
||||
}
|
||||
if (ct.startsWith('image/')) {
|
||||
parts.push(`${code} ${ct.split('/')[1]}`);
|
||||
continue;
|
||||
}
|
||||
if (ct === 'application/zip') {
|
||||
parts.push(`${code} zip`);
|
||||
continue;
|
||||
}
|
||||
const s = resp.content[ct]?.schema;
|
||||
const sub = summarizeSchemaCompact(s);
|
||||
if (sub) {
|
||||
parts.push(`${code} ${sub}`);
|
||||
} else {
|
||||
parts.push(code);
|
||||
}
|
||||
}
|
||||
return parts.join(', ');
|
||||
}
|
||||
|
||||
function renderRoutesOverview(operations) {
|
||||
const lines = [];
|
||||
lines.push('# Dash API Routes Overview');
|
||||
lines.push('');
|
||||
lines.push(`Overview of all ${operations.length} API endpoints across all modules.`);
|
||||
lines.push('');
|
||||
lines.push('| Method | Route | Params | Return Type | Summary | Docs |');
|
||||
lines.push('|:---|:---|:---|:---|:---|:---|');
|
||||
for (const op of operations) {
|
||||
const method = op.method.toUpperCase();
|
||||
const summary = (op.summary || '').replace(/[\r\n|]+/g, ' ').trim();
|
||||
const paramsStr = op.params === '—' ? '—' : `\`${op.params.replace(/\|/g, '\\|')}\``;
|
||||
const returnsStr = op.returns === '—' ? '—' : `\`${op.returns.replace(/\|/g, '\\|')}\``;
|
||||
lines.push(`| \`${method}\` | \`${op.path}\` | ${paramsStr} | ${returnsStr} | ${summary} | [\`routes/${op.file}\`](routes/${op.file}) |`);
|
||||
}
|
||||
lines.push('');
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
// --- generate ---
|
||||
ensureDir(docsDir);
|
||||
rmDirContents(routesDocsDir);
|
||||
ensureDir(routesDocsDir);
|
||||
|
||||
fs.writeFileSync(path.join(docsDir, 'openapi.json'), JSON.stringify(spec, null, 2) + '\n');
|
||||
|
||||
const operations = [];
|
||||
const paths = Object.keys(spec.paths || {}).sort();
|
||||
|
||||
@@ -455,10 +569,19 @@ for (const apiPath of paths) {
|
||||
const absFile = path.join(routesDocsDir, relFile);
|
||||
ensureDir(path.dirname(absFile));
|
||||
fs.writeFileSync(absFile, renderOperation(apiPath, method, operation));
|
||||
operations.push({ path: apiPath, method, file: relFile.replace(/\\/g, '/') });
|
||||
operations.push({
|
||||
path: apiPath,
|
||||
method,
|
||||
file: relFile.replace(/\\/g, '/'),
|
||||
summary: operation.summary || '',
|
||||
tags: operation.tags || [],
|
||||
params: summarizeOperationParams(operation),
|
||||
returns: summarizeOperationReturns(operation),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.join(docsDir, 'routes.md'), renderRoutesOverview(operations));
|
||||
fs.writeFileSync(path.join(docsDir, 'README.md'), renderReadme(operations));
|
||||
|
||||
console.log(`Wrote ${operations.length} route docs + openapi.json → docs/api/`);
|
||||
console.log(`Wrote ${operations.length} route docs + routes.md → docs/api/`);
|
||||
|
||||
Reference in New Issue
Block a user