1
0
mirror of synced 2026-01-22 00:01:39 -05:00
Files
docs/src/article-api/scripts/generate-api-docs.ts
2025-03-12 19:09:21 +00:00

175 lines
5.1 KiB
TypeScript

import { writeFileSync, readFileSync, existsSync } from 'fs'
function main({ sources, outputPath }: { sources: string[]; outputPath: string }): void {
// Extract API documentation comments from all source files
const allDocs = sources.flatMap((sourcePath) => extractApiDocs(sourcePath))
// Generate markdown
const markdown = generateMarkdown(allDocs)
// Update README
updateReadme(outputPath, markdown)
console.log('API documentation generated successfully!')
}
// Extract API docs from comments in the file
function extractApiDocs(file: string): string[] {
const apiDocs: any[] = []
// get the content from the api routes
const content = readFileSync(file, 'utf8')
// Get the router method definitions with JSDOC-style comments
const routeRegex =
/\/\*\*\s*([\s\S]*?)\s*\*\/\s*router\.(get|post|put|delete)\s*\(\s*['"]([^'"]*)['"]/g
let match
while ((match = routeRegex.exec(content)) !== null) {
const commentBlock = match[1]
const method = match[2]
const path = match[3]
// The description is first line of the comment
const description = commentBlock
.trim()
.split('\n')[0]
.trim()
.replace(/^\*\s*/, '')
// Grab the other elements from the comment
// we currently support: params, returns, examples, throws
const params = extractParams(commentBlock)
const returns = extractReturns(commentBlock)
const examples = extractExample(commentBlock)
const throws = extractThrows(commentBlock)
apiDocs.push({
method, // GET, POST, etc
path: file.includes('article.ts') ? `/api/article${path}` : `/api/pagelist${path}`, // Prepend base path
description, // defined in the top of the block comment
params, // defined from @params
returns, // defined from @returns
examples, // defined from @example
throws, // defined from @throws
})
}
return apiDocs
}
function extractThrows(commentBlock: string): string[] {
const throwsRegex = /@throws\s+{([^}]+)}\s+([^\n]+)/g
const throws: string[] = []
let throwsMatch
while ((throwsMatch = throwsRegex.exec(commentBlock)) !== null) {
const type = throwsMatch[1]
const desc = throwsMatch[2].trim()
throws.push(`- (${type}): ${desc}`)
}
return throws
}
// Extract parameters from comment block
function extractParams(commentBlock: string): string[] {
const paramRegex = /@param\s+{([^}]+)}\s+([^\s]+)\s+([^\n]+)/g
const params: string[] = []
let paramMatch
while ((paramMatch = paramRegex.exec(commentBlock)) !== null) {
const type = paramMatch[1]
const name = paramMatch[2]
const desc = paramMatch[3].trim()
params.push(`- **${name}** (${type}) ${desc}`)
}
return params
}
// Extract return info from comment block
function extractReturns(commentBlock: string): string {
const returnMatch = commentBlock.match(/@returns\s+{([^}]+)}\s+([^\n]+)/)
if (returnMatch) {
const type = returnMatch[1]
const desc = returnMatch[2].trim()
return `**Returns**: (${type}) - ${desc}`
}
return ''
}
// Extract example from comment block
function extractExample(commentBlock: string): string {
const exampleMatch = commentBlock.match(/@example\b([\s\S]*?)(?=\s*\*\s*@|\s*\*\/|$)/)
if (exampleMatch) {
// Clean up the example text by removing leading asterisks and spaces from each line, preserving tabs
return exampleMatch[1]
.split('\n')
.map((line) => line.replace(/^\s*\*\s?/, ''))
.join('\n')
.trim()
}
return ''
}
// Generate markdown from parsed documentation
function generateMarkdown(apiDocs: any[]): string {
let markdown = '## Reference: API endpoints\n\n'
apiDocs.forEach((doc) => {
markdown += `### ${doc.method.toUpperCase()} ${doc.path}\n\n`
markdown += `${doc.description}\n\n`
if (doc.params.length > 0) {
markdown += '**Parameters**:\n'
markdown += doc.params.join('\n')
markdown += '\n\n'
}
if (doc.returns) {
markdown += `${doc.returns}\n\n`
}
if (doc.throws.length > 0) {
markdown += '**Throws**:\n'
markdown += doc.throws.join('\n')
markdown += '\n\n'
}
if (doc.examples) {
markdown += `**Example**:\n\`\`\`\n${doc.examples}\n\`\`\`\n\n`
}
markdown += '---\n\n'
})
return markdown
}
// Update README with generated documentation
function updateReadme(readmePath: string, markdown: string): void {
if (existsSync(readmePath)) {
let readme = readFileSync(readmePath, 'utf8')
const placeholderComment = `<!-- API reference docs automatically generated, do not edit below this comment -->`
// Replace API documentation section, or append to end
if (readme.includes(placeholderComment)) {
const pattern = new RegExp(placeholderComment + '[\\s\\S]*', 'g')
readme = readme.replace(pattern, placeholderComment + '\n' + markdown)
} else {
readme += '\n' + markdown
}
writeFileSync(readmePath, readme)
} else {
writeFileSync(readmePath, markdown)
}
}
main({
sources: ['src/article-api/middleware/article.ts', 'src/article-api/middleware/pagelist.ts'],
outputPath: 'src/article-api/README.md',
})