Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | 12x 12x 12x 1x 12x 3x 3x 3x 1x 3x 4x 3x 3x 3x 2x 1x 11x | import * as fs from 'fs';
import * as path from 'path';
import { DEFAULT_OPENAPI_INFO, type RouteForDocs, type OpenApiOptions } from './types.js';
import { buildPaths, buildSchemas } from './spec-builder.js';
export type { RouteForDocs, OpenApiInfo, OpenApiServer, OpenApiOptions, RouteMetadata, ResponseMetadata, ApiDocsHtmlOptions } from './types.js';
export function generateOpenApiSpec(routes: RouteForDocs[], options: OpenApiOptions): object {
const info = { ...DEFAULT_OPENAPI_INFO, ...options.info };
const spec: Record<string, unknown> = {
openapi: '3.0.3',
info,
paths: buildPaths(routes, options),
components: {
schemas: buildSchemas(routes),
},
};
if (options.servers && options.servers.length > 0) {
spec.servers = options.servers;
}
return spec;
}
export async function generateOpenApiFile(
outputPath: string,
routes: RouteForDocs[],
options: OpenApiOptions
): Promise<void> {
const spec = generateOpenApiSpec(routes, options);
const dir = path.dirname(outputPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(outputPath, JSON.stringify(spec, null, 2), 'utf-8');
}
export function generateSwaggerUiHtml(apiJsonPath: string, title: string = 'API Documentation'): string {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${escapeHtml(title)}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css">
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
<script>
SwaggerUIBundle({
url: '${escapeHtml(apiJsonPath)}',
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.presets.standalone
]
});
</script>
</body>
</html>`;
}
export function generateApiDocsHtml(
openApiJsonPath: string,
title: string,
options: import('./types.js').ApiDocsHtmlOptions = {}
): string {
const hasAsyncApi = Boolean(options.asyncApiJsonPath?.trim());
const asyncApiPath = options.asyncApiJsonPath ?? '';
if (!hasAsyncApi) {
return generateSwaggerUiHtml(openApiJsonPath, title);
}
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${escapeHtml(title)}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css">
<link rel="stylesheet" href="https://unpkg.com/@asyncapi/react-component@1.0.0-next.39/styles/default.min.css">
<style>
.vitek-docs-tabs { display: flex; gap: 0; border-bottom: 1px solid #ddd; margin-bottom: 0; padding: 0 1rem; background: #fafafa; }
.vitek-docs-tab { padding: 0.75rem 1.25rem; cursor: pointer; font-weight: 500; color: #666; border-bottom: 2px solid transparent; margin-bottom: -1px; }
.vitek-docs-tab:hover { color: #333; }
.vitek-docs-tab.active { color: #4990e2; border-bottom-color: #4990e2; }
.vitek-docs-panel { display: none; padding: 0; height: calc(100vh - 50px); }
.vitek-docs-panel.active { display: block; }
.vitek-docs-panel iframe { border: none; width: 100%; height: 100%; }
#asyncapi-ui { padding: 1rem; min-height: 100%; box-sizing: border-box; }
</style>
</head>
<body>
<div class="vitek-docs-tabs">
<div class="vitek-docs-tab active" data-tab="rest">REST</div>
<div class="vitek-docs-tab" data-tab="websockets">WebSockets</div>
</div>
<div id="panel-rest" class="vitek-docs-panel active">
<div id="swagger-ui"></div>
</div>
<div id="panel-websockets" class="vitek-docs-panel">
<div id="asyncapi-ui"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
<script>
SwaggerUIBundle({
url: '${escapeHtml(openApiJsonPath)}',
dom_id: '#swagger-ui',
presets: [SwaggerUIBundle.presets.apis, SwaggerUIBundle.presets.standalone]
});
</script>
<script src="https://unpkg.com/@asyncapi/react-component@1.0.0-next.39/browser/standalone/index.js"></script>
<script>
(function() {
var tabs = document.querySelectorAll('.vitek-docs-tab');
var panels = document.querySelectorAll('.vitek-docs-panel');
var asyncApiLoaded = false;
var asyncApiPath = '${escapeHtml(asyncApiPath)}';
function showTab(name) {
tabs.forEach(function(t) { t.classList.toggle('active', t.getAttribute('data-tab') === name); });
panels.forEach(function(p) {
var isActive = (p.id === 'panel-' + name);
p.classList.toggle('active', isActive);
if (isActive && name === 'websockets' && !asyncApiLoaded && typeof AsyncApiStandalone !== 'undefined') {
asyncApiLoaded = true;
fetch(asyncApiPath).then(function(r) { return r.text(); }).then(function(schema) {
AsyncApiStandalone.render({ schema: schema, config: { show: { sidebar: true } } }, document.getElementById('asyncapi-ui'));
}).catch(function(err) { document.getElementById('asyncapi-ui').innerHTML = '<p>Failed to load AsyncAPI spec: ' + err.message + '</p>'; });
}
});
}
tabs.forEach(function(t) { t.addEventListener('click', function() { showTab(t.getAttribute('data-tab')); }); });
})();
</script>
</body>
</html>`;
}
function escapeHtml(s: string): string {
return s
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
|