@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
JavaScript
/******/ (() => { // 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