api-morph
Version:
A modern TypeScript-first OpenAPI document generator that analyzes your code and JSDoc comments to automatically generate comprehensive and accurate API documentation.
137 lines (133 loc) • 3.97 kB
JavaScript
import { createRequire } from "node:module";
import z from "zod/v4";
import path from "node:path";
//#region src/registry/SchemaRegistry.ts
/**
* Schema 注册中心,管理运行时收集的 Zod Schema 信息
*/
var SchemaRegistry = class SchemaRegistry {
static instance;
schemas = /* @__PURE__ */ new Map();
/**
* 获取单例实例
*/
static getInstance() {
if (!SchemaRegistry.instance) SchemaRegistry.instance = new SchemaRegistry();
return SchemaRegistry.instance;
}
/**
* 注册 Schema 信息
* @param location 调用位置(文件路径:行号)
* @param schemas Schema 对象
*/
register(location, schemas) {
const schemaInfo = {
location,
schemas: {}
};
if (schemas.body) schemaInfo.schemas.body = z.toJSONSchema(schemas.body);
if (schemas.query) schemaInfo.schemas.query = z.toJSONSchema(schemas.query);
if (schemas.params) schemaInfo.schemas.params = z.toJSONSchema(schemas.params);
if (schemas.headers) schemaInfo.schemas.headers = z.toJSONSchema(schemas.headers);
this.schemas.set(location, schemaInfo);
}
/**
* 根据位置获取 Schema 信息
* @param location 文件位置(文件路径:行号)
* @returns Schema 信息
*/
get(location) {
return this.schemas.get(location);
}
/**
* 清空所有已注册的 Schema
*/
clear() {
this.schemas.clear();
}
};
/**
* 获取调用栈中的调用位置信息
* @param skipFrames 跳过的栈帧数量
* @returns 调用位置字符串(文件路径:行号)
*/
function getCallLocation(skipFrames = 3) {
const stack = (/* @__PURE__ */ new Error()).stack;
if (!stack) return "unknown";
const lines = stack.split("\n");
for (let i = skipFrames; i < lines.length; i++) {
const line = lines[i];
if (line.includes("at ") && !line.includes("zodValidator") && !line.includes("SchemaRegistry")) {
const patterns = [/at .* \((.+):(\d+):(\d+)\)/, /at (.+):(\d+):(\d+)/];
for (const pattern of patterns) {
const match = line.match(pattern);
if (match?.[1] && match[2]) return `${match[1]}:${match[2]}`;
}
}
}
return "unknown";
}
//#endregion
//#region src/core/swagger.ts
/**
* 获取 Swagger UI 静态资源目录。
* @returns Swagger UI 静态资源目录。
*
* @category Core
*/
function getSwaggerUIAssetDir() {
const require = createRequire(import.meta.url);
const packageJsonPath = require.resolve("swagger-ui-dist/package.json");
const assetDir = path.dirname(packageJsonPath);
return assetDir;
}
/**
* 生成 Swagger UI 的 HTML 字符串。
* @param options 选项。
* @returns 完整的 Swagger UI HTML 字符串。
*
* @category Core
*/
function generateSwaggerUIHTML(options = {}) {
const { url = "/openapi.json", title = "Swagger UI", customCss = "", customJs = "", persistAuthorization = false } = options;
const html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>${title}</title>
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16">
<link rel="stylesheet" href="./swagger-ui.css">
<link rel="stylesheet" href="./index.css">
${customCss ? `<style>\n${customCss}\n</style>` : ""}
</head>
<body>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js"><\/script>
<script src="./swagger-ui-standalone-preset.js"><\/script>
<script>
window.onload = () => {
const ui = SwaggerUIBundle({
url: "${url}",
dom_id: "#swagger-ui",
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout",
persistAuthorization: ${persistAuthorization}
});
window.ui = ui;
};
${customJs}
<\/script>
</body>
</html>`;
return html;
}
//#endregion
export { SchemaRegistry, generateSwaggerUIHTML, getCallLocation, getSwaggerUIAssetDir };