UNPKG

@paultaku/node-mock-server

Version:

A TypeScript-based mock server with automatic Swagger-based mock file generation

1,068 lines (1,022 loc) 39.1 kB
/******/ (() => { // webpackBootstrap /******/ "use strict"; /******/ var __webpack_modules__ = ({ /***/ 115: /***/ ((module) => { module.exports = require("yaml"); /***/ }), /***/ 252: /***/ ((module) => { module.exports = require("express"); /***/ }), /***/ 285: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { Object.defineProperty(exports, "__esModule", ({ value: true })); exports.MultiServerManager = exports.MockServerManager = void 0; exports.createMockServer = createMockServer; exports.createMultiServerManager = createMultiServerManager; const server_1 = __webpack_require__(422); class MockServerManager { constructor(config) { this.server = null; this.isRunning = false; this.port = config.port; this.mockRoot = config.mockRoot || "./mock"; if (config.autoStart) { this.start(); } } /** * 启动服务器 */ async start() { if (this.isRunning) { console.log(`Server is already running on port ${this.port}`); return; } try { await (0, server_1.startMockServer)(this.port, this.mockRoot); this.isRunning = true; console.log(`MockServerManager: Server started successfully on port ${this.port}`); } catch (error) { console.error(`MockServerManager: Failed to start server on port ${this.port}:`, error); throw error; } } /** * 停止服务器 */ stop() { return new Promise((resolve, reject) => { if (!this.isRunning) { console.log("Server is not running"); resolve(); return; } if (this.server) { this.server.close((error) => { if (error) { console.error("Error stopping server:", error); reject(error); } else { this.isRunning = false; this.server = null; console.log(`MockServerManager: Server stopped on port ${this.port}`); resolve(); } }); } else { this.isRunning = false; resolve(); } }); } /** * 重启服务器 */ async restart() { console.log("Restarting server..."); await this.stop(); await this.start(); console.log("Server restarted successfully"); } /** * 检查服务器是否正在运行 */ isServerRunning() { return this.isRunning; } /** * 获取服务器端口 */ getPort() { return this.port; } /** * 获取 mock 根目录 */ getMockRoot() { return this.mockRoot; } /** * 获取服务器状态信息 */ getStatus() { return { isRunning: this.isRunning, port: this.port, mockRoot: this.mockRoot, url: `http://localhost:${this.port}`, }; } } exports.MockServerManager = MockServerManager; // 创建多个服务器实例的管理器 class MultiServerManager { constructor() { this.servers = new Map(); } /** * 创建并启动一个服务器 */ async createServer(port, config) { if (this.servers.has(port)) { throw new Error(`Server on port ${port} already exists`); } const serverConfig = { port, autoStart: false, ...config, }; const server = new MockServerManager(serverConfig); await server.start(); this.servers.set(port, server); return server; } /** * 停止并移除一个服务器 */ async removeServer(port) { const server = this.servers.get(port); if (!server) { throw new Error(`Server on port ${port} not found`); } await server.stop(); this.servers.delete(port); } /** * 停止所有服务器 */ async stopAllServers() { const stopPromises = Array.from(this.servers.values()).map((server) => server.stop()); await Promise.all(stopPromises); this.servers.clear(); } /** * 获取所有服务器状态 */ getAllServerStatus() { return Array.from(this.servers.entries()).map(([port, server]) => ({ port, status: server.getStatus(), })); } /** * 获取特定端口的服务器 */ getServer(port) { return this.servers.get(port); } /** * 检查端口是否被使用 */ isPortInUse(port) { return this.servers.has(port); } } exports.MultiServerManager = MultiServerManager; // 导出便捷函数 function createMockServer(port = 3000) { return new MockServerManager({ port, autoStart: true }); } function createMultiServerManager() { return new MultiServerManager(); } /***/ }), /***/ 315: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { Object.defineProperty(exports, "__esModule", ({ value: true })); exports.SwaggerDocSchema = void 0; const zod_1 = __webpack_require__(569); exports.SwaggerDocSchema = zod_1.z.object({ openapi: zod_1.z.string(), info: zod_1.z.object({ title: zod_1.z.string(), version: zod_1.z.string(), }), paths: zod_1.z.record(zod_1.z.string(), zod_1.z.any()), components: zod_1.z.optional(zod_1.z.any()), }); /***/ }), /***/ 422: /***/ (function(module, exports, __webpack_require__) { /* module decorator */ module = __webpack_require__.nmd(module); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.startMockServer = startMockServer; const express_1 = __importDefault(__webpack_require__(252)); const path_1 = __importDefault(__webpack_require__(928)); const fs_extra_1 = __importDefault(__webpack_require__(652)); const zod_1 = __webpack_require__(569); const status_manager_1 = __webpack_require__(537); const DEFAULT_MOCK_ROOT = path_1.default.resolve(__dirname, "../mock"); const DEFAULT_MOCK_FILE = "successful-operation-200.json"; // Store the mock response state of each endpoint let mockStates = new Map(); // Only allow letters, numbers, -, _, {}, not : * ? ( ) [ ] etc. function isValidMockPart(part) { return /^[a-zA-Z0-9_\-{}]+$/.test(part); } // Get all mock endpoint templates (e.g. user/{username}/GET) async function getAllMockTemplates(mockRoot) { async function walk(dir, parts = []) { const entries = await fs_extra_1.default.readdir(dir); let results = []; for (const entry of entries) { if (!isValidMockPart(entry)) continue; // Skip invalid names const fullPath = path_1.default.join(dir, entry); const stat = await fs_extra_1.default.stat(fullPath); if (stat.isDirectory()) { // Check if there are json files under this directory (i.e. method directory) const files = await fs_extra_1.default.readdir(fullPath); const jsonFiles = files.filter((f) => f.endsWith(".json") && f !== "status.json"); if (jsonFiles.length > 0) { // This is a method directory, push parts+method results.push([...parts, entry]); } // Continue recursion results = results.concat(await walk(fullPath, [...parts, entry])); } } return results; } return walk(mockRoot); } // Path parameter template matching function matchTemplate(requestParts, templates, method) { let bestMatch = null; for (const tpl of templates) { if (tpl.length !== requestParts.length + 1) continue; // +1 for method const lastElement = tpl[tpl.length - 1]; if (!lastElement || lastElement.toUpperCase() !== method) continue; let params = {}; let matched = true; for (let i = 0; i < requestParts.length; i++) { const tplElement = tpl[i]; if (!tplElement) continue; if (tplElement.startsWith("{") && tplElement.endsWith("}")) { params[tplElement.slice(1, -1)] = requestParts[i] || ""; } else if (tplElement !== requestParts[i]) { matched = false; break; } } if (matched) { bestMatch = { template: tpl, params }; break; } } return bestMatch; } // Get all available mock files for an endpoint async function getAvailableMockFiles(endpointDir) { try { const files = await fs_extra_1.default.readdir(endpointDir); return files.filter((file) => file.endsWith(".json") && file !== "status.json"); } catch (error) { return []; } } // Get the mock state key for the current endpoint function getMockStateKey(path, method) { return `${method.toUpperCase()}:${path}`; } // Create Express app function createApp(mockRoot = DEFAULT_MOCK_ROOT) { const app = (0, express_1.default)(); // Middleware app.use(express_1.default.json()); app.use(express_1.default.static(path_1.default.join(__dirname, "./public"))); // Load all status.json files when service starts (async () => { const templates = await getAllMockTemplates(mockRoot); mockStates = await (0, status_manager_1.loadAllStatusJson)(mockRoot, templates); console.log("[status-manager] All status.json files loaded"); })(); // API endpoint: get all available endpoints app.get("/_mock/endpoints", async (req, res) => { try { console.log("Getting all mock templates..."); const templates = await getAllMockTemplates(mockRoot); console.log("Templates found:", templates); const endpoints = []; for (const template of templates) { const method = template[template.length - 1] || ""; const pathParts = template.slice(0, -1); const apiPath = "/" + pathParts.join("/"); const stateKey = getMockStateKey(apiPath, method); const currentMock = mockStates.get(stateKey) || DEFAULT_MOCK_FILE; // Read status.json to get delayMillisecond const statusPath = (0, status_manager_1.getStatusJsonPath)(mockRoot, apiPath, method); let delayMillisecond = undefined; try { const status = await (0, status_manager_1.readStatusJson)(statusPath); if (status && typeof status.delayMillisecond === "number") { delayMillisecond = status.delayMillisecond; } } catch { } endpoints.push({ path: apiPath, method: method, currentMock: currentMock, availableMocks: [], // Explicit type delayMillisecond, }); } // Get available mock files for each endpoint for (const endpoint of endpoints) { const endpointDir = path_1.default.join(mockRoot, ...endpoint.path.replace(/^\//, "").split("/"), endpoint.method); endpoint.availableMocks = await getAvailableMockFiles(endpointDir); } res.json(endpoints); } catch (error) { res .status(500) .json({ error: "Failed to get endpoints", detail: String(error) }); } }); // API endpoint: set the mock response for an endpoint const SetMockRequestSchema = zod_1.z.object({ path: zod_1.z.string(), method: zod_1.z.string(), mockFile: zod_1.z.string(), }); // API endpoint: update mock status via /_mock URL segment app.post("/_mock/update", async (req, res) => { try { const { path: apiPath, method, mockFile, delayMillisecond } = req.body; if (!apiPath || !method) { return res .status(400) .json({ error: "Missing required parameters: path and method" }); } const statusPath = (0, status_manager_1.getStatusJsonPath)(mockRoot, apiPath, method); let status = await (0, status_manager_1.readStatusJson)(statusPath); if (!status) status = { selected: DEFAULT_MOCK_FILE }; // Update mock file if provided if (mockFile) { // Validate if the mock file exists const endpointDir = path_1.default.join(mockRoot, ...apiPath.replace(/^\//, "").split("/"), method.toUpperCase()); const mockFilePath = path_1.default.join(endpointDir, mockFile); if (!(await fs_extra_1.default.pathExists(mockFilePath))) { return res .status(400) .json({ error: "Mock file not found", file: mockFilePath }); } status.selected = mockFile; const stateKey = getMockStateKey(apiPath, method); mockStates.set(stateKey, mockFile); } // Update delay if provided if (typeof delayMillisecond === "number") { if (delayMillisecond < 0 || delayMillisecond > 60000) { return res .status(400) .json({ error: "Delay must be between 0 and 60000 milliseconds" }); } status.delayMillisecond = delayMillisecond; } // Write updated status await fs_extra_1.default.writeJson(statusPath, status, { spaces: 2 }); console.log(`[status-manager] Updated ${statusPath} via /_mock/update`); return res.json({ success: true, message: "Mock status updated successfully", status: { selected: status.selected, delayMillisecond: status.delayMillisecond, }, }); } catch (error) { return res .status(500) .json({ error: "Failed to update mock status", detail: String(error) }); } }); // API endpoint: get mock status via /_mock URL segment app.get("/_mock/status", async (req, res) => { try { const { method, path: apiPath } = req.query; if (!method || !apiPath || typeof method !== "string" || typeof apiPath !== "string") { return res .status(400) .json({ error: "Missing method or path parameter" }); } const statusPath = (0, status_manager_1.getStatusJsonPath)(mockRoot, apiPath, method); const status = await (0, status_manager_1.readStatusJson)(statusPath); const stateKey = getMockStateKey(apiPath, method); const currentMock = mockStates.get(stateKey) || DEFAULT_MOCK_FILE; return res.json({ path: apiPath, method: method, currentMock: status?.selected || currentMock, delayMillisecond: status?.delayMillisecond || 0, }); } catch (error) { return res .status(500) .json({ error: "Failed to get mock status", detail: String(error) }); } }); // New: API for setting delay // 1. /api/set-delay validation and writing const SetDelayRequestSchema = zod_1.z.object({ path: zod_1.z.string(), method: zod_1.z.string(), delayMillisecond: zod_1.z.number().min(0).max(60000), }); app.post("/_mock/set-delay", async (req, res) => { try { const { path: apiPath, method, delayMillisecond, } = SetDelayRequestSchema.parse(req.body); const statusPath = (0, status_manager_1.getStatusJsonPath)(mockRoot, apiPath, method); let status = await (0, status_manager_1.readStatusJson)(statusPath); if (!status) status = { selected: DEFAULT_MOCK_FILE }; await fs_extra_1.default.writeJson(statusPath, { ...status, delayMillisecond }, { spaces: 2 }); console.log(`[status-manager] Set delay ${delayMillisecond}ms for ${statusPath}`); return res.json({ success: true, message: `Delay set to ${delayMillisecond}ms`, }); } catch (error) { if (error instanceof zod_1.z.ZodError) { return res .status(400) .json({ error: "Invalid request data", details: error.errors }); } else { return res .status(500) .json({ error: "Failed to set delay", detail: String(error) }); } } }); // The main mock server logic app.use(async (req, res, next) => { try { const reqPath = req.path.replace(/^\//, ""); const method = req.method.toUpperCase(); const requestParts = reqPath ? reqPath.split("/") : []; // Skip API endpoints if (reqPath.startsWith("api/")) { return next(); } const templates = await getAllMockTemplates(mockRoot); const match = matchTemplate(requestParts, templates, method); let endpointDir; let apiPath; if (match) { endpointDir = path_1.default.join(mockRoot, ...match.template); apiPath = "/" + match.template.slice(0, -1).join("/"); } else { // fallback: exact path endpointDir = path_1.default.join(mockRoot, ...requestParts, method); apiPath = "/" + requestParts.join("/"); } // Select mock response file const stateKey = getMockStateKey(apiPath, method); let mockFile = mockStates.get(stateKey); if (!mockFile) { // fallback: read status.json or default const statusPath = (0, status_manager_1.getStatusJsonPath)(mockRoot, apiPath, method); const status = await (0, status_manager_1.readStatusJson)(statusPath); if (status && status.selected) { mockFile = status.selected; mockStates.set(stateKey, mockFile); console.log(`[status-manager] fallback read ${statusPath} -> ${mockFile}`); } else { mockFile = DEFAULT_MOCK_FILE; mockStates.set(stateKey, mockFile); console.log(`[status-manager] fallback default ${stateKey} -> ${mockFile}`); } } const filePath = path_1.default.join(endpointDir, mockFile); if (!(await fs_extra_1.default.pathExists(filePath))) { return res.status(404).json({ error: "Mock file not found", file: filePath, availableFiles: await getAvailableMockFiles(endpointDir), }); } const mock = await fs_extra_1.default.readJson(filePath); // Set headers if (Array.isArray(mock.header)) { for (const h of mock.header) { if (h && h.key && h.value) { res.setHeader(h.key, h.value); } } } // Set status code const statusMatch = mockFile.match(/-(\d+)\.json$/); if (statusMatch && statusMatch[1]) { res.status(parseInt(statusMatch[1])); } // Read status.json to get delayMillisecond const statusPath = (0, status_manager_1.getStatusJsonPath)(mockRoot, apiPath, method); const status = await (0, status_manager_1.readStatusJson)(statusPath); let delayMillisecond = 0; if (status) { if (typeof status.delayMillisecond === "number" && status.delayMillisecond > 0) { delayMillisecond = status.delayMillisecond; } else if (typeof status.delayMillisecond === "number" && status.delayMillisecond > 0) { // Compatible with old field delayMillisecond = status.delayMillisecond * 1000; } if (delayMillisecond > 0) { console.log(`[status-manager] ${apiPath} ${method} delay ${delayMillisecond}ms`); } } // Delay response if (delayMillisecond > 0) { await new Promise((resolve) => setTimeout(resolve, delayMillisecond)); } return res.json(mock.body); } catch (error) { return res .status(500) .json({ error: "Mock server error", detail: String(error) }); } }); // 404 handling app.use((req, res) => { res.status(404).json({ error: "API endpoint not found" }); }); return app; } // Function to start the server function startMockServer(port = 3001, mockRoot) { const resolvedMockRoot = mockRoot ? path_1.default.resolve(mockRoot) : DEFAULT_MOCK_ROOT; return new Promise((resolve, reject) => { try { const app = createApp(resolvedMockRoot); const server = app.listen(port, () => { console.log(`Mock server running at http://localhost:${port}`); console.log(`API endpoints available at http://localhost:${port}/_mock/endpoints`); console.log(`Mock root directory: ${resolvedMockRoot}`); resolve(); }); server.on("error", (error) => { reject(error); }); } catch (error) { reject(error); } }); } // If this file is run directly, start the server if (__webpack_require__.c[__webpack_require__.s] === module) { const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3001; const MOCK_ROOT = process.env.MOCK_ROOT; startMockServer(PORT, MOCK_ROOT).catch((error) => { console.error("Failed to start mock server:", error); process.exit(1); }); } /***/ }), /***/ 537: /***/ (function(__unused_webpack_module, exports, __webpack_require__) { var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.getStatusJsonPath = getStatusJsonPath; exports.readStatusJson = readStatusJson; exports.writeStatusJson = writeStatusJson; exports.loadAllStatusJson = loadAllStatusJson; const fs_extra_1 = __importDefault(__webpack_require__(652)); const path_1 = __importDefault(__webpack_require__(928)); const DEFAULT_MOCK_FILE = "successful-operation-200.json"; /** * 获取 status.json 路径 * @param mockRoot mock 根目录 * @param endpointPath 形如 /pet/{petId} * @param method HTTP 方法(GET/POST/...) */ function getStatusJsonPath(mockRoot, endpointPath, method) { return path_1.default.join(mockRoot, ...endpointPath.replace(/^\//, "").split("/"), method.toUpperCase(), "status.json"); } /** * 读取 status.json * @param statusPath status.json 文件路径 * @returns StatusJson 对象或 null */ async function readStatusJson(statusPath) { try { const data = await fs_extra_1.default.readJson(statusPath); if (typeof data.selected === "string" && data.selected.endsWith(".json")) { return data; } return null; } catch { return null; } } /** * 写入 status.json(原子写入) * @param statusPath status.json 文件路径 * @param selected 选中的 mock 文件名 */ async function writeStatusJson(statusPath, selected) { await fs_extra_1.default.writeJson(statusPath, { selected }, { spaces: 2 }); } /** * 初始化所有 endpoint 的 mock 选择状态 * @param mockRoot mock 根目录 * @param templates 所有 endpoint 模板数组 * @returns Map<stateKey, selectedMockFile> */ async function loadAllStatusJson(mockRoot, templates) { const stateMap = new Map(); for (const template of templates) { const method = template[template.length - 1] || ""; const endpointPath = "/" + template.slice(0, -1).join("/"); if (!method) continue; // 跳过无效 method const statusPath = getStatusJsonPath(mockRoot, endpointPath, method); const status = await readStatusJson(statusPath); if (status && status.selected) { stateMap.set(`${method.toUpperCase()}:${endpointPath}`, status.selected); } else { // fallback stateMap.set(`${method.toUpperCase()}:${endpointPath}`, DEFAULT_MOCK_FILE); } } return stateMap; } /***/ }), /***/ 569: /***/ ((module) => { module.exports = require("zod"); /***/ }), /***/ 652: /***/ ((module) => { module.exports = require("fs-extra"); /***/ }), /***/ 729: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { // Main exports for the npm package Object.defineProperty(exports, "__esModule", ({ value: true })); exports.generateMockFromSwagger = exports.createMultiServerManager = exports.createMockServer = exports.MultiServerManager = exports.MockServerManager = exports.startMockServer = void 0; // Server functionality var server_1 = __webpack_require__(422); Object.defineProperty(exports, "startMockServer", ({ enumerable: true, get: function () { return server_1.startMockServer; } })); // Server management var mock_server_manager_1 = __webpack_require__(285); Object.defineProperty(exports, "MockServerManager", ({ enumerable: true, get: function () { return mock_server_manager_1.MockServerManager; } })); Object.defineProperty(exports, "MultiServerManager", ({ enumerable: true, get: function () { return mock_server_manager_1.MultiServerManager; } })); Object.defineProperty(exports, "createMockServer", ({ enumerable: true, get: function () { return mock_server_manager_1.createMockServer; } })); Object.defineProperty(exports, "createMultiServerManager", ({ enumerable: true, get: function () { return mock_server_manager_1.createMultiServerManager; } })); // Mock generation var mock_generator_1 = __webpack_require__(755); Object.defineProperty(exports, "generateMockFromSwagger", ({ enumerable: true, get: function () { return mock_generator_1.generateMockFromSwagger; } })); /***/ }), /***/ 755: /***/ (function(module, exports, __webpack_require__) { var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", ({ value: true })); exports.generateMockFromSwagger = generateMockFromSwagger; const fs = __importStar(__webpack_require__(652)); const yaml = __importStar(__webpack_require__(115)); const path = __importStar(__webpack_require__(928)); const swagger_1 = __webpack_require__(315); // Safe filename generation function sanitizeFileName(name) { return name .toLowerCase() .replace(/\s+/g, "-") // Convert spaces to - .replace(/[^a-z0-9\-_]/g, "-") // Convert all illegal characters to - .replace(/-+/g, "-") // Merge consecutive - .replace(/^-|-$/g, "") // Remove leading and trailing - .substring(0, 100); // Limit length } // 递归生成 mock 数据 function generateMockDataFromSchema(schema, components) { if (!schema) return null; // 处理 $ref if (schema.$ref) { const refPath = schema.$ref.replace(/^#\//, "").split("/"); let ref = components; for (const seg of refPath.slice(1)) { if (ref && ref[seg]) { ref = ref[seg]; } else { console.warn(`Warning: Could not resolve $ref: ${schema.$ref}`); return null; } } return generateMockDataFromSchema(ref, components); } // 优先 example if (schema.example !== undefined) return schema.example; // type: object if (schema.type === "object" || schema.properties) { const obj = {}; const props = schema.properties || {}; for (const [key, propSchema] of Object.entries(props)) { obj[key] = generateMockDataFromSchema(propSchema, components); } return obj; } // type: array if (schema.type === "array" && schema.items) { const item = generateMockDataFromSchema(schema.items, components); return [item]; } // type: string if (schema.type === "string") { if (schema.enum && schema.enum.length > 0) { return schema.enum[0]; } if (schema.format === "date-time") return "2023-01-01T00:00:00Z"; if (schema.format === "date") return "2023-01-01"; if (schema.format === "email") return "user@example.com"; if (schema.format === "uri") return "https://example.com"; if (schema.format === "uuid") return "123e4567-e89b-12d3-a456-426614174000"; return "string"; } // type: integer/number if (schema.type === "integer" || schema.type === "number") { if (schema.minimum !== undefined) return schema.minimum; if (schema.maximum !== undefined) return Math.floor(schema.maximum / 2); return 0; } // type: boolean if (schema.type === "boolean") { return true; } // fallback return null; } // 生成默认的响应文件 function generateDefaultResponses() { return { "200": { description: "Successful operation", content: { "application/json": { schema: { type: "object", properties: { success: { type: "boolean" }, message: { type: "string" }, }, }, }, }, }, "400": { description: "Bad request", content: { "application/json": { schema: { type: "object", properties: { error: { type: "string" }, code: { type: "string" }, }, }, }, }, }, "404": { description: "Not found", content: { "application/json": { schema: { type: "object", properties: { error: { type: "string" }, code: { type: "string" }, }, }, }, }, }, "500": { description: "Internal server error", content: { "application/json": { schema: { type: "object", properties: { error: { type: "string" }, code: { type: "string" }, }, }, }, }, }, }; } async function generateMockFromSwagger(swaggerPath, outputPath) { try { console.log(`Reading Swagger file: ${swaggerPath}`); // 读取 swagger yaml const raw = await fs.readFile(swaggerPath, "utf-8"); const doc = yaml.parse(raw); // 类型校验 const swagger = swagger_1.SwaggerDocSchema.parse(doc); const components = swagger.components || {}; console.log(`Processing ${Object.keys(swagger.paths).length} paths...`); // 遍历 paths for (const [apiPath, methods] of Object.entries(swagger.paths)) { for (const [method, operation] of Object.entries(methods)) { const methodUpper = method.toUpperCase(); // 跳过非HTTP方法 if (![ "GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", ].includes(methodUpper)) { continue; } // 构建目录路径 const pathParts = apiPath.replace(/^\//, "").split("/"); const safePathParts = pathParts.map((part) => { // 保持路径参数格式 {paramName} if (part.startsWith("{") && part.endsWith("}")) { return part; } // 其他部分进行安全处理 return sanitizeFileName(part); }); const endpointDir = path.join(outputPath, ...safePathParts, methodUpper); await fs.ensureDir(endpointDir); console.log(`Generated directory: ${endpointDir}`); // 处理响应 const responses = operation.responses || generateDefaultResponses(); for (const [status, resp] of Object.entries(responses)) { const desc = resp.description ? sanitizeFileName(resp.description) : "response"; const fileName = `${desc}-${status}.json`; const filePath = path.join(endpointDir, fileName); // 生成 mock 数据 let mockBody = {}; // 只处理 application/json const content = resp.content?.["application/json"]; if (content && content.schema) { mockBody = generateMockDataFromSchema(content.schema, components); } else { // 如果没有schema,生成默认响应 mockBody = { success: status === "200", message: resp.description || "Response", status: parseInt(status), }; } const mock = { header: [], body: mockBody, }; await fs.writeJson(filePath, mock, { spaces: 2 }); console.log(`Generated mock file: ${filePath}`); } } } console.log("Mock generation completed successfully!"); } catch (error) { console.error("Error generating mock files:", error); throw error; } } // 为了兼容性保留 CommonJS 导出 module.exports = { generateMockFromSwagger }; /***/ }), /***/ 928: /***/ ((module) => { module.exports = require("path"); /***/ }) /******/ }); /************************************************************************/ /******/ // The module cache /******/ var __webpack_module_cache__ = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ var cachedModule = __webpack_module_cache__[moduleId]; /******/ if (cachedModule !== undefined) { /******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = __webpack_module_cache__[moduleId] = { /******/ id: moduleId, /******/ loaded: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.loaded = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ // expose the module cache /******/ __webpack_require__.c = __webpack_module_cache__; /******/ /************************************************************************/ /******/ /* webpack/runtime/node module decorator */ /******/ (() => { /******/ __webpack_require__.nmd = (module) => { /******/ module.paths = []; /******/ if (!module.children) module.children = []; /******/ return module; /******/ }; /******/ })(); /******/ /************************************************************************/ /******/ /******/ // module cache are used so entry inlining is disabled /******/ // startup /******/ // Load entry module and return exports /******/ var __webpack_exports__ = __webpack_require__(__webpack_require__.s = 729); /******/ module.exports = __webpack_exports__; /******/ /******/ })() ; //# sourceMappingURL=index.js.map