# 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;
}
```
