mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-12 02:37:45 +08:00
feat: add issue discovery view for managing discovery sessions and findings
- 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.
This commit is contained in:
447
.claude/skills/software-manual/scripts/screenshot-helper.md
Normal file
447
.claude/skills/software-manual/scripts/screenshot-helper.md
Normal file
@@ -0,0 +1,447 @@
|
||||
# Screenshot Helper
|
||||
|
||||
Guide for capturing screenshots using Chrome MCP.
|
||||
|
||||
## Overview
|
||||
|
||||
This script helps capture screenshots of web interfaces for the software manual using Chrome MCP or fallback methods.
|
||||
|
||||
## Chrome MCP Prerequisites
|
||||
|
||||
### Check MCP Availability
|
||||
|
||||
```javascript
|
||||
async function checkChromeMCPAvailability() {
|
||||
try {
|
||||
// Attempt to get Chrome version via MCP
|
||||
const version = await mcp__chrome__getVersion();
|
||||
return {
|
||||
available: true,
|
||||
browser: version.browser,
|
||||
version: version.version
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
available: false,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### MCP Configuration
|
||||
|
||||
Expected Claude configuration for Chrome MCP:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"chrome": {
|
||||
"command": "npx",
|
||||
"args": ["@anthropic-ai/mcp-chrome"],
|
||||
"env": {
|
||||
"CHROME_PATH": "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Screenshot Workflow
|
||||
|
||||
### Step 1: Prepare Environment
|
||||
|
||||
```javascript
|
||||
async function prepareScreenshotEnvironment(workDir, config) {
|
||||
const screenshotDir = `${workDir}/screenshots`;
|
||||
|
||||
// Create directory
|
||||
Bash({ command: `mkdir -p "${screenshotDir}"` });
|
||||
|
||||
// Check Chrome MCP
|
||||
const chromeMCP = await checkChromeMCPAvailability();
|
||||
|
||||
if (!chromeMCP.available) {
|
||||
console.log('Chrome MCP not available. Will generate manual guide.');
|
||||
return { mode: 'manual' };
|
||||
}
|
||||
|
||||
// Start development server if needed
|
||||
if (config.screenshot_config?.dev_command) {
|
||||
const server = await startDevServer(config);
|
||||
return { mode: 'auto', server, screenshotDir };
|
||||
}
|
||||
|
||||
return { mode: 'auto', screenshotDir };
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Start Development Server
|
||||
|
||||
```javascript
|
||||
async function startDevServer(config) {
|
||||
const devCommand = config.screenshot_config.dev_command;
|
||||
const devUrl = config.screenshot_config.dev_url;
|
||||
|
||||
// Start server in background
|
||||
const server = Bash({
|
||||
command: devCommand,
|
||||
run_in_background: true
|
||||
});
|
||||
|
||||
console.log(`Starting dev server: ${devCommand}`);
|
||||
|
||||
// Wait for server to be ready
|
||||
const ready = await waitForServer(devUrl, 30000);
|
||||
|
||||
if (!ready) {
|
||||
throw new Error(`Server at ${devUrl} did not start in time`);
|
||||
}
|
||||
|
||||
console.log(`Dev server ready at ${devUrl}`);
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
async function waitForServer(url, timeout = 30000) {
|
||||
const start = Date.now();
|
||||
|
||||
while (Date.now() - start < timeout) {
|
||||
try {
|
||||
const response = await fetch(url, { method: 'HEAD' });
|
||||
if (response.ok) return true;
|
||||
} catch (e) {
|
||||
// Server not ready
|
||||
}
|
||||
await sleep(1000);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Capture Screenshots
|
||||
|
||||
```javascript
|
||||
async function captureScreenshots(screenshots, config, workDir) {
|
||||
const results = {
|
||||
captured: [],
|
||||
failed: []
|
||||
};
|
||||
|
||||
const devUrl = config.screenshot_config.dev_url;
|
||||
const screenshotDir = `${workDir}/screenshots`;
|
||||
|
||||
for (const ss of screenshots) {
|
||||
try {
|
||||
// Build full URL
|
||||
const fullUrl = new URL(ss.url, devUrl).href;
|
||||
|
||||
console.log(`Capturing: ${ss.id} (${fullUrl})`);
|
||||
|
||||
// Configure capture options
|
||||
const options = {
|
||||
url: fullUrl,
|
||||
viewport: { width: 1280, height: 800 },
|
||||
fullPage: ss.fullPage || false
|
||||
};
|
||||
|
||||
// Wait for specific element if specified
|
||||
if (ss.wait_for) {
|
||||
options.waitFor = ss.wait_for;
|
||||
}
|
||||
|
||||
// Capture specific element if selector provided
|
||||
if (ss.selector) {
|
||||
options.selector = ss.selector;
|
||||
}
|
||||
|
||||
// Add delay for animations
|
||||
await sleep(500);
|
||||
|
||||
// Capture via Chrome MCP
|
||||
const result = await mcp__chrome__screenshot(options);
|
||||
|
||||
// Save as PNG
|
||||
const filename = `${ss.id}.png`;
|
||||
Write(`${screenshotDir}/${filename}`, result.data, { encoding: 'base64' });
|
||||
|
||||
results.captured.push({
|
||||
id: ss.id,
|
||||
file: filename,
|
||||
url: ss.url,
|
||||
description: ss.description,
|
||||
size: result.data.length
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Failed to capture ${ss.id}:`, error.message);
|
||||
results.failed.push({
|
||||
id: ss.id,
|
||||
url: ss.url,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Generate Manifest
|
||||
|
||||
```javascript
|
||||
function generateScreenshotManifest(results, workDir) {
|
||||
const manifest = {
|
||||
generated: new Date().toISOString(),
|
||||
total: results.captured.length + results.failed.length,
|
||||
captured: results.captured.length,
|
||||
failed: results.failed.length,
|
||||
screenshots: results.captured,
|
||||
failures: results.failed
|
||||
};
|
||||
|
||||
Write(`${workDir}/screenshots/screenshots-manifest.json`,
|
||||
JSON.stringify(manifest, null, 2));
|
||||
|
||||
return manifest;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5: Cleanup
|
||||
|
||||
```javascript
|
||||
async function cleanupScreenshotEnvironment(env) {
|
||||
if (env.server) {
|
||||
console.log('Stopping dev server...');
|
||||
KillShell({ shell_id: env.server.task_id });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Main Runner
|
||||
|
||||
```javascript
|
||||
async function runScreenshotCapture(workDir, screenshots) {
|
||||
const config = JSON.parse(Read(`${workDir}/manual-config.json`));
|
||||
|
||||
// Prepare environment
|
||||
const env = await prepareScreenshotEnvironment(workDir, config);
|
||||
|
||||
if (env.mode === 'manual') {
|
||||
// Generate manual capture guide
|
||||
generateManualCaptureGuide(screenshots, workDir);
|
||||
return { success: false, mode: 'manual' };
|
||||
}
|
||||
|
||||
try {
|
||||
// Capture screenshots
|
||||
const results = await captureScreenshots(screenshots, config, workDir);
|
||||
|
||||
// Generate manifest
|
||||
const manifest = generateScreenshotManifest(results, workDir);
|
||||
|
||||
// Generate manual guide for failed captures
|
||||
if (results.failed.length > 0) {
|
||||
generateManualCaptureGuide(results.failed, workDir);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
captured: results.captured.length,
|
||||
failed: results.failed.length,
|
||||
manifest
|
||||
};
|
||||
|
||||
} finally {
|
||||
// Cleanup
|
||||
await cleanupScreenshotEnvironment(env);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Manual Capture Fallback
|
||||
|
||||
When Chrome MCP is unavailable:
|
||||
|
||||
```javascript
|
||||
function generateManualCaptureGuide(screenshots, workDir) {
|
||||
const guide = `
|
||||
# Manual Screenshot Capture Guide
|
||||
|
||||
Chrome MCP is not available. Please capture screenshots manually.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Start your development server
|
||||
2. Open a browser
|
||||
3. Use a screenshot tool (Snipping Tool, Screenshot, etc.)
|
||||
|
||||
## Screenshots Required
|
||||
|
||||
${screenshots.map((ss, i) => `
|
||||
### ${i + 1}. ${ss.id}
|
||||
|
||||
- **URL**: ${ss.url}
|
||||
- **Description**: ${ss.description}
|
||||
- **Save as**: \`screenshots/${ss.id}.png\`
|
||||
${ss.selector ? `- **Capture area**: \`${ss.selector}\` element only` : '- **Type**: Full page or viewport'}
|
||||
${ss.wait_for ? `- **Wait for**: \`${ss.wait_for}\` to be visible` : ''}
|
||||
|
||||
**Steps:**
|
||||
1. Navigate to ${ss.url}
|
||||
${ss.wait_for ? `2. Wait for ${ss.wait_for} to appear` : ''}
|
||||
${ss.selector ? `2. Capture only the ${ss.selector} area` : '2. Capture the full viewport'}
|
||||
3. Save as \`${ss.id}.png\`
|
||||
`).join('\n')}
|
||||
|
||||
## After Capturing
|
||||
|
||||
1. Place all PNG files in the \`screenshots/\` directory
|
||||
2. Ensure filenames match exactly (case-sensitive)
|
||||
3. Run Phase 5 (HTML Assembly) to continue
|
||||
|
||||
## Screenshot Specifications
|
||||
|
||||
- **Format**: PNG
|
||||
- **Width**: 1280px recommended
|
||||
- **Quality**: High
|
||||
- **Annotations**: None (add in post-processing if needed)
|
||||
`;
|
||||
|
||||
Write(`${workDir}/screenshots/MANUAL_CAPTURE.md`, guide);
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Options
|
||||
|
||||
### Viewport Sizes
|
||||
|
||||
```javascript
|
||||
const viewportPresets = {
|
||||
desktop: { width: 1280, height: 800 },
|
||||
tablet: { width: 768, height: 1024 },
|
||||
mobile: { width: 375, height: 667 },
|
||||
wide: { width: 1920, height: 1080 }
|
||||
};
|
||||
|
||||
async function captureResponsive(ss, config, workDir) {
|
||||
const results = [];
|
||||
|
||||
for (const [name, viewport] of Object.entries(viewportPresets)) {
|
||||
const result = await mcp__chrome__screenshot({
|
||||
url: ss.url,
|
||||
viewport
|
||||
});
|
||||
|
||||
const filename = `${ss.id}-${name}.png`;
|
||||
Write(`${workDir}/screenshots/${filename}`, result.data, { encoding: 'base64' });
|
||||
|
||||
results.push({ viewport: name, file: filename });
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
```
|
||||
|
||||
### Before/After Comparisons
|
||||
|
||||
```javascript
|
||||
async function captureInteraction(ss, config, workDir) {
|
||||
const baseUrl = config.screenshot_config.dev_url;
|
||||
const fullUrl = new URL(ss.url, baseUrl).href;
|
||||
|
||||
// Capture before state
|
||||
const before = await mcp__chrome__screenshot({
|
||||
url: fullUrl,
|
||||
viewport: { width: 1280, height: 800 }
|
||||
});
|
||||
Write(`${workDir}/screenshots/${ss.id}-before.png`, before.data, { encoding: 'base64' });
|
||||
|
||||
// Perform interaction (click, type, etc.)
|
||||
if (ss.interaction) {
|
||||
await mcp__chrome__click({ selector: ss.interaction.click });
|
||||
await sleep(500);
|
||||
}
|
||||
|
||||
// Capture after state
|
||||
const after = await mcp__chrome__screenshot({
|
||||
url: fullUrl,
|
||||
viewport: { width: 1280, height: 800 }
|
||||
});
|
||||
Write(`${workDir}/screenshots/${ss.id}-after.png`, after.data, { encoding: 'base64' });
|
||||
|
||||
return {
|
||||
before: `${ss.id}-before.png`,
|
||||
after: `${ss.id}-after.png`
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Screenshot Annotation
|
||||
|
||||
```javascript
|
||||
function generateAnnotationGuide(screenshots, workDir) {
|
||||
const guide = `
|
||||
# Screenshot Annotation Guide
|
||||
|
||||
For screenshots requiring callouts or highlights:
|
||||
|
||||
## Tools
|
||||
- macOS: Preview, Skitch
|
||||
- Windows: Snipping Tool, ShareX
|
||||
- Cross-platform: Greenshot, Lightshot
|
||||
|
||||
## Annotation Guidelines
|
||||
|
||||
1. **Callouts**: Use numbered circles (1, 2, 3)
|
||||
2. **Highlights**: Use semi-transparent rectangles
|
||||
3. **Arrows**: Point from text to element
|
||||
4. **Text**: Use sans-serif font, 12-14pt
|
||||
|
||||
## Color Scheme
|
||||
|
||||
- Primary: #0d6efd (blue)
|
||||
- Secondary: #6c757d (gray)
|
||||
- Success: #198754 (green)
|
||||
- Warning: #ffc107 (yellow)
|
||||
- Danger: #dc3545 (red)
|
||||
|
||||
## Screenshots Needing Annotation
|
||||
|
||||
${screenshots.filter(s => s.annotate).map(ss => `
|
||||
- **${ss.id}**: ${ss.description}
|
||||
- Highlight: ${ss.annotate.highlight || 'N/A'}
|
||||
- Callouts: ${ss.annotate.callouts?.join(', ') || 'N/A'}
|
||||
`).join('\n')}
|
||||
`;
|
||||
|
||||
Write(`${workDir}/screenshots/ANNOTATION_GUIDE.md`, guide);
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Chrome MCP Not Found
|
||||
|
||||
1. Check Claude MCP configuration
|
||||
2. Verify Chrome is installed
|
||||
3. Check CHROME_PATH environment variable
|
||||
|
||||
### Screenshots Are Blank
|
||||
|
||||
1. Increase wait time before capture
|
||||
2. Check if page requires authentication
|
||||
3. Verify URL is correct
|
||||
|
||||
### Elements Not Visible
|
||||
|
||||
1. Scroll element into view
|
||||
2. Expand collapsed sections
|
||||
3. Wait for animations to complete
|
||||
|
||||
### Server Not Starting
|
||||
|
||||
1. Check if port is already in use
|
||||
2. Verify dev command is correct
|
||||
3. Check for startup errors in logs
|
||||
419
.claude/skills/software-manual/scripts/swagger-runner.md
Normal file
419
.claude/skills/software-manual/scripts/swagger-runner.md
Normal file
@@ -0,0 +1,419 @@
|
||||
# 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;
|
||||
}
|
||||
```
|
||||
357
.claude/skills/software-manual/scripts/typedoc-runner.md
Normal file
357
.claude/skills/software-manual/scripts/typedoc-runner.md
Normal file
@@ -0,0 +1,357 @@
|
||||
# TypeDoc Runner
|
||||
|
||||
Guide for generating frontend API documentation using TypeDoc.
|
||||
|
||||
## Overview
|
||||
|
||||
TypeDoc generates API documentation from TypeScript source code by analyzing type annotations and JSDoc comments.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Check TypeScript Project
|
||||
|
||||
```javascript
|
||||
// Verify TypeScript is used
|
||||
const packageJson = JSON.parse(Read('package.json'));
|
||||
const hasTypeScript = packageJson.devDependencies?.typescript ||
|
||||
packageJson.dependencies?.typescript;
|
||||
|
||||
if (!hasTypeScript) {
|
||||
console.log('Not a TypeScript project. Skipping TypeDoc.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for tsconfig.json
|
||||
const hasTsConfig = Glob('tsconfig.json').length > 0;
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
### Install TypeDoc
|
||||
|
||||
```bash
|
||||
npm install --save-dev typedoc typedoc-plugin-markdown
|
||||
```
|
||||
|
||||
### Optional Plugins
|
||||
|
||||
```bash
|
||||
# For better Markdown output
|
||||
npm install --save-dev typedoc-plugin-markdown
|
||||
|
||||
# For README inclusion
|
||||
npm install --save-dev typedoc-plugin-rename-defaults
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### typedoc.json
|
||||
|
||||
Create `typedoc.json` in project root:
|
||||
|
||||
```json
|
||||
{
|
||||
"entryPoints": ["./src/index.ts"],
|
||||
"entryPointStrategy": "expand",
|
||||
"out": ".workflow/.scratchpad/manual-{timestamp}/api-docs/frontend",
|
||||
"plugin": ["typedoc-plugin-markdown"],
|
||||
"exclude": [
|
||||
"**/node_modules/**",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/tests/**"
|
||||
],
|
||||
"excludePrivate": true,
|
||||
"excludeProtected": true,
|
||||
"excludeInternal": true,
|
||||
"hideGenerator": true,
|
||||
"readme": "none",
|
||||
"categorizeByGroup": true,
|
||||
"navigation": {
|
||||
"includeCategories": true,
|
||||
"includeGroups": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Alternative: CLI Options
|
||||
|
||||
```bash
|
||||
npx typedoc \
|
||||
--entryPoints src/index.ts \
|
||||
--entryPointStrategy expand \
|
||||
--out api-docs/frontend \
|
||||
--plugin typedoc-plugin-markdown \
|
||||
--exclude "**/node_modules/**" \
|
||||
--exclude "**/*.test.ts" \
|
||||
--excludePrivate \
|
||||
--excludeProtected \
|
||||
--readme none
|
||||
```
|
||||
|
||||
## Execution
|
||||
|
||||
### Basic Run
|
||||
|
||||
```javascript
|
||||
async function runTypeDoc(workDir) {
|
||||
const outputDir = `${workDir}/api-docs/frontend`;
|
||||
|
||||
// Create output directory
|
||||
Bash({ command: `mkdir -p "${outputDir}"` });
|
||||
|
||||
// Run TypeDoc
|
||||
const result = Bash({
|
||||
command: `npx typedoc --out "${outputDir}" --plugin typedoc-plugin-markdown src/`,
|
||||
timeout: 120000 // 2 minutes
|
||||
});
|
||||
|
||||
if (result.exitCode !== 0) {
|
||||
console.error('TypeDoc failed:', result.stderr);
|
||||
return { success: false, error: result.stderr };
|
||||
}
|
||||
|
||||
// List generated files
|
||||
const files = Glob(`${outputDir}/**/*.md`);
|
||||
console.log(`Generated ${files.length} documentation files`);
|
||||
|
||||
return { success: true, files };
|
||||
}
|
||||
```
|
||||
|
||||
### With Custom Entry Points
|
||||
|
||||
```javascript
|
||||
async function runTypeDocCustom(workDir, entryPoints) {
|
||||
const outputDir = `${workDir}/api-docs/frontend`;
|
||||
|
||||
// Build entry points string
|
||||
const entries = entryPoints.map(e => `--entryPoints "${e}"`).join(' ');
|
||||
|
||||
const result = Bash({
|
||||
command: `npx typedoc ${entries} --out "${outputDir}" --plugin typedoc-plugin-markdown`,
|
||||
timeout: 120000
|
||||
});
|
||||
|
||||
return { success: result.exitCode === 0 };
|
||||
}
|
||||
|
||||
// Example: Document specific files
|
||||
await runTypeDocCustom(workDir, [
|
||||
'src/api/index.ts',
|
||||
'src/hooks/index.ts',
|
||||
'src/utils/index.ts'
|
||||
]);
|
||||
```
|
||||
|
||||
## Output Structure
|
||||
|
||||
```
|
||||
api-docs/frontend/
|
||||
├── README.md # Index
|
||||
├── modules.md # Module list
|
||||
├── modules/
|
||||
│ ├── api.md # API module
|
||||
│ ├── hooks.md # Hooks module
|
||||
│ └── utils.md # Utils module
|
||||
├── classes/
|
||||
│ ├── ApiClient.md # Class documentation
|
||||
│ └── ...
|
||||
├── interfaces/
|
||||
│ ├── Config.md # Interface documentation
|
||||
│ └── ...
|
||||
└── functions/
|
||||
├── formatDate.md # Function documentation
|
||||
└── ...
|
||||
```
|
||||
|
||||
## Integration with Manual
|
||||
|
||||
### Reading TypeDoc Output
|
||||
|
||||
```javascript
|
||||
async function integrateTypeDocOutput(workDir) {
|
||||
const apiDocsDir = `${workDir}/api-docs/frontend`;
|
||||
const files = Glob(`${apiDocsDir}/**/*.md`);
|
||||
|
||||
// Build API reference content
|
||||
let content = '## Frontend API Reference\n\n';
|
||||
|
||||
// Add modules
|
||||
const modules = Glob(`${apiDocsDir}/modules/*.md`);
|
||||
for (const mod of modules) {
|
||||
const modContent = Read(mod);
|
||||
content += `### ${extractTitle(modContent)}\n\n`;
|
||||
content += summarizeModule(modContent);
|
||||
}
|
||||
|
||||
// Add functions
|
||||
const functions = Glob(`${apiDocsDir}/functions/*.md`);
|
||||
content += '\n### Functions\n\n';
|
||||
for (const fn of functions) {
|
||||
const fnContent = Read(fn);
|
||||
content += formatFunctionDoc(fnContent);
|
||||
}
|
||||
|
||||
// Add hooks
|
||||
const hooks = Glob(`${apiDocsDir}/functions/*Hook*.md`);
|
||||
if (hooks.length > 0) {
|
||||
content += '\n### Hooks\n\n';
|
||||
for (const hook of hooks) {
|
||||
const hookContent = Read(hook);
|
||||
content += formatHookDoc(hookContent);
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
```
|
||||
|
||||
### Example Output Format
|
||||
|
||||
```markdown
|
||||
## Frontend API Reference
|
||||
|
||||
### API Module
|
||||
|
||||
Functions for interacting with the backend API.
|
||||
|
||||
#### fetchProjects
|
||||
|
||||
```typescript
|
||||
function fetchProjects(options?: FetchOptions): Promise<Project[]>
|
||||
```
|
||||
|
||||
Fetches all projects for the current user.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Name | Type | Description |
|
||||
|------|------|-------------|
|
||||
| options | FetchOptions | Optional fetch configuration |
|
||||
|
||||
**Returns:** Promise<Project[]>
|
||||
|
||||
### Hooks
|
||||
|
||||
#### useProjects
|
||||
|
||||
```typescript
|
||||
function useProjects(options?: UseProjectsOptions): UseProjectsResult
|
||||
```
|
||||
|
||||
React hook for managing project data.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Name | Type | Description |
|
||||
|------|------|-------------|
|
||||
| options.status | string | Filter by project status |
|
||||
| options.limit | number | Max projects to fetch |
|
||||
|
||||
**Returns:**
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| projects | Project[] | Array of projects |
|
||||
| loading | boolean | Loading state |
|
||||
| error | Error \| null | Error if failed |
|
||||
| refetch | () => void | Refresh data |
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### "Cannot find module 'typescript'"
|
||||
|
||||
```bash
|
||||
npm install --save-dev typescript
|
||||
```
|
||||
|
||||
#### "No entry points found"
|
||||
|
||||
Ensure entry points exist:
|
||||
|
||||
```bash
|
||||
# Check entry points
|
||||
ls src/index.ts
|
||||
|
||||
# Or use glob pattern
|
||||
npx typedoc --entryPoints "src/**/*.ts"
|
||||
```
|
||||
|
||||
#### "Unsupported TypeScript version"
|
||||
|
||||
```bash
|
||||
# Check TypeDoc compatibility
|
||||
npm info typedoc peerDependencies
|
||||
|
||||
# Install compatible version
|
||||
npm install --save-dev typedoc@0.25.x
|
||||
```
|
||||
|
||||
### Debugging
|
||||
|
||||
```bash
|
||||
# Verbose output
|
||||
npx typedoc --logLevel Verbose src/
|
||||
|
||||
# Show warnings
|
||||
npx typedoc --treatWarningsAsErrors src/
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Document Exports Only
|
||||
|
||||
```typescript
|
||||
// Good: Public API documented
|
||||
/**
|
||||
* Fetches projects from the API.
|
||||
* @param options - Fetch options
|
||||
* @returns Promise resolving to projects
|
||||
*/
|
||||
export function fetchProjects(options?: FetchOptions): Promise<Project[]> {
|
||||
// ...
|
||||
}
|
||||
|
||||
// Internal: Not documented
|
||||
function internalHelper() {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Use JSDoc Comments
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* User hook for managing authentication state.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { user, login, logout } = useAuth();
|
||||
* ```
|
||||
*
|
||||
* @returns Authentication state and methods
|
||||
*/
|
||||
export function useAuth(): AuthResult {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Define Types Properly
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Configuration for the API client.
|
||||
*/
|
||||
export interface ApiConfig {
|
||||
/** API base URL */
|
||||
baseUrl: string;
|
||||
/** Request timeout in milliseconds */
|
||||
timeout?: number;
|
||||
/** Custom headers to include */
|
||||
headers?: Record<string, string>;
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user