mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-11 02:33:51 +08:00
- Implemented main render function for the issue discovery view. - Added data loading functions to fetch discoveries, details, findings, and progress. - Created rendering functions for discovery list and detail sections. - Introduced filtering and searching capabilities for findings. - Implemented actions for exporting and dismissing findings. - Added polling mechanism to track discovery progress. - Included utility functions for HTML escaping and cleanup.
420 lines
9.0 KiB
Markdown
420 lines
9.0 KiB
Markdown
# Swagger/OpenAPI Runner
|
|
|
|
Guide for generating backend API documentation from OpenAPI/Swagger specifications.
|
|
|
|
## Overview
|
|
|
|
This script extracts and converts OpenAPI/Swagger specifications to Markdown format for inclusion in the software manual.
|
|
|
|
## Detection Strategy
|
|
|
|
### Check for Existing Specification
|
|
|
|
```javascript
|
|
async function detectOpenAPISpec() {
|
|
// Check for existing spec files
|
|
const specPatterns = [
|
|
'openapi.json',
|
|
'openapi.yaml',
|
|
'openapi.yml',
|
|
'swagger.json',
|
|
'swagger.yaml',
|
|
'swagger.yml',
|
|
'**/openapi*.json',
|
|
'**/swagger*.json'
|
|
];
|
|
|
|
for (const pattern of specPatterns) {
|
|
const files = Glob(pattern);
|
|
if (files.length > 0) {
|
|
return { found: true, type: 'file', path: files[0] };
|
|
}
|
|
}
|
|
|
|
// Check for swagger-jsdoc in dependencies
|
|
const packageJson = JSON.parse(Read('package.json'));
|
|
if (packageJson.dependencies?.['swagger-jsdoc'] ||
|
|
packageJson.devDependencies?.['swagger-jsdoc']) {
|
|
return { found: true, type: 'jsdoc' };
|
|
}
|
|
|
|
// Check for NestJS Swagger
|
|
if (packageJson.dependencies?.['@nestjs/swagger']) {
|
|
return { found: true, type: 'nestjs' };
|
|
}
|
|
|
|
// Check for runtime endpoint
|
|
return { found: false, suggestion: 'runtime' };
|
|
}
|
|
```
|
|
|
|
## Extraction Methods
|
|
|
|
### Method A: From Existing Spec File
|
|
|
|
```javascript
|
|
async function extractFromFile(specPath, workDir) {
|
|
const outputDir = `${workDir}/api-docs/backend`;
|
|
Bash({ command: `mkdir -p "${outputDir}"` });
|
|
|
|
// Copy spec to output
|
|
Bash({ command: `cp "${specPath}" "${outputDir}/openapi.json"` });
|
|
|
|
// Convert to Markdown using widdershins
|
|
const result = Bash({
|
|
command: `npx widdershins "${specPath}" -o "${outputDir}/api-reference.md" --language_tabs 'javascript:JavaScript' 'python:Python' 'bash:cURL'`,
|
|
timeout: 60000
|
|
});
|
|
|
|
return { success: result.exitCode === 0, outputDir };
|
|
}
|
|
```
|
|
|
|
### Method B: From swagger-jsdoc
|
|
|
|
```javascript
|
|
async function extractFromJsDoc(workDir) {
|
|
const outputDir = `${workDir}/api-docs/backend`;
|
|
|
|
// Look for swagger definition file
|
|
const defFiles = Glob('**/swagger*.js').concat(Glob('**/openapi*.js'));
|
|
if (defFiles.length === 0) {
|
|
return { success: false, error: 'No swagger definition found' };
|
|
}
|
|
|
|
// Generate spec
|
|
const result = Bash({
|
|
command: `npx swagger-jsdoc -d "${defFiles[0]}" -o "${outputDir}/openapi.json"`,
|
|
timeout: 60000
|
|
});
|
|
|
|
if (result.exitCode !== 0) {
|
|
return { success: false, error: result.stderr };
|
|
}
|
|
|
|
// Convert to Markdown
|
|
Bash({
|
|
command: `npx widdershins "${outputDir}/openapi.json" -o "${outputDir}/api-reference.md" --language_tabs 'javascript:JavaScript' 'bash:cURL'`
|
|
});
|
|
|
|
return { success: true, outputDir };
|
|
}
|
|
```
|
|
|
|
### Method C: From NestJS Swagger
|
|
|
|
```javascript
|
|
async function extractFromNestJS(workDir) {
|
|
const outputDir = `${workDir}/api-docs/backend`;
|
|
|
|
// NestJS typically exposes /api-docs-json at runtime
|
|
// We need to start the server temporarily
|
|
|
|
// Start server in background
|
|
const server = Bash({
|
|
command: 'npm run start:dev',
|
|
run_in_background: true,
|
|
timeout: 30000
|
|
});
|
|
|
|
// Wait for server to be ready
|
|
await waitForServer('http://localhost:3000', 30000);
|
|
|
|
// Fetch OpenAPI spec
|
|
const spec = await fetch('http://localhost:3000/api-docs-json');
|
|
const specJson = await spec.json();
|
|
|
|
// Save spec
|
|
Write(`${outputDir}/openapi.json`, JSON.stringify(specJson, null, 2));
|
|
|
|
// Stop server
|
|
KillShell({ shell_id: server.task_id });
|
|
|
|
// Convert to Markdown
|
|
Bash({
|
|
command: `npx widdershins "${outputDir}/openapi.json" -o "${outputDir}/api-reference.md" --language_tabs 'javascript:JavaScript' 'bash:cURL'`
|
|
});
|
|
|
|
return { success: true, outputDir };
|
|
}
|
|
```
|
|
|
|
### Method D: From Runtime Endpoint
|
|
|
|
```javascript
|
|
async function extractFromRuntime(workDir, serverUrl = 'http://localhost:3000') {
|
|
const outputDir = `${workDir}/api-docs/backend`;
|
|
|
|
// Common OpenAPI endpoint paths
|
|
const endpointPaths = [
|
|
'/api-docs-json',
|
|
'/swagger.json',
|
|
'/openapi.json',
|
|
'/docs/json',
|
|
'/api/v1/docs.json'
|
|
];
|
|
|
|
let specJson = null;
|
|
|
|
for (const path of endpointPaths) {
|
|
try {
|
|
const response = await fetch(`${serverUrl}${path}`);
|
|
if (response.ok) {
|
|
specJson = await response.json();
|
|
break;
|
|
}
|
|
} catch (e) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!specJson) {
|
|
return { success: false, error: 'Could not fetch OpenAPI spec from server' };
|
|
}
|
|
|
|
// Save and convert
|
|
Write(`${outputDir}/openapi.json`, JSON.stringify(specJson, null, 2));
|
|
|
|
Bash({
|
|
command: `npx widdershins "${outputDir}/openapi.json" -o "${outputDir}/api-reference.md"`
|
|
});
|
|
|
|
return { success: true, outputDir };
|
|
}
|
|
```
|
|
|
|
## Installation
|
|
|
|
### Required Tools
|
|
|
|
```bash
|
|
# For OpenAPI to Markdown conversion
|
|
npm install -g widdershins
|
|
|
|
# Or as dev dependency
|
|
npm install --save-dev widdershins
|
|
|
|
# For generating from JSDoc comments
|
|
npm install --save-dev swagger-jsdoc
|
|
```
|
|
|
|
## Configuration
|
|
|
|
### widdershins Options
|
|
|
|
```bash
|
|
npx widdershins openapi.json \
|
|
-o api-reference.md \
|
|
--language_tabs 'javascript:JavaScript' 'python:Python' 'bash:cURL' \
|
|
--summary \
|
|
--omitHeader \
|
|
--resolve \
|
|
--expandBody
|
|
```
|
|
|
|
| Option | Description |
|
|
|--------|-------------|
|
|
| `--language_tabs` | Code example languages |
|
|
| `--summary` | Use summary as operation heading |
|
|
| `--omitHeader` | Don't include title header |
|
|
| `--resolve` | Resolve $ref references |
|
|
| `--expandBody` | Show full request body |
|
|
|
|
### swagger-jsdoc Definition
|
|
|
|
Example `swagger-def.js`:
|
|
|
|
```javascript
|
|
module.exports = {
|
|
definition: {
|
|
openapi: '3.0.0',
|
|
info: {
|
|
title: 'MyApp API',
|
|
version: '1.0.0',
|
|
description: 'API documentation for MyApp'
|
|
},
|
|
servers: [
|
|
{ url: 'http://localhost:3000/api/v1' }
|
|
]
|
|
},
|
|
apis: ['./src/routes/*.js', './src/controllers/*.js']
|
|
};
|
|
```
|
|
|
|
## Output Format
|
|
|
|
### Generated Markdown Structure
|
|
|
|
```markdown
|
|
# MyApp API
|
|
|
|
## Overview
|
|
|
|
Base URL: `http://localhost:3000/api/v1`
|
|
|
|
## Authentication
|
|
|
|
This API uses Bearer token authentication.
|
|
|
|
---
|
|
|
|
## Projects
|
|
|
|
### List Projects
|
|
|
|
`GET /projects`
|
|
|
|
Returns a list of all projects.
|
|
|
|
**Parameters**
|
|
|
|
| Name | In | Type | Required | Description |
|
|
|------|-----|------|----------|-------------|
|
|
| status | query | string | false | Filter by status |
|
|
| page | query | integer | false | Page number |
|
|
|
|
**Responses**
|
|
|
|
| Status | Description |
|
|
|--------|-------------|
|
|
| 200 | Successful response |
|
|
| 401 | Unauthorized |
|
|
|
|
**Example Request**
|
|
|
|
```javascript
|
|
fetch('/api/v1/projects?status=active')
|
|
.then(res => res.json())
|
|
.then(data => console.log(data));
|
|
```
|
|
|
|
**Example Response**
|
|
|
|
```json
|
|
{
|
|
"data": [
|
|
{ "id": "1", "name": "Project 1" }
|
|
],
|
|
"pagination": {
|
|
"page": 1,
|
|
"total": 10
|
|
}
|
|
}
|
|
```
|
|
```
|
|
|
|
## Integration
|
|
|
|
### Main Runner
|
|
|
|
```javascript
|
|
async function runSwaggerExtraction(workDir) {
|
|
const detection = await detectOpenAPISpec();
|
|
|
|
if (!detection.found) {
|
|
console.log('No OpenAPI spec detected. Skipping backend API docs.');
|
|
return { success: false, skipped: true };
|
|
}
|
|
|
|
let result;
|
|
|
|
switch (detection.type) {
|
|
case 'file':
|
|
result = await extractFromFile(detection.path, workDir);
|
|
break;
|
|
case 'jsdoc':
|
|
result = await extractFromJsDoc(workDir);
|
|
break;
|
|
case 'nestjs':
|
|
result = await extractFromNestJS(workDir);
|
|
break;
|
|
default:
|
|
result = await extractFromRuntime(workDir);
|
|
}
|
|
|
|
if (result.success) {
|
|
// Post-process the Markdown
|
|
await postProcessApiDocs(result.outputDir);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
async function postProcessApiDocs(outputDir) {
|
|
const mdFile = `${outputDir}/api-reference.md`;
|
|
let content = Read(mdFile);
|
|
|
|
// Remove widdershins header
|
|
content = content.replace(/^---[\s\S]*?---\n/, '');
|
|
|
|
// Add custom styling hints
|
|
content = content.replace(/^(#{1,3} .+)$/gm, '$1\n');
|
|
|
|
Write(mdFile, content);
|
|
}
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
#### "widdershins: command not found"
|
|
|
|
```bash
|
|
npm install -g widdershins
|
|
# Or use npx
|
|
npx widdershins openapi.json -o api.md
|
|
```
|
|
|
|
#### "Error parsing OpenAPI spec"
|
|
|
|
```bash
|
|
# Validate spec first
|
|
npx @redocly/cli lint openapi.json
|
|
|
|
# Fix common issues
|
|
npx @redocly/cli bundle openapi.json -o fixed.json
|
|
```
|
|
|
|
#### "Server not responding"
|
|
|
|
Ensure the development server is running and accessible:
|
|
|
|
```bash
|
|
# Check if server is running
|
|
curl http://localhost:3000/health
|
|
|
|
# Check OpenAPI endpoint
|
|
curl http://localhost:3000/api-docs-json
|
|
```
|
|
|
|
### Manual Fallback
|
|
|
|
If automatic extraction fails, document APIs manually:
|
|
|
|
1. List all route files: `Glob('**/routes/*.js')`
|
|
2. Extract route definitions using regex
|
|
3. Build documentation structure manually
|
|
|
|
```javascript
|
|
async function manualApiExtraction(workDir) {
|
|
const routeFiles = Glob('src/routes/*.js').concat(Glob('src/routes/*.ts'));
|
|
const endpoints = [];
|
|
|
|
for (const file of routeFiles) {
|
|
const content = Read(file);
|
|
const routes = content.matchAll(/router\.(get|post|put|delete|patch)\(['"]([^'"]+)['"]/g);
|
|
|
|
for (const match of routes) {
|
|
endpoints.push({
|
|
method: match[1].toUpperCase(),
|
|
path: match[2],
|
|
file: file
|
|
});
|
|
}
|
|
}
|
|
|
|
return endpoints;
|
|
}
|
|
```
|