vite-plugin-automock 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/mockFileUtils.ts","../src/index.ts","../src/middleware.ts","../src/inspector.ts","../src/mockBundler.ts","../src/client/interceptor.ts"],"sourcesContent":["import path from \"path\";\nimport fs from \"fs-extra\";\nimport prettier from \"prettier\";\n\nexport interface MockFileConfig {\n enable: boolean;\n data: unknown;\n delay: number;\n status: number;\n [key: string]: unknown;\n}\n\nexport interface MockFileInfo {\n config: MockFileConfig;\n serializable: boolean;\n hasDynamicData: boolean;\n headerComment?: string;\n description?: string;\n dataText: string;\n isBinary?: boolean;\n}\n\nexport const DEFAULT_CONFIG: MockFileConfig = {\n enable: true,\n data: null,\n delay: 0,\n status: 200,\n};\n\n// 简单实现 cross-platform 路径分隔符转换\nfunction toPosixPath(p: string): string {\n return p.replace(/\\\\/g, \"/\");\n}\n\n// 检测是否为二进制文件\nconst isBinaryResponse = (\n contentType: string | undefined,\n data: Buffer,\n): boolean => {\n if (!contentType) return false;\n\n const binaryTypes = [\n \"application/octet-stream\",\n \"application/pdf\",\n \"application/zip\",\n \"application/x-zip-compressed\",\n \"application/vnd.ms-excel\",\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n \"application/msword\",\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n \"application/vnd.ms-powerpoint\",\n \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n \"image/\",\n \"video/\",\n \"audio/\",\n ];\n\n return (\n binaryTypes.some((type) => contentType.toLowerCase().includes(type)) ||\n !isBufferTextLike(data)\n );\n};\n\n// 检测Buffer是否像文本数据\nconst isBufferTextLike = (buffer: Buffer): boolean => {\n try {\n // 检查前100字节是否包含非文本字符\n const sample = buffer.slice(0, 100);\n const text = sample.toString(\"utf8\");\n\n // 如果包含null字节或过多的控制字符,可能是二进制\n const nullBytes = [...sample].filter((b) => b === 0).length;\n const controlChars = [...sample].filter(\n (b) => b < 32 && b !== 9 && b !== 10 && b !== 13,\n ).length;\n\n return nullBytes === 0 && controlChars < 5;\n } catch {\n return false;\n }\n};\n\n// 从Content-Type获取文件扩展名\nconst getFileExtension = (\n contentType: string | undefined,\n url: string,\n): string => {\n // 从Content-Type映射\n const mimeMap: Record<string, string> = {\n \"application/json\": \"json\",\n \"application/pdf\": \"pdf\",\n \"application/zip\": \"zip\",\n \"application/vnd.ms-excel\": \"xls\",\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\": \"xlsx\",\n \"application/msword\": \"doc\",\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\":\n \"docx\",\n \"application/vnd.ms-powerpoint\": \"ppt\",\n \"application/vnd.openxmlformats-officedocument.presentationml.presentation\":\n \"pptx\",\n \"image/jpeg\": \"jpg\",\n \"image/png\": \"png\",\n \"image/gif\": \"gif\",\n \"text/plain\": \"txt\",\n \"text/html\": \"html\",\n \"text/css\": \"css\",\n \"application/javascript\": \"js\",\n \"text/xml\": \"xml\",\n };\n\n if (contentType && mimeMap[contentType.toLowerCase()]) {\n return mimeMap[contentType.toLowerCase()];\n }\n\n // 尝试从URL的查询参数中解析文件名\n try {\n const urlObj = new URL(url, \"http://localhost\");\n const fileName = urlObj.searchParams.get(\"file_name\");\n if (fileName) {\n const extensionMatch = fileName.match(/\\.([a-zA-Z0-9]+)$/);\n if (extensionMatch) {\n return extensionMatch[1];\n }\n }\n } catch (error) {\n // 忽略URL解析错误\n }\n\n return \"bin\";\n};\n\nconst saveMockData = async (\n url: string,\n method: string,\n data: Buffer | string,\n rootDir: string,\n statusCode?: number,\n contentType?: string,\n): Promise<string | null> => {\n try {\n const absoluteRootDir = path.isAbsolute(rootDir)\n ? rootDir\n : path.resolve(process.cwd(), rootDir);\n\n // 正确解析URL,处理查询参数和路径\n let pathname: string;\n let search: string;\n\n try {\n // 如果url包含协议信息,直接解析\n if (url.startsWith(\"http\")) {\n const urlObj = new URL(url);\n pathname = urlObj.pathname;\n search = urlObj.search;\n } else {\n // 如果是相对路径,先补全协议\n const urlObj = new URL(url, \"http://localhost\");\n pathname = urlObj.pathname;\n search = urlObj.search;\n }\n } catch (error) {\n // 如果URL解析失败,尝试手动处理\n const [pathPart, ...searchPart] = url.split(\"?\");\n pathname = pathPart;\n search = searchPart.length > 0 ? \"?\" + searchPart.join(\"?\") : \"\";\n }\n\n const filePath = path.join(\n absoluteRootDir,\n pathname.replace(/^\\//, \"\"),\n method.toLowerCase() + \".js\",\n );\n\n const dir = path.dirname(filePath);\n fs.ensureDirSync(dir);\n\n // 检测是否为二进制文件\n const isBuffer = Buffer.isBuffer(data);\n const binaryData = isBuffer ? data : Buffer.from(data || \"\");\n const isBinary = isBinaryResponse(contentType, binaryData);\n\n if (isBinary) {\n // 二进制文件处理\n const extension = getFileExtension(contentType, url);\n const binaryFilePath = filePath.replace(/\\.js$/, \".\" + extension);\n\n // 检查文件是否已存在,如果存在则不覆盖\n if (fs.existsSync(binaryFilePath)) {\n return null;\n }\n\n // 保存二进制文件\n fs.writeFileSync(binaryFilePath, binaryData);\n\n // 创建对应的JS配置文件\n const configContent = `/**\n * Mock data for ${pathname} (${method.toUpperCase()})${search || \"\"}\n * @description ${pathname}${search || \"\"} - Binary file (${extension})\n * Generated at ${new Date().toISOString()}\n */\nexport default {\n enable: false,\n data: {\n __binaryFile: '${extension}',\n __originalPath: '${pathname}',\n __originalQuery: '${search}',\n __originalUrl: '${pathname}${search || \"\"}',\n __contentType: '${contentType}',\n __fileSize: ${binaryData.length}\n },\n delay: 0,\n status: ${statusCode || 200}\n}`;\n\n try {\n const formattedCode = await prettier.format(configContent, {\n parser: \"babel\",\n });\n fs.writeFileSync(filePath, formattedCode, \"utf-8\");\n } catch (error) {\n fs.writeFileSync(filePath, configContent, \"utf-8\");\n }\n\n return filePath;\n } else {\n // JSON文件处理\n // 检查文件是否已存在,如果存在则不覆盖\n if (fs.existsSync(filePath)) {\n return null;\n }\n\n const dataStr = isBuffer ? data.toString(\"utf8\") : data || \"\";\n let jsonData;\n // 如果响应为空或只包含空白字符\n if (!dataStr || dataStr.trim() === \"\") {\n jsonData = {\n error: true,\n message: `Empty response (${statusCode || \"unknown status\"})`,\n status: statusCode || 404,\n data: null,\n };\n } else {\n try {\n jsonData = JSON.parse(dataStr);\n // 如果解析成功且是错误状态码,添加错误标记\n if (statusCode && statusCode >= 400) {\n if (typeof jsonData === \"object\" && jsonData !== null) {\n jsonData = {\n ...jsonData,\n __mockStatusCode: statusCode,\n __isErrorResponse: true,\n };\n } else {\n jsonData = {\n originalData: jsonData,\n __mockStatusCode: statusCode,\n __isErrorResponse: true,\n };\n }\n }\n } catch {\n // 如果不是JSON格式,可能是HTML错误页面或其他格式\n jsonData = {\n error: true,\n message: `Non-JSON response (${statusCode || \"unknown status\"})`,\n status: statusCode || 404,\n data: dataStr,\n __mockStatusCode: statusCode,\n __isErrorResponse: true,\n };\n }\n }\n\n const content = `/**\n * Mock data for ${pathname} (${method.toUpperCase()})${search || \"\"}\n * @description ${pathname}${search || \"\"}\n * Generated at ${new Date().toISOString()}\n */\nexport default {\n enable: false,\n data: ${JSON.stringify(jsonData)},\n delay: 0,\n status: ${statusCode || 200}\n}`;\n\n try {\n const formattedCode = await prettier.format(content, {\n parser: \"babel\",\n });\n fs.writeFileSync(filePath, formattedCode, \"utf-8\");\n return filePath;\n } catch (error) {\n fs.writeFileSync(filePath, content, \"utf-8\");\n return filePath;\n }\n }\n } catch (error) {\n console.error(`Failed to save mock data for ${url}:`, error);\n console.error(\n `URL details: url=${url}, method=${method}, statusCode=${statusCode}, contentType=${contentType}`,\n );\n throw error;\n }\n};\n\nconst recursiveReadAllFiles = (dir: string): string[] => {\n if (!fs.existsSync(dir)) return [];\n\n const files: string[] = [];\n try {\n const list = fs.readdirSync(dir);\n list.forEach((file) => {\n const filePath = path.join(dir, file);\n const stat = fs.statSync(filePath);\n if (stat.isDirectory()) {\n files.push(...recursiveReadAllFiles(filePath));\n } else {\n files.push(filePath);\n }\n });\n } catch (error) {\n console.error(`Error reading directory ${dir}:`, error);\n }\n return files;\n};\n\nconst buildMockIndex = (mockDir: string): Map<string, string> => {\n const mockFileMap = new Map<string, string>();\n\n if (!fs.existsSync(mockDir)) {\n fs.ensureDirSync(mockDir);\n return mockFileMap;\n }\n\n const files = recursiveReadAllFiles(mockDir);\n\n files.forEach((filePath) => {\n if (!filePath.endsWith(\".js\")) {\n return;\n }\n\n try {\n const relativePath = path.relative(mockDir, filePath);\n const method = path.basename(filePath, \".js\");\n const dirPath = path.dirname(relativePath);\n const urlPath = \"/\" + toPosixPath(dirPath);\n const absolutePath = path.isAbsolute(filePath)\n ? filePath\n : path.resolve(process.cwd(), filePath);\n const key = `${urlPath}/${method}.js`.toLowerCase();\n\n mockFileMap.set(key, absolutePath);\n } catch (error) {\n console.error(`❌ [automock] 处理文件失败 ${filePath}:`, error);\n }\n });\n\n return mockFileMap;\n};\n\nconst parseMockModule = async (filePath: string): Promise<MockFileInfo> => {\n const absolutePath = path.isAbsolute(filePath)\n ? filePath\n : path.resolve(process.cwd(), filePath);\n\n if (!fs.existsSync(absolutePath)) {\n throw new Error(`Mock file does not exist: ${absolutePath}`);\n }\n\n const content = fs.readFileSync(absolutePath, \"utf-8\");\n const headerCommentMatch = content.match(/^(\\/\\*\\*[\\s\\S]*?\\*\\/)/);\n const headerComment = headerCommentMatch ? headerCommentMatch[1] : undefined;\n\n // 提取 @description\n let description: string | undefined;\n if (headerComment) {\n const descMatch = headerComment.match(/@description\\s+(.+?)(?:\\n|\\*\\/)/s);\n if (descMatch) {\n description = descMatch[1].trim();\n }\n }\n\n const { default: mockModule } = await import(\n `${absolutePath}?t=${Date.now()}`\n );\n\n const exportedConfig =\n typeof mockModule === \"function\" ? mockModule() : mockModule;\n const hasDynamicData = typeof exportedConfig?.data === \"function\";\n const config: MockFileConfig = {\n ...DEFAULT_CONFIG,\n ...(exportedConfig ?? {}),\n };\n\n const serializable = !hasDynamicData;\n const dataObj =\n typeof config.data === \"object\" && config.data !== null\n ? (config.data as Record<string, unknown>)\n : null;\n const isBinary = dataObj !== null && \"__binaryFile\" in dataObj;\n\n return {\n config,\n serializable,\n hasDynamicData,\n headerComment,\n description,\n dataText: isBinary\n ? `/* Binary file: ${dataObj?.__binaryFile} */`\n : serializable\n ? JSON.stringify(config.data ?? null, null, 2)\n : \"/* data is generated dynamically and cannot be edited here */\",\n isBinary,\n };\n};\n\nconst writeMockFile = async (\n filePath: string,\n mockInfo: Pick<MockFileInfo, \"config\" | \"headerComment\" | \"description\">,\n): Promise<void> => {\n const absolutePath = path.isAbsolute(filePath)\n ? filePath\n : path.resolve(process.cwd(), filePath);\n\n let header = mockInfo.headerComment || \"\";\n\n // 如果提供了 description,更新或添加到注释中\n if (mockInfo.description !== undefined) {\n if (header) {\n // 替换现有的 @description\n if (/@description/.test(header)) {\n header = header.replace(\n /@description\\s+.+?(?=\\n|\\*\\/)/s,\n `@description ${mockInfo.description}`,\n );\n } else {\n // 在最后一行之前添加 @description\n header = header.replace(\n /\\*\\//,\n ` * @description ${mockInfo.description}\\n */`,\n );\n }\n }\n }\n\n const content = header ? `${header}\\n` : \"\";\n const finalContent = `${content}export default ${JSON.stringify(mockInfo.config, null, 2)}\\n`;\n\n try {\n const formattedCode = await prettier.format(finalContent, {\n parser: \"babel\",\n });\n fs.writeFileSync(absolutePath, formattedCode, \"utf-8\");\n } catch (error) {\n console.error(\"Error formatting code with prettier:\", error);\n fs.writeFileSync(absolutePath, finalContent, \"utf-8\");\n }\n};\n\nexport { saveMockData, buildMockIndex, parseMockModule, writeMockFile };\n","import path from 'path'\nimport fs from 'fs-extra'\n\nimport { automock as createAutomockPlugin } from './middleware'\nimport { bundleMockFiles, writeMockBundle } from './mockBundler'\nimport { buildMockIndex } from './mockFileUtils'\nimport type { Plugin } from 'vite'\nimport type { AutomockPluginOptions } from './types'\nimport type { MockBundle } from './mockBundler'\n\nexport type { AutomockPluginOptions, InspectorOptions } from './types'\nexport { saveMockData, buildMockIndex, parseMockModule, writeMockFile } from './mockFileUtils'\nexport type { MockBundle, MockBundleData } from './mockBundler'\nexport { bundleMockFiles, writeMockBundle } from './mockBundler'\n\n// Client interceptor exports\nexport {\n createMockInterceptor,\n initMockInterceptor,\n initMockInterceptorForPureHttp,\n setMockEnabled,\n isMockEnabled,\n loadMockData,\n registerHttpInstance\n} from './client'\nexport type { MockInterceptorOptions, MockBundleData as ClientMockBundleData } from './client/interceptor'\n\nexport interface AutomockWithBundleOptions extends AutomockPluginOptions {\n bundleMockData?: boolean\n bundleOutputPath?: string\n}\n\nexport function automock(options: AutomockWithBundleOptions = {}): Plugin {\n const {\n bundleMockData = true,\n bundleOutputPath = 'public/mock-data.json',\n ...pluginOptions\n } = options\n\n const basePlugin = createAutomockPlugin(pluginOptions)\n\n // Store bundle data to write it later\n let cachedBundle: MockBundle | null = null\n\n return {\n ...basePlugin,\n name: 'vite-plugin-automock-with-bundle',\n buildEnd: async () => {\n if (bundleMockData && pluginOptions.productionMock !== false) {\n try {\n const mockDir = pluginOptions.mockDir || path.join(process.cwd(), 'mock')\n\n // Check if mock directory exists\n if (!fs.existsSync(mockDir)) {\n console.log('[automock] Mock directory not found, skipping bundle generation')\n console.log('[automock] Using existing mock-data.json if present')\n return\n }\n\n // Check if there are any mock files\n const mockFileMap = buildMockIndex(mockDir)\n if (mockFileMap.size === 0) {\n console.log('[automock] No mock files found, skipping bundle generation')\n return\n }\n\n // Cache the bundle for later use\n cachedBundle = await bundleMockFiles({ mockDir })\n\n const outputPath = path.join(process.cwd(), bundleOutputPath)\n\n writeMockBundle(cachedBundle, outputPath)\n console.log(`[automock] Mock bundle written to: ${outputPath}`)\n } catch (error) {\n console.error('[automock] Failed to bundle mock data:', error)\n }\n }\n },\n writeBundle: async () => {\n // Re-write the file if it was deleted by other plugins\n const outputPath = path.join(process.cwd(), bundleOutputPath)\n if (!fs.existsSync(outputPath) && cachedBundle) {\n console.log('[automock] Re-writing mock bundle in writeBundle...')\n writeMockBundle(cachedBundle, outputPath)\n }\n },\n closeBundle: async () => {\n // Final check and re-write if needed\n const outputPath = path.join(process.cwd(), bundleOutputPath)\n if (!fs.existsSync(outputPath) && cachedBundle) {\n console.log('[automock] Re-writing mock bundle in closeBundle...')\n writeMockBundle(cachedBundle, outputPath)\n }\n }\n }\n}\n\n","import chokidar from \"chokidar\";\nimport debounce from \"lodash.debounce\";\nimport path from \"path\";\nimport fs from \"fs-extra\";\nimport http from \"http\";\nimport https from \"https\";\n\nimport { buildMockIndex, saveMockData } from \"./mockFileUtils\";\nimport { createInspectorHandler } from \"./inspector\";\n\nimport type { Plugin, ViteDevServer } from \"vite\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\nimport type { FSWatcher } from \"chokidar\";\nimport type { AutomockPluginOptions } from \"./types\";\nimport { normalizeInspectorConfig } from \"./inspector\";\n\nexport function automock(options: AutomockPluginOptions): Plugin {\n const {\n mockDir: configMockDir = path.join(process.cwd(), \"mock\"),\n apiPrefix = \"/api\",\n pathRewrite = (p) => p,\n proxyBaseUrl,\n inspector = false,\n } = options;\n\n const mockDir = path.isAbsolute(configMockDir)\n ? configMockDir\n : path.resolve(process.cwd(), configMockDir);\n\n if (!fs.existsSync(mockDir)) {\n try {\n fs.ensureDirSync(mockDir);\n console.log(`✅ [automock] Mock directory created: ${mockDir}`);\n } catch (error) {\n console.error(`❌ [automock] 创建 Mock 目录失败: ${mockDir}`, error);\n }\n }\n\n let watcher: FSWatcher | undefined;\n\n return {\n name: \"vite-plugin-automock\",\n config() {},\n configureServer(server) {\n let mockFileMap = buildMockIndex(mockDir);\n console.log(`✅ [automock] Loaded ${mockFileMap.size} mock files`);\n\n const rebuildMockFileMap = debounce(() => {\n mockFileMap = buildMockIndex(mockDir);\n }, 200);\n\n watcher = chokidar.watch(mockDir, {\n ignoreInitial: true,\n persistent: true,\n depth: 30,\n });\n watcher.on(\"add\", () => rebuildMockFileMap());\n watcher.on(\"unlink\", () => rebuildMockFileMap());\n\n server.httpServer?.on(\"close\", () => {\n watcher?.close();\n });\n\n const inspectorHandler = createInspectorHandler({\n inspector,\n apiPrefix,\n mockDir,\n getMockFileMap: () => mockFileMap,\n });\n\n // 在服务器启动后打印 Inspector URL\n if (inspector) {\n const inspectorConfig = normalizeInspectorConfig(inspector);\n server.httpServer?.once(\"listening\", () => {\n setTimeout(() => {\n const address = server.httpServer?.address();\n if (address && typeof address === \"object\") {\n const protocol = server.config.server.https ? \"https\" : \"http\";\n const host =\n address.address === \"::\" || address.address === \"0.0.0.0\"\n ? \"localhost\"\n : address.address;\n const port = address.port;\n const inspectorUrl = `${protocol}://${host}:${port}${inspectorConfig.route}`;\n console.log(\n ` ➜ \\x1b[36mMock Inspector\\x1b[0m: \\x1b[1m${inspectorUrl}\\x1b[0m`,\n );\n }\n }, 100);\n });\n }\n\n server.middlewares.use(\n async (\n req: IncomingMessage,\n res: ServerResponse,\n next: (err?: Error | unknown) => void,\n ): Promise<void> => {\n if (inspectorHandler && req.url) {\n const handled = await inspectorHandler(req, res);\n if (handled) {\n return;\n }\n }\n\n if (!req.url?.startsWith(apiPrefix)) {\n return next();\n }\n\n const method = (req.method || \"GET\").toLowerCase();\n const urlObj = new URL(req.url, \"http://localhost\");\n const pathname = urlObj.pathname;\n const key = `${pathname}/${method}.js`.toLowerCase();\n const isExist = mockFileMap.has(key);\n\n if (isExist) {\n try {\n const mockFilePath = mockFileMap.get(key)!;\n const absolutePath = path.isAbsolute(mockFilePath)\n ? mockFilePath\n : path.resolve(process.cwd(), mockFilePath);\n\n if (!fs.existsSync(absolutePath)) {\n throw new Error(`Mock file does not exist: ${absolutePath}`);\n }\n\n const { default: mockModule } = await import(\n `${absolutePath}?t=${Date.now()}`\n );\n\n const mockResult =\n typeof mockModule.data === \"function\"\n ? mockModule.data()\n : mockModule;\n\n const {\n enable = true,\n data,\n delay = 0,\n status = 200,\n } = mockResult || {};\n\n if (enable) {\n setTimeout(() => {\n // 检查是否为二进制文件mock\n const isBinaryMock = data?.__binaryFile;\n\n if (isBinaryMock) {\n // 处理二进制文件mock\n const binaryFilePath = absolutePath.replace(\n /\\.js$/,\n \".\" + data.__binaryFile,\n );\n\n if (fs.existsSync(binaryFilePath)) {\n try {\n const binaryData = fs.readFileSync(binaryFilePath);\n\n // 设置正确的content-type\n const contentType =\n data.__contentType || \"application/octet-stream\";\n res.setHeader(\"Content-Type\", contentType);\n res.setHeader(\"Content-Length\", binaryData.length);\n res.setHeader(\"X-Mock-Response\", \"true\");\n res.setHeader(\"X-Mock-Source\", \"vite-plugin-automock\");\n res.setHeader(\"X-Mock-Binary-File\", \"true\");\n res.statusCode = status;\n res.end(binaryData);\n } catch (error) {\n console.error(\n \"❌ [automock] 读取二进制mock文件失败:\",\n error,\n );\n res.statusCode = 500;\n res.end(\n JSON.stringify({\n error: \"Failed to read binary mock file\",\n }),\n );\n }\n } else {\n console.error(\n \"❌ [automock] 二进制mock文件不存在:\",\n binaryFilePath,\n );\n res.statusCode = 404;\n res.end(\n JSON.stringify({ error: \"Binary mock file not found\" }),\n );\n }\n } else {\n // 处理普通JSON mock\n res.setHeader(\n \"Content-Type\",\n \"application/json; charset=utf-8\",\n );\n res.setHeader(\"X-Mock-Response\", \"true\");\n res.setHeader(\"X-Mock-Source\", \"vite-plugin-automock\");\n res.statusCode = status;\n res.end(\n typeof data === \"string\" ? data : JSON.stringify(data),\n );\n }\n }, delay);\n return;\n }\n } catch (error) {\n console.error(`❌ [automock] 加载 mock 文件失败:`, error);\n }\n }\n\n const shouldSaveMockData = !isExist;\n\n if (!proxyBaseUrl) {\n res.statusCode = 404;\n res.end(\n JSON.stringify({\n error: \"No mock found and proxyBaseUrl not configured\",\n }),\n );\n return;\n }\n\n try {\n const targetUrl = proxyBaseUrl + pathRewrite(req.url || \"\");\n const client = targetUrl.startsWith(\"https\") ? https : http;\n\n if (\n req.method === \"POST\" ||\n req.method === \"PUT\" ||\n req.method === \"PATCH\"\n ) {\n let bodyStr = \"\";\n req.on(\"data\", (chunk) => {\n bodyStr += chunk.toString();\n });\n req.on(\"end\", () => {\n sendProxyRequest(bodyStr);\n });\n } else {\n sendProxyRequest();\n }\n\n function sendProxyRequest(body?: string) {\n // Parse proxyBaseUrl to get the correct host for the Host header\n const targetUrlObj = new URL(proxyBaseUrl);\n const proxyOptions = {\n method: req.method,\n headers: {\n ...req.headers,\n host: targetUrlObj.host, // Override Host header with target server's host\n },\n rejectUnauthorized: false,\n };\n\n const proxyReq = client.request(\n targetUrl,\n proxyOptions,\n (proxyRes) => {\n const contentType = proxyRes.headers[\"content-type\"];\n const chunks: Buffer[] = [];\n\n proxyRes.on(\"data\", (chunk) => {\n chunks.push(chunk);\n });\n proxyRes.on(\"end\", async () => {\n try {\n // 将所有chunk合并为一个Buffer\n const responseData = Buffer.concat(chunks);\n\n if (shouldSaveMockData) {\n try {\n console.log(\n `🔄 [automock] 尝试保存 mock: ${req.url!} -> ${pathname}`,\n );\n const savedFilePath = await saveMockData(\n req.url!,\n method,\n responseData,\n mockDir,\n proxyRes.statusCode,\n contentType,\n );\n if (savedFilePath) {\n console.log(\n `✅ [automock] 已保存 mock: ${pathname}`,\n );\n mockFileMap = buildMockIndex(mockDir);\n } else {\n console.log(\n `ℹ️ [automock] mock 文件已存在,跳过: ${pathname}`,\n );\n }\n } catch (saveError) {\n console.error(\n \"❌ [automock] 保存 mock 失败:\",\n saveError,\n );\n }\n }\n\n // 返回响应,保持原始的content-type和headers\n res.writeHead(\n proxyRes.statusCode || 200,\n proxyRes.headers,\n );\n res.end(responseData);\n } catch (error) {\n console.error(\"❌ [automock] 处理响应失败:\", error);\n res.writeHead(\n proxyRes.statusCode || 200,\n proxyRes.headers,\n );\n res.end(Buffer.concat(chunks));\n }\n });\n },\n );\n\n proxyReq.on(\"error\", (err) => {\n console.error(\"❌ [automock] 代理请求失败:\", err);\n res.statusCode = 500;\n res.end(JSON.stringify({ error: err.message }));\n });\n\n if (\n body &&\n (req.method === \"POST\" ||\n req.method === \"PUT\" ||\n req.method === \"PATCH\")\n ) {\n proxyReq.write(body);\n }\n\n proxyReq.end();\n }\n } catch (error) {\n console.error(\"❌ [automock] 代理请求异常:\", error);\n res.statusCode = 500;\n res.end(JSON.stringify({ error: \"Internal server error\" }));\n }\n },\n );\n },\n closeBundle() {\n watcher?.close();\n },\n transform() {\n return null;\n },\n };\n}\n","import path from \"path\";\nimport { parseMockModule, writeMockFile } from \"./mockFileUtils\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\nimport type { AutomockPluginOptions, InspectorOptions } from \"./types\";\nimport type { MockFileConfig } from \"./mockFileUtils\";\n\ninterface InspectorContext {\n req: IncomingMessage;\n res: ServerResponse;\n mockDir: string;\n inspectorRoute: string;\n apiPrefix: string;\n inspectorConfig: Required<InspectorOptions>;\n getMockFileMap: () => Map<string, string>;\n}\n\ninterface InspectorHandlerOptions {\n inspector: AutomockPluginOptions[\"inspector\"];\n apiPrefix: string;\n mockDir: string;\n getMockFileMap: () => Map<string, string>;\n}\n\ninterface InspectorApiContext extends InspectorContext {\n pathname: string;\n}\n\ninterface InspectorPayload {\n key: string;\n config?: Partial<MockFileConfig>;\n description?: string;\n}\n\nexport const DEFAULT_ROUTE = \"/__mock/\";\n\nfunction ensureTrailingSlash(route: string): string {\n return route.endsWith(\"/\") ? route : `${route}/`;\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n\nexport function normalizeInspectorConfig(\n input: AutomockPluginOptions[\"inspector\"],\n): Required<InspectorOptions> {\n if (input === false || input === undefined) {\n return { route: DEFAULT_ROUTE, enableToggle: true };\n }\n if (input === true) {\n return { route: DEFAULT_ROUTE, enableToggle: true };\n }\n return {\n route: ensureTrailingSlash(input.route ?? DEFAULT_ROUTE),\n enableToggle: input.enableToggle ?? true,\n };\n}\n\nexport type InspectorRequestHandler = (\n req: IncomingMessage,\n res: ServerResponse,\n) => Promise<boolean>;\n\nexport function createInspectorHandler(\n options: InspectorHandlerOptions,\n): InspectorRequestHandler | null {\n if (!options.inspector) {\n return null;\n }\n\n const inspectorConfig = normalizeInspectorConfig(options.inspector);\n const inspectorRoute = ensureTrailingSlash(inspectorConfig.route);\n\n return async (req, res) => {\n if (!req.url) {\n return false;\n }\n const url = new URL(req.url, \"http://localhost\");\n if (!url.pathname.startsWith(inspectorRoute)) {\n return false;\n }\n\n await handleInspectorRequest({\n req,\n res,\n mockDir: options.mockDir,\n inspectorRoute,\n apiPrefix: options.apiPrefix,\n inspectorConfig,\n getMockFileMap: options.getMockFileMap,\n });\n\n return true;\n };\n}\n\nasync function handleInspectorRequest(\n context: InspectorContext,\n): Promise<void> {\n const { req, res, inspectorRoute } = context;\n const url = new URL(req.url || inspectorRoute, \"http://localhost\");\n const normalizedRoute = ensureTrailingSlash(inspectorRoute);\n\n if (\n url.pathname === normalizedRoute.slice(0, -1) ||\n url.pathname === normalizedRoute\n ) {\n await serveInspectorHtml(context);\n return;\n }\n\n const relativePath = url.pathname.startsWith(normalizedRoute)\n ? url.pathname.slice(normalizedRoute.length)\n : null;\n\n if (relativePath && relativePath.startsWith(\"api/\")) {\n await handleInspectorApi({ ...context, pathname: relativePath.slice(4) });\n return;\n }\n\n res.statusCode = 404;\n res.end(\"Not Found\");\n}\n\nasync function serveInspectorHtml({\n res,\n inspectorRoute,\n apiPrefix,\n inspectorConfig,\n}: InspectorContext): Promise<void> {\n const routeJson = JSON.stringify(ensureTrailingSlash(inspectorRoute));\n const allowToggleJson = JSON.stringify(inspectorConfig.enableToggle);\n const apiPrefixEscaped = escapeHtml(apiPrefix);\n\n const html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Mock Inspector</title>\n <style>\n @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');\n\n :root {\n --bg-primary: #ffffff;\n --bg-secondary: #f9fafb;\n --bg-tertiary: #f3f4f6;\n --bg-hover: #e5e7eb;\n --border-color: #e5e7eb;\n --border-subtle: #f3f4f6;\n --text-primary: #111827;\n --text-secondary: #4b5563;\n --text-muted: #9ca3af;\n --accent-indigo: #6366f1;\n --accent-indigo-light: #e0e7ff;\n --accent-indigo-hover: #4f46e5;\n --accent-emerald: #10b981;\n --accent-emerald-light: #d1fae5;\n --accent-amber: #f59e0b;\n --accent-amber-light: #fef3c7;\n --accent-rose: #ef4444;\n --accent-rose-light: #fee2e2;\n --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);\n --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);\n --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);\n --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);\n --radius-sm: 6px;\n --radius-md: 8px;\n --radius-lg: 12px;\n }\n\n * {\n box-sizing: border-box;\n }\n\n body {\n margin: 0;\n background: linear-gradient(135deg, #f8fafc 0%, #e0e7ff 25%, #fdf4ff 50%, #ecfdf5 75%, #f0fdf4 100%);\n color: var(--text-primary);\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n display: flex;\n flex-direction: column;\n height: 100vh;\n overflow: hidden;\n position: relative;\n }\n\n /* Multiple ambient gradient orbs */\n body::before {\n content: '';\n position: fixed;\n top: -15%;\n right: -10%;\n width: 60vw;\n height: 60vw;\n background: radial-gradient(circle, rgba(99, 102, 241, 0.25) 0%, rgba(139, 92, 246, 0.15) 30%, transparent 70%);\n filter: blur(100px);\n pointer-events: none;\n z-index: 0;\n animation: float 20s ease-in-out infinite;\n }\n\n body::after {\n content: '';\n position: fixed;\n bottom: -15%;\n left: -10%;\n width: 50vw;\n height: 50vw;\n background: radial-gradient(circle, rgba(16, 185, 129, 0.2) 0%, rgba(34, 197, 94, 0.12) 30%, transparent 70%);\n filter: blur(100px);\n pointer-events: none;\n z-index: 0;\n animation: float 25s ease-in-out infinite reverse;\n }\n\n @keyframes float {\n 0%, 100% { transform: translate(0, 0) scale(1); }\n 33% { transform: translate(30px, -30px) scale(1.05); }\n 66% { transform: translate(-20px, 20px) scale(0.95); }\n }\n\n /* Page load animation */\n @keyframes fadeSlideIn {\n from {\n opacity: 0;\n transform: translateY(8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n\n body > * {\n animation: fadeSlideIn 0.3s ease-out backwards;\n }\n\n header {\n padding: 1rem 1.5rem;\n display: flex;\n align-items: center;\n gap: 1rem;\n border-bottom: 1px solid rgba(99, 102, 241, 0.1);\n background: linear-gradient(135deg, rgba(255, 255, 255, 0.85) 0%, rgba(238, 242, 255, 0.75) 50%, rgba(250, 245, 255, 0.85) 100%);\n backdrop-filter: blur(20px);\n flex-shrink: 0;\n position: relative;\n z-index: 1;\n box-shadow: 0 4px 30px rgba(99, 102, 241, 0.1);\n }\n\n header h1 {\n font-size: 1.1rem;\n margin: 0;\n font-weight: 600;\n color: var(--text-primary);\n letter-spacing: -0.01em;\n background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 50%, #ec4899 100%);\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n background-clip: text;\n }\n\n main {\n flex: 1;\n display: grid;\n grid-template-columns: var(--sidebar-width, 380px) 4px 1fr;\n background: transparent;\n min-height: 0;\n overflow: hidden;\n position: relative;\n z-index: 1;\n }\n\n aside {\n background: linear-gradient(180deg, rgba(238, 242, 255, 0.5) 0%, rgba(250, 245, 255, 0.4) 50%, rgba(236, 253, 245, 0.5) 100%);\n backdrop-filter: blur(15px);\n overflow-y: auto;\n overflow-x: hidden;\n min-width: 200px;\n max-width: 800px;\n height: 100%;\n border-right: 1px solid rgba(99, 102, 241, 0.15);\n }\n\n aside::-webkit-scrollbar {\n width: 6px;\n }\n\n aside::-webkit-scrollbar-track {\n background: transparent;\n }\n\n aside::-webkit-scrollbar-thumb {\n background: var(--border-color);\n border-radius: 3px;\n }\n\n aside::-webkit-scrollbar-thumb:hover {\n background: var(--text-muted);\n }\n\n .resizer {\n background: var(--border-color);\n cursor: col-resize;\n position: relative;\n user-select: none;\n transition: all 0.2s ease;\n }\n\n .resizer:hover,\n .resizer.active {\n background: var(--accent-indigo);\n }\n\n .resizer::after {\n content: '';\n position: absolute;\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n width: 3px;\n height: 32px;\n background: var(--text-muted);\n border-radius: 2px;\n opacity: 0;\n transition: opacity 0.2s ease;\n }\n\n .resizer:hover::after,\n .resizer.active::after {\n opacity: 1;\n background: white;\n }\n\n .global-controls {\n padding: 0.75rem 1rem;\n border-bottom: 1px solid var(--border-color);\n display: flex;\n gap: 0.5rem;\n background: var(--bg-secondary);\n position: sticky;\n top: 0;\n z-index: 10;\n }\n\n .global-controls .secondary {\n flex: 1;\n padding: 0.45rem 0.65rem;\n font-size: 0.7rem;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.3rem;\n font-weight: 500;\n }\n\n .global-controls .secondary:hover {\n background: var(--accent-indigo-light);\n border-color: var(--accent-indigo);\n color: var(--accent-indigo);\n }\n\n /* Tree view styles */\n .tree-node {\n user-select: none;\n }\n\n .tree-node-content {\n display: flex;\n align-items: center;\n padding: 0.4rem 0.6rem;\n cursor: pointer;\n transition: all 0.2s ease;\n border-bottom: 1px solid var(--border-subtle);\n position: relative;\n }\n\n .tree-node-content::before {\n content: '';\n position: absolute;\n inset: 0;\n background: linear-gradient(135deg, rgba(139, 92, 246, 0.15) 0%, rgba(236, 72, 153, 0.1) 100%);\n opacity: 0;\n transition: opacity 0.2s ease;\n border-radius: var(--radius-sm);\n }\n\n .tree-node-content:hover::before {\n opacity: 1;\n }\n\n .tree-node-content > * {\n position: relative;\n z-index: 1;\n }\n\n .tree-node-content.selected {\n background: linear-gradient(135deg, rgba(139, 92, 246, 0.2) 0%, rgba(236, 72, 153, 0.15) 100%);\n box-shadow: 0 4px 15px rgba(139, 92, 246, 0.25);\n }\n\n .tree-expand-icon {\n width: 18px;\n height: 18px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-right: 0.25rem;\n transition: transform 0.2s ease;\n cursor: pointer;\n color: var(--text-muted);\n font-size: 0.65rem;\n }\n\n .tree-expand-icon.expanded {\n transform: rotate(90deg);\n }\n\n .tree-expand-icon.hidden {\n visibility: hidden;\n }\n\n .tree-node-checkbox {\n appearance: none;\n -webkit-appearance: none;\n width: 16px;\n height: 16px;\n cursor: pointer;\n margin-right: 0.5rem;\n border: 2px solid var(--border-color);\n border-radius: 4px;\n background: var(--bg-primary);\n position: relative;\n transition: all 0.15s ease;\n flex-shrink: 0;\n }\n\n .tree-node-checkbox:hover {\n border-color: var(--accent-emerald);\n }\n\n .tree-node-checkbox:checked {\n background: var(--bg-primary);\n border-color: var(--accent-emerald);\n }\n\n .tree-node-checkbox:checked::after {\n content: '';\n position: absolute;\n top: 50%;\n left: 50%;\n width: 3px;\n height: 6px;\n border: solid var(--accent-emerald);\n border-width: 0 2px 2px 0;\n transform: translate(-50%, -60%) rotate(45deg);\n }\n\n .tree-node-checkbox:indeterminate {\n background: var(--bg-primary);\n border-color: var(--accent-emerald);\n }\n\n .tree-node-checkbox:indeterminate::after {\n content: '';\n position: absolute;\n top: 50%;\n left: 50%;\n width: 8px;\n height: 2px;\n background: var(--accent-emerald);\n transform: translate(-50%, -50%);\n }\n\n .tree-node-label {\n flex: 1;\n font-size: 0.82rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n }\n\n .tree-node-label.folder {\n font-weight: 600;\n color: var(--text-primary);\n display: flex;\n align-items: center;\n gap: 0.25rem;\n }\n\n .tree-node-label.folder .tree-node-count {\n flex-shrink: 0;\n }\n\n .tree-node-label.file {\n color: var(--text-secondary);\n }\n\n .tree-node-method {\n font-size: 0.65rem;\n padding: 0.15rem 0.45rem;\n border-radius: var(--radius-sm);\n margin-right: 0.4rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n }\n\n .tree-node-method.get {\n background: linear-gradient(135deg, var(--accent-emerald-light) 0%, rgba(16, 185, 129, 0.15) 100%);\n color: #047857;\n box-shadow: 0 2px 6px rgba(16, 185, 129, 0.15);\n }\n\n .tree-node-method.post {\n background: linear-gradient(135deg, var(--accent-amber-light) 0%, rgba(245, 158, 11, 0.15) 100%);\n color: #b45309;\n box-shadow: 0 2px 6px rgba(245, 158, 11, 0.15);\n }\n\n .tree-node-method.put {\n background: linear-gradient(135deg, var(--accent-indigo-light) 0%, rgba(99, 102, 241, 0.15) 100%);\n color: #4338ca;\n box-shadow: 0 2px 6px rgba(99, 102, 241, 0.15);\n }\n\n .tree-node-method.delete {\n background: linear-gradient(135deg, var(--accent-rose-light) 0%, rgba(239, 68, 68, 0.15) 100%);\n color: #b91c1c;\n box-shadow: 0 2px 6px rgba(239, 68, 68, 0.15);\n }\n\n .tree-node-method.patch {\n background: linear-gradient(135deg, #ede9fe 0%, rgba(139, 92, 246, 0.15) 100%);\n color: #7c3aed;\n box-shadow: 0 2px 6px rgba(139, 92, 246, 0.15);\n }\n\n .tree-children {\n padding-left: 1rem;\n display: none;\n }\n\n .tree-children.expanded {\n display: block;\n }\n\n .tree-node-count {\n font-size: 0.7rem;\n color: var(--text-muted);\n margin-left: 0.4rem;\n }\n\n .tree-node-delete {\n display: none;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n margin-left: auto;\n border-radius: var(--radius-sm);\n cursor: pointer;\n color: var(--accent-rose);\n font-size: 0.85rem;\n transition: all 0.15s ease;\n user-select: none;\n }\n\n .tree-node-delete:hover {\n background: var(--accent-rose-light);\n }\n\n .tree-node-content:hover .tree-node-delete {\n display: flex;\n }\n\n #mock-details {\n height: calc(100% - 60px);\n }\n\n section {\n background: linear-gradient(180deg, rgba(255, 255, 255, 0.6) 0%, rgba(238, 242, 255, 0.5) 50%, rgba(250, 245, 255, 0.6) 100%);\n backdrop-filter: blur(15px);\n padding: 1.5rem;\n overflow-y: auto;\n overflow-x: hidden;\n display: flex;\n flex-direction: column;\n gap: 1rem;\n height: 100%;\n }\n\n section::-webkit-scrollbar {\n width: 6px;\n }\n\n section::-webkit-scrollbar-track {\n background: transparent;\n }\n\n section::-webkit-scrollbar-thumb {\n background: var(--border-color);\n border-radius: 3px;\n }\n\n section::-webkit-scrollbar-thumb:hover {\n background: var(--text-muted);\n }\n\n section > h3 {\n margin: 0;\n flex-shrink: 0;\n color: var(--text-primary);\n font-size: 0.85rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n }\n\n section .data-container {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n height: 100%;\n }\n\n .controls {\n display: flex;\n flex-wrap: wrap;\n gap: 1rem;\n align-items: flex-start;\n flex-shrink: 0;\n }\n\n .controls h2 {\n width: 100%;\n margin: 0 0 0.75rem 0;\n font-size: 1rem;\n display: flex;\n align-items: center;\n gap: 0.75rem;\n }\n\n .controls label input[type=\"text\"] {\n padding: 0.4rem 0.6rem;\n border-radius: var(--radius-sm);\n border: 1px solid var(--border-color);\n background: var(--bg-primary);\n color: var(--text-primary);\n font-family: inherit;\n font-size: 0.8rem;\n outline: none;\n transition: all 0.15s ease;\n }\n\n .controls label input[type=\"text\"]:focus {\n border-color: var(--accent-indigo);\n box-shadow: 0 0 0 3px var(--accent-indigo-light);\n }\n\n .badge {\n display: inline-flex;\n align-items: center;\n gap: 0.25rem;\n border-radius: var(--radius-sm);\n padding: 0.25rem 0.65rem;\n font-size: 0.65rem;\n font-weight: 600;\n background: linear-gradient(135deg, #818cf8 0%, #a78bfa 50%, #f472b6 100%);\n color: white;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n box-shadow: 0 4px 15px rgba(139, 92, 246, 0.3);\n }\n\n textarea {\n width: 100%;\n flex: 1;\n min-height: 300px;\n font-family: 'JetBrains Mono', 'SF Mono', 'Consolas', monospace;\n font-size: 0.82rem;\n padding: 1rem;\n border: 1px solid var(--border-color);\n border-radius: var(--radius-md);\n resize: none;\n background: var(--bg-secondary);\n color: var(--text-primary);\n overflow-y: auto;\n outline: none;\n transition: all 0.15s ease;\n line-height: 1.6;\n }\n\n textarea:focus {\n border-color: var(--accent-indigo);\n box-shadow: 0 0 0 3px var(--accent-indigo-light);\n }\n\n label {\n font-size: 0.75rem;\n color: var(--text-secondary);\n display: flex;\n gap: 0.5rem;\n align-items: center;\n }\n\n input[type=\"number\"] {\n width: 90px;\n padding: 0.35rem 0.5rem;\n border-radius: var(--radius-sm);\n border: 1px solid var(--border-color);\n background: var(--bg-primary);\n color: var(--text-primary);\n font-family: inherit;\n font-size: 0.8rem;\n outline: none;\n transition: all 0.15s ease;\n }\n\n input[type=\"number\"]:focus {\n border-color: var(--accent-indigo);\n box-shadow: 0 0 0 3px var(--accent-indigo-light);\n }\n\n .actions {\n display: flex;\n gap: 0.75rem;\n flex-shrink: 0;\n margin-top: 0.5rem;\n }\n\n button.primary {\n background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%);\n color: white;\n padding: 0.5rem 1rem;\n border-radius: var(--radius-md);\n border: none;\n cursor: pointer;\n font-weight: 500;\n font-family: inherit;\n font-size: 0.8rem;\n transition: all 0.2s ease;\n box-shadow: 0 4px 20px rgba(139, 92, 246, 0.4);\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n position: relative;\n overflow: hidden;\n }\n\n button.primary::before {\n content: '';\n position: absolute;\n inset: 0;\n background: linear-gradient(135deg, rgba(255, 255, 255, 0.2) 0%, transparent 50%);\n opacity: 0;\n transition: opacity 0.3s ease;\n }\n\n button.primary:hover::before {\n opacity: 1;\n }\n\n button.primary .btn-icon {\n color: white;\n }\n\n button.primary:hover {\n background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 50%, #9333ea 100%);\n box-shadow: 0 6px 25px rgba(139, 92, 246, 0.5);\n transform: translateY(-2px);\n }\n\n button.secondary {\n background: var(--bg-primary);\n color: var(--text-secondary);\n padding: 0.5rem 1rem;\n border-radius: var(--radius-md);\n border: 1px solid var(--border-color);\n cursor: pointer;\n font-family: inherit;\n font-size: 0.8rem;\n transition: all 0.15s ease;\n }\n\n button.secondary:hover {\n background: var(--bg-hover);\n color: var(--text-primary);\n }\n\n /* Button icons */\n .btn-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 1rem;\n font-weight: 300;\n line-height: 1;\n }\n\n .btn-icon-check {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 0.85rem;\n font-weight: 600;\n line-height: 1;\n color: var(--accent-emerald);\n }\n\n .btn-icon-cross {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 0.85rem;\n font-weight: 600;\n line-height: 1;\n color: var(--accent-rose);\n }\n\n /* Detail panel checkbox */\n #toggle-enable {\n appearance: none;\n -webkit-appearance: none;\n width: 16px;\n height: 16px;\n cursor: pointer;\n border: 2px solid var(--border-color);\n border-radius: 4px;\n background: var(--bg-primary);\n position: relative;\n transition: all 0.15s ease;\n flex-shrink: 0;\n }\n\n #toggle-enable:hover {\n border-color: var(--accent-emerald);\n }\n\n #toggle-enable:checked {\n background: var(--bg-primary);\n border-color: var(--accent-emerald);\n }\n\n #toggle-enable:checked::after {\n content: '';\n position: absolute;\n top: 50%;\n left: 50%;\n width: 3px;\n height: 6px;\n border: solid var(--accent-emerald);\n border-width: 0 2px 2px 0;\n transform: translate(-50%, -60%) rotate(45deg);\n }\n\n .empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n color: var(--text-muted);\n gap: 0.5rem;\n height: 100%;\n font-size: 0.85rem;\n }\n\n pre {\n background: var(--bg-secondary);\n padding: 1rem;\n border-radius: var(--radius-md);\n font-family: 'JetBrains Mono', 'SF Mono', 'Consolas', monospace;\n overflow: auto;\n flex: 1;\n margin: 0;\n min-height: 300px;\n color: var(--text-primary);\n font-size: 0.82rem;\n line-height: 1.6;\n border: 1px solid var(--border-color);\n }\n\n textarea.error {\n border-color: var(--accent-rose);\n box-shadow: 0 0 0 3px var(--accent-rose-light);\n }\n\n /* Modal styles */\n .modal-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: linear-gradient(135deg, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.4) 100%);\n backdrop-filter: blur(8px);\n display: none;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n }\n\n .modal-overlay.show {\n display: flex;\n animation: modalFadeIn 0.2s ease-out;\n }\n\n @keyframes modalFadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n }\n\n .modal {\n background: linear-gradient(180deg, rgba(255, 255, 255, 0.95) 0%, rgba(238, 242, 255, 0.9) 50%, rgba(250, 245, 255, 0.95) 100%);\n backdrop-filter: blur(25px);\n border: 1px solid rgba(255, 255, 255, 0.6);\n border-radius: var(--radius-lg);\n padding: 2rem;\n max-width: 500px;\n width: 90%;\n box-shadow: 0 25px 50px rgba(139, 92, 246, 0.25), 0 0 100px rgba(236, 72, 153, 0.15);\n animation: modalSlideUp 0.3s ease-out;\n position: relative;\n }\n\n .modal::before {\n content: '';\n position: absolute;\n inset: -2px;\n background: linear-gradient(135deg, rgba(139, 92, 246, 0.3), rgba(236, 72, 153, 0.3), rgba(59, 130, 246, 0.3));\n border-radius: calc(var(--radius-lg) + 2px);\n z-index: -1;\n opacity: 0.5;\n }\n\n @keyframes modalSlideUp {\n from {\n opacity: 0;\n transform: translateY(16px) scale(0.98);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n }\n\n .modal h2 {\n margin: 0 0 1.5rem 0;\n color: var(--text-primary);\n font-size: 1.1rem;\n font-weight: 600;\n }\n\n .modal .form-group {\n margin-bottom: 1.25rem;\n }\n\n .modal .form-group label {\n display: block;\n margin-bottom: 0.5rem;\n font-weight: 500;\n color: var(--text-primary);\n font-size: 0.8rem;\n }\n\n .modal .form-group input,\n .modal .form-group select,\n .modal .form-group textarea {\n width: 100%;\n padding: 0.6rem;\n border: 1px solid var(--border-color);\n border-radius: var(--radius-sm);\n font-size: 0.85rem;\n font-family: inherit;\n background: var(--bg-primary);\n color: var(--text-primary);\n outline: none;\n transition: all 0.15s ease;\n }\n\n .modal .form-group input:focus,\n .modal .form-group select:focus,\n .modal .form-group textarea:focus {\n border-color: var(--accent-indigo);\n box-shadow: 0 0 0 3px var(--accent-indigo-light);\n }\n\n .modal .form-group textarea {\n min-height: 100px;\n font-family: 'JetBrains Mono', 'SF Mono', 'Consolas', monospace;\n font-size: 0.8rem;\n }\n\n .modal .form-group small {\n display: block;\n margin-top: 0.35rem;\n color: var(--text-muted);\n font-size: 0.7rem;\n }\n\n .modal .form-actions {\n display: flex;\n gap: 1rem;\n justify-content: flex-end;\n margin-top: 1.5rem;\n }\n\n /* Animation delays for stagger effect */\n header { animation-delay: 0.05s; }\n main { animation-delay: 0.1s; }\n </style>\n</head>\n<body>\n <header>\n <h1>Mock Inspector</h1>\n <span class=\"badge\">${apiPrefixEscaped}</span>\n <button id=\"new-api-btn\" class=\"primary\" style=\"margin-left: auto;\"><span class=\"btn-icon\">+</span> New API</button>\n </header>\n <main>\n <aside id=\"sidebar\">\n <div class=\"global-controls\">\n <button id=\"enable-all\" class=\"secondary\"><span class=\"btn-icon-check\">✓</span> 开启所有</button>\n <button id=\"disable-all\" class=\"secondary\"><span class=\"btn-icon-cross\">✗</span> 关闭所有</button>\n </div>\n <ul id=\"mock-list\"></ul>\n </aside>\n <div class=\"resizer\" id=\"resizer\"></div>\n <section>\n <div id=\"mock-details\" class=\"empty\">\n <p>Select a mock entry to inspect</p>\n </div>\n </section>\n </main>\n \n <div id=\"new-api-modal\" class=\"modal-overlay\">\n <div class=\"modal\">\n <h2><span class=\"btn-icon\">+</span> New API Mock</h2>\n <form id=\"new-api-form\">\n <div class=\"form-group\">\n <label for=\"new-api-method\">HTTP Method</label>\n <select id=\"new-api-method\" required>\n <option value=\"get\">GET</option>\n <option value=\"post\">POST</option>\n <option value=\"put\">PUT</option>\n <option value=\"delete\">DELETE</option>\n <option value=\"patch\">PATCH</option>\n </select>\n </div>\n <div class=\"form-group\">\n <label for=\"new-api-path\">API Path (without prefix)</label>\n <input type=\"text\" id=\"new-api-path\" placeholder=\"/users/list\" required />\n <small style=\"color: rgba(15, 23, 42, 0.6); font-size: 0.85rem;\">例如:/users/list 或 /api/items</small>\n </div>\n <div class=\"form-group\">\n <label for=\"new-api-description\">Description (Optional)</label>\n <input type=\"text\" id=\"new-api-description\" placeholder=\"例如:用户列表接口\" />\n </div>\n <div class=\"form-group\">\n <label for=\"new-api-data\">Response Data (JSON)</label>\n <textarea id=\"new-api-data\" placeholder='{ \"code\": 200, \"data\": [] }'>{ \"code\": 200, \"data\": [] }</textarea>\n </div>\n <div class=\"form-actions\">\n <button type=\"button\" id=\"cancel-new-api\">Cancel</button>\n <button type=\"submit\" class=\"primary\">Create</button>\n </div>\n </form>\n </div>\n </div>\n \n <script>\n const inspectorRoute = ${routeJson};\n const apiBase = (inspectorRoute.endsWith('/') ? inspectorRoute.slice(0, -1) : inspectorRoute) + '/api';\n const allowToggle = ${allowToggleJson};\n\n function escapeHtml(value) {\n if (value == null) return '';\n return String(value)\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n }\n\n // Tree data structure conversion\n function buildMockTree(mocks) {\n const root = { id: 'root', name: 'root', type: 'folder', children: [], checked: false, indeterminate: false };\n\n mocks.forEach(mock => {\n // Parse the file path to build tree structure\n // Example: \"automock/api/v1/asset-groups/prod-db-redis/put.js\"\n const parts = mock.file.split('/').filter(p => p);\n let currentNode = root;\n\n parts.forEach((part, index) => {\n const isFile = part.endsWith('.js') || part.endsWith('.ts');\n const nodeName = isFile ? part.replace(/\\.(js|ts)$/, '') : part;\n const nodeId = parts.slice(0, index + 1).join('/');\n\n let childNode = currentNode.children.find(child => child.name === nodeName);\n\n if (!childNode) {\n childNode = {\n id: nodeId,\n name: nodeName,\n type: isFile ? 'file' : 'folder',\n level: index,\n children: [],\n checked: false,\n indeterminate: false\n };\n\n if (isFile) {\n childNode.mockInfo = mock;\n }\n\n currentNode.children.push(childNode);\n }\n\n currentNode = childNode;\n });\n });\n\n // Initialize checked state based on mock.config.enable\n function initCheckedState(node) {\n if (node.type === 'file' && node.mockInfo) {\n node.checked = node.mockInfo.config.enable || false;\n node.indeterminate = false;\n }\n if (node.children && node.children.length > 0) {\n node.children.forEach(initCheckedState);\n }\n }\n\n initCheckedState(root);\n\n // Calculate parent states\n function updateParentStates(node) {\n if (node.children && node.children.length > 0) {\n node.children.forEach(updateParentStates);\n\n const allChecked = node.children.every(child => child.checked && !child.indeterminate);\n const someChecked = node.children.some(child => child.checked || child.indeterminate);\n\n node.checked = allChecked;\n node.indeterminate = !allChecked && someChecked;\n }\n }\n\n updateParentStates(root);\n\n return root.children;\n }\n\n // Get all leaf node (file) keys under a node\n function getAllFileKeys(node) {\n if (node.type === 'file') {\n return [node.mockInfo?.key].filter(Boolean);\n }\n\n if (node.children && node.children.length > 0) {\n const keys = [];\n node.children.forEach(child => {\n keys.push(...getAllFileKeys(child));\n });\n return keys;\n }\n\n return [];\n }\n\n // Update tree node state recursively\n function updateTreeNodeState(node, checked, updateChildren = true) {\n if (updateChildren && node.children && node.children.length > 0) {\n node.children.forEach(child => {\n updateTreeNodeState(child, checked, true);\n });\n }\n\n node.checked = checked;\n node.indeterminate = false;\n }\n\n // Update parent states bottom-up\n function updateParentNodeState(node, tree) {\n // Find parent node\n function findParent(n, targetId, parent = null) {\n if (n.id === targetId) return parent;\n if (n.children) {\n for (const child of n.children) {\n const result = findParent(child, targetId, n);\n if (result) return result;\n }\n }\n return null;\n }\n\n const parent = findParent({ children: tree }, node.id);\n\n if (parent) {\n const allChecked = parent.children.every(child => child.checked && !child.indeterminate);\n const someChecked = parent.children.some(child => child.checked || child.indeterminate);\n\n parent.checked = allChecked;\n parent.indeterminate = !allChecked && someChecked;\n\n updateParentNodeState(parent, tree);\n }\n }\n\n // Render tree node recursively\n function renderTreeNode(node, level = 0, expandedNodes = new Set()) {\n const hasChildren = node.children && node.children.length > 0;\n const isExpanded = expandedNodes.has(node.id);\n const paddingLeft = level * 1.2 + 0.5;\n\n let html = '<div class=\"tree-node\" data-node-id=\"' + escapeHtml(node.id) + '\" data-node-type=\"' + node.type + '\">';\n\n // Node content\n html += '<div class=\"tree-node-content\" style=\"padding-left: ' + paddingLeft + 'rem\">';\n\n // Expand/collapse icon\n if (hasChildren) {\n html += '<span class=\"tree-expand-icon' + (isExpanded ? ' expanded' : '') + '\">▶</span>';\n } else {\n html += '<span class=\"tree-expand-icon hidden\"></span>';\n }\n\n // Checkbox\n const checkedAttr = node.checked ? 'checked' : '';\n const indeterminateAttr = node.indeterminate ? 'data-indeterminate=\"true\"' : '';\n html += '<input type=\"checkbox\" class=\"tree-node-checkbox\" ' + checkedAttr + ' ' + indeterminateAttr + ' />';\n\n // Node label\n if (node.type === 'file' && node.mockInfo) {\n const mock = node.mockInfo;\n const methodClass = mock.method?.toLowerCase() || 'get';\n html += '<span class=\"tree-node-method ' + methodClass + '\">' + escapeHtml(mock.method?.toUpperCase() || 'GET') + '</span>';\n html += '<span class=\"tree-node-label file\" title=\"' + escapeHtml(mock.path) + '\">' + escapeHtml(mock.path) + '</span>';\n if (mock.description) {\n html += '<span class=\"tree-node-count\" title=\"' + escapeHtml(mock.description) + '\">' + escapeHtml(mock.description) + '</span>';\n }\n // Delete button (only for files)\n html += '<span class=\"tree-node-delete\" data-mock-key=\"' + escapeHtml(mock.key) + '\" data-is-folder=\"false\" title=\"删除此 Mock\">✕</span>';\n } else {\n const fileCount = getAllFileKeys(node);\n const fileKeysJson = JSON.stringify(fileCount);\n // Put count inside the label to avoid layout shift when delete button appears\n html += '<span class=\"tree-node-label folder\">' + escapeHtml(node.name) + ' <span class=\"tree-node-count\">(' + fileCount.length + ')</span></span>';\n // Delete button for folders\n html += '<span class=\"tree-node-delete\" data-mock-keys=\"' + escapeHtml(fileKeysJson) + '\" data-is-folder=\"true\" data-folder-name=\"' + escapeHtml(node.name) + '\" title=\"删除此文件夹及所有 Mock\">✕</span>';\n }\n\n html += '</div>';\n\n // Children container\n if (hasChildren) {\n html += '<div class=\"tree-children' + (isExpanded ? ' expanded' : '') + '\">';\n node.children.forEach(child => {\n html += renderTreeNode(child, level + 1, expandedNodes);\n });\n html += '</div>';\n }\n\n html += '</div>';\n\n return html;\n }\n\n // Find node by id in tree\n function findNodeById(nodes, id) {\n for (const node of nodes) {\n if (node.id === id) return node;\n if (node.children) {\n const found = findNodeById(node.children, id);\n if (found) return found;\n }\n }\n return null;\n }\n\n // Toggle tree node expand/collapse\n function toggleTreeNodeExpand(nodeId) {\n const nodeEl = document.querySelector('.tree-node[data-node-id=\"' + nodeId + '\"]');\n if (!nodeEl) return;\n\n const childrenEl = nodeEl.querySelector('.tree-children');\n const iconEl = nodeEl.querySelector('.tree-expand-icon');\n\n if (childrenEl && iconEl && !iconEl.classList.contains('hidden')) {\n const isExpanded = childrenEl.classList.contains('expanded');\n if (isExpanded) {\n childrenEl.classList.remove('expanded');\n iconEl.classList.remove('expanded');\n } else {\n childrenEl.classList.add('expanded');\n iconEl.classList.add('expanded');\n }\n }\n }\n\n function renderToggleSection(mock) {\n if (!allowToggle) {\n return '';\n }\n const checked = mock.config.enable ? 'checked' : '';\n return (\n '<label>' +\n '<input type=\"checkbox\" id=\"toggle-enable\" ' + checked + ' />' +\n 'Enable' +\n '</label>'\n );\n }\n\n function renderDataSection(mock) {\n if (mock.editable) {\n return '<div class=\"data-container\"><textarea id=\"data-editor\"></textarea><div class=\"actions\"><button class=\"primary\" id=\"save-btn\">Save</button></div></div>';\n }\n return '<div class=\"data-container\"><pre id=\"data-preview\"></pre></div>';\n }\n\n async function fetchMocks() {\n try {\n const res = await fetch(apiBase + '/list');\n if (!res.ok) {\n throw new Error('HTTP ' + res.status + ': ' + res.statusText);\n }\n const data = await res.json();\n return data.mocks || [];\n } catch (error) {\n console.error('[Inspector] Failed to fetch mocks:', error);\n return [];\n }\n }\n\n function renderMockList(mocks, preserveExpandedState = false) {\n const list = document.getElementById('mock-list');\n if (!list) {\n console.error('[Inspector] Element #mock-list not found!');\n return;\n }\n\n // Save current expanded state if requested\n let savedExpandedNodes = new Set();\n if (preserveExpandedState) {\n list.querySelectorAll('.tree-children.expanded').forEach(el => {\n const parentNode = el.closest('.tree-node');\n if (parentNode) {\n savedExpandedNodes.add(parentNode.dataset.nodeId);\n }\n });\n }\n\n list.innerHTML = '';\n if (!mocks || mocks.length === 0) {\n console.warn('[Inspector] No mocks to display');\n list.innerHTML = '<div style=\"padding: 1rem; color: #999;\">No mock files found</div>';\n return;\n }\n\n // Build tree structure\n const tree = buildMockTree(mocks);\n\n // Use saved expanded state or expand first level by default\n const expandedNodes = savedExpandedNodes.size > 0 ? savedExpandedNodes : new Set();\n if (expandedNodes.size === 0) {\n tree.forEach(node => {\n if (node.type === 'folder') {\n expandedNodes.add(node.id);\n }\n });\n }\n\n let treeHtml = '';\n tree.forEach(node => {\n const nodeHtml = renderTreeNode(node, 0, expandedNodes);\n treeHtml += nodeHtml;\n });\n\n list.innerHTML = treeHtml;\n\n // Attach event listeners\n attachTreeEventListeners();\n }\n\n // Attach event listeners for tree interactions\n function attachTreeEventListeners() {\n const list = document.getElementById('mock-list');\n if (!list) return;\n\n // Handle expand/collapse clicks\n list.querySelectorAll('.tree-expand-icon').forEach(icon => {\n icon.addEventListener('click', (e) => {\n e.stopPropagation();\n const nodeEl = icon.closest('.tree-node');\n if (nodeEl) {\n const nodeId = nodeEl.dataset.nodeId;\n toggleTreeNodeExpand(nodeId);\n }\n });\n });\n\n // Handle checkbox clicks\n list.querySelectorAll('.tree-node-checkbox').forEach(checkbox => {\n checkbox.addEventListener('click', async (e) => {\n e.stopPropagation();\n const nodeEl = checkbox.closest('.tree-node');\n if (!nodeEl) return;\n\n const nodeId = nodeEl.dataset.nodeId;\n const nodeType = nodeEl.dataset.nodeType;\n const checked = checkbox.checked;\n\n // Get current tree data\n const tree = buildMockTree(currentMocks);\n\n // Find and update node\n const node = findNodeById(tree, nodeId);\n if (node) {\n // Update children\n updateTreeNodeState(node, checked, true);\n // Update parents\n updateParentNodeState(node, tree);\n\n // Apply changes to all affected file nodes\n const affectedKeys = getAllFileKeys(node);\n await Promise.all(affectedKeys.map(key => toggleMockEnable(key, checked)));\n\n // Re-render tree with preserved expanded state\n renderMockList(currentMocks, true);\n }\n });\n });\n\n // Handle node content clicks (for selection)\n list.querySelectorAll('.tree-node-content').forEach(content => {\n content.addEventListener('click', (e) => {\n // Don't select if clicking on checkbox or expand icon\n if (e.target.classList.contains('tree-node-checkbox') ||\n e.target.classList.contains('tree-expand-icon')) {\n return;\n }\n\n const nodeEl = content.closest('.tree-node');\n if (!nodeEl) return;\n\n const nodeId = nodeEl.dataset.nodeId;\n const nodeType = nodeEl.dataset.nodeType;\n\n // Remove previous selection\n document.querySelectorAll('.tree-node-content.selected').forEach(el => {\n el.classList.remove('selected');\n });\n\n // Add selection to current node\n content.classList.add('selected');\n\n // For file nodes, select the mock\n if (nodeType === 'file') {\n const tree = buildMockTree(currentMocks);\n const node = findNodeById(tree, nodeId);\n if (node && node.mockInfo) {\n selectMock(node.mockInfo.key, content);\n }\n }\n });\n });\n\n // Set indeterminate state for checkboxes\n list.querySelectorAll('.tree-node-checkbox[data-indeterminate=\"true\"]').forEach(cb => {\n cb.indeterminate = true;\n });\n\n // Handle delete button clicks\n list.querySelectorAll('.tree-node-delete').forEach(deleteBtn => {\n deleteBtn.addEventListener('click', async (e) => {\n e.stopPropagation();\n\n // Check if user has chosen \"never ask again\"\n const skipConfirm = localStorage.getItem('mockInspector_skipDeleteConfirm') === 'true';\n\n const isFolder = deleteBtn.dataset.isFolder === 'true';\n let confirmMessage = '';\n let keysToDelete = [];\n\n if (isFolder) {\n // Folder deletion\n const folderName = deleteBtn.dataset.folderName || '';\n const keysJson = deleteBtn.dataset.mockKeys || '[]';\n keysToDelete = JSON.parse(keysJson);\n confirmMessage = '确定要删除文件夹 \"' + folderName + '\" 及其包含的 ' + keysToDelete.length + ' 个 Mock 文件吗?此操作不可撤销。';\n } else {\n // Single file deletion\n const mockKey = deleteBtn.dataset.mockKey;\n if (!mockKey) return;\n keysToDelete = [mockKey];\n confirmMessage = '确定要删除这个 Mock 数据吗?此操作不可撤销。';\n }\n\n // If user chose \"never ask again\", delete directly\n if (skipConfirm) {\n await performDelete(keysToDelete);\n return;\n }\n\n // Otherwise, show custom confirmation dialog\n showDeleteConfirmDialog(confirmMessage, keysToDelete);\n });\n });\n\n // Custom delete confirmation dialog\n function showDeleteConfirmDialog(message, keysToDelete) {\n const modal = document.getElementById('delete-confirm-modal');\n const messageEl = document.getElementById('delete-confirm-message');\n const neverAskCheckbox = document.getElementById('delete-never-ask');\n const confirmBtn = document.getElementById('delete-confirm-btn');\n const cancelBtn = document.getElementById('delete-cancel-btn');\n\n messageEl.textContent = message;\n neverAskCheckbox.checked = false;\n modal.classList.add('show');\n\n // Remove old event listeners\n const newConfirmBtn = confirmBtn.cloneNode(true);\n const newCancelBtn = cancelBtn.cloneNode(true);\n confirmBtn.parentNode.replaceChild(newConfirmBtn, confirmBtn);\n cancelBtn.parentNode.replaceChild(newCancelBtn, cancelBtn);\n\n // Confirm button handler\n newConfirmBtn.addEventListener('click', async () => {\n // Save preference if \"never ask again\" is checked\n if (neverAskCheckbox.checked) {\n localStorage.setItem('mockInspector_skipDeleteConfirm', 'true');\n }\n modal.classList.remove('show');\n await performDelete(keysToDelete);\n });\n\n // Cancel button handler\n newCancelBtn.addEventListener('click', () => {\n modal.classList.remove('show');\n });\n\n // Close on overlay click\n modal.addEventListener('click', (e) => {\n if (e.target === modal) {\n modal.classList.remove('show');\n }\n });\n }\n\n // Perform the actual deletion\n async function performDelete(keysToDelete) {\n try {\n // Delete all keys (either single file or entire folder)\n const deletePromises = keysToDelete.map(key =>\n fetch(apiBase + '/delete?key=' + encodeURIComponent(key), {\n method: 'DELETE'\n })\n );\n\n const results = await Promise.all(deletePromises);\n\n // Check if any deletion failed\n const failedDeletions = results.filter(res => !res.ok);\n if (failedDeletions.length > 0) {\n const errors = await Promise.all(failedDeletions.map(res => res.json()));\n throw new Error(errors.map(e => e.error).join(', '));\n }\n\n // Refresh the list\n currentMocks = await fetchMocks();\n renderMockList(currentMocks, true);\n\n // Clear details panel if any deleted mock was selected\n if (keysToDelete.includes(window.currentKey)) {\n window.currentKey = null;\n document.getElementById('mock-details').innerHTML = '<p>Select a mock entry to inspect</p>';\n document.getElementById('mock-details').className = 'empty';\n }\n } catch (error) {\n alert('删除失败: ' + error.message);\n }\n }\n }\n\n function renderDetails(mock) {\n const container = document.getElementById('mock-details');\n if (!mock) {\n container.className = 'empty';\n container.innerHTML = '<p>Select a mock entry to inspect</p>';\n return;\n }\n\n container.className = '';\n container.innerHTML = [\n '<div style=\"display: flex; flex-direction: column; height: 100%;\">',\n ' <div class=\"controls\">',\n ' <h2>',\n ' <span class=\"badge\">' + escapeHtml(mock.method.toUpperCase()) + '</span>',\n ' ' + escapeHtml(mock.path),\n ' </h2>',\n ' <label style=\"flex: 1;\" >',\n ' Desc: ',\n ' <input type=\"text\" id=\"description-input\" placeholder=\"例如:用户列表接口\" value=\"' + escapeHtml(mock.description || '') + '\" style=\"width: 100%; margin-top: 0.25rem;\" />',\n ' </label>',\n ' <label>Delay <input type=\"number\" id=\"delay-input\" value=\"' + mock.config.delay + '\" min=\"0\" step=\"50\" /> ms</label>',\n ' <label>Status <input type=\"number\" id=\"status-input\" value=\"' + mock.config.status + '\" min=\"100\" max=\"599\" /></label>',\n ' ' + renderToggleSection(mock),\n ' </div>',\n ' <h3>Response Data</h3>',\n ' ' + renderDataSection(mock),\n '</div>'\n ].join('\\\\n');\n\n const descriptionInput = document.getElementById('description-input');\n if (descriptionInput) {\n descriptionInput.addEventListener('change', () => updateDescription(descriptionInput.value));\n }\n\n if (allowToggle) {\n const enableToggle = document.getElementById('toggle-enable');\n if (enableToggle) {\n enableToggle.addEventListener('change', () => updateConfig({ enable: enableToggle.checked }));\n }\n }\n\n const delayInput = document.getElementById('delay-input');\n if (delayInput) {\n delayInput.addEventListener('change', () => updateConfig({ delay: Number(delayInput.value) || 0 }));\n }\n\n const statusInput = document.getElementById('status-input');\n if (statusInput) {\n statusInput.addEventListener('change', () => updateConfig({ status: Number(statusInput.value) || 200 }));\n }\n\n if (mock.editable) {\n const textarea = document.getElementById('data-editor');\n if (textarea) {\n textarea.value = mock.dataText || '';\n const saveBtn = document.getElementById('save-btn');\n if (saveBtn) {\n saveBtn.addEventListener('click', async () => {\n try {\n const raw = textarea.value || 'null';\n const parsed = JSON.parse(raw);\n await updateConfig({ data: parsed });\n textarea.classList.remove('error');\n } catch (err) {\n textarea.classList.add('error');\n alert('Invalid JSON: ' + err.message);\n }\n });\n }\n }\n } else {\n const pre = document.getElementById('data-preview');\n if (pre) {\n pre.textContent = mock.dataText || '';\n }\n }\n }\n\n let currentMocks = [];\n\n async function updateConfig(partial) {\n if (!window.currentKey) return;\n if (!allowToggle && 'enable' in partial) {\n delete partial.enable;\n }\n const res = await fetch(apiBase + '/update', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ key: window.currentKey, config: partial })\n });\n const data = await res.json();\n const updated = data.mock;\n const index = currentMocks.findIndex((item) => item.key === window.currentKey);\n if (index >= 0) {\n currentMocks[index] = updated;\n renderDetails(updated);\n }\n }\n\n async function updateDescription(description) {\n if (!window.currentKey) return;\n try {\n const res = await fetch(apiBase + '/update', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ key: window.currentKey, description: description })\n });\n const data = await res.json();\n const updated = data.mock;\n const index = currentMocks.findIndex((item) => item.key === window.currentKey);\n if (index >= 0) {\n currentMocks[index] = updated;\n renderDetails(updated);\n // 重新渲染列表以更新显示\n renderMockList(currentMocks, true);\n }\n } catch (error) {\n console.error('[Inspector] Failed to update description:', error);\n alert('更新描述失败: ' + error.message);\n }\n }\n\n async function selectMock(key, button) {\n window.currentKey = key;\n document.querySelectorAll('aside button').forEach((btn) => btn.classList.remove('active'));\n button.classList.add('active');\n const res = await fetch(apiBase + '/detail?key=' + encodeURIComponent(key));\n const data = await res.json();\n const mock = data.mock;\n const index = currentMocks.findIndex((item) => item.key === key);\n if (index >= 0) {\n currentMocks[index] = mock;\n }\n renderDetails(mock);\n }\n\n async function toggleMockEnable(key, enable) {\n try {\n const res = await fetch(apiBase + '/update', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ key: key, config: { enable: enable } })\n });\n const data = await res.json();\n const updated = data.mock;\n const index = currentMocks.findIndex((item) => item.key === key);\n if (index >= 0) {\n currentMocks[index] = updated;\n // 如果当前正在查看这个 mock,更新详情\n if (window.currentKey === key) {\n renderDetails(updated);\n }\n }\n } catch (error) {\n console.error('[Inspector] Failed to toggle mock:', error);\n alert('更新失败: ' + error.message);\n }\n }\n\n function startEditDescription(li, mock) {\n // 清空 li 内容,创建编辑框\n li.innerHTML = '';\n \n const input = document.createElement('input');\n input.type = 'text';\n input.className = 'description-edit';\n input.value = mock.description || '';\n input.placeholder = '输入业务描述,例如:用户列表接口';\n \n const saveEdit = async () => {\n const newDescription = input.value.trim();\n try {\n const res = await fetch(apiBase + '/update', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ key: mock.key, description: newDescription })\n });\n const data = await res.json();\n const updated = data.mock;\n const index = currentMocks.findIndex((item) => item.key === mock.key);\n if (index >= 0) {\n currentMocks[index] = updated;\n }\n // 重新渲染列表\n renderMockList(currentMocks, true);\n // 如果当前正在查看这个 mock,更新详情\n if (window.currentKey === mock.key) {\n renderDetails(updated);\n }\n } catch (error) {\n console.error('[Inspector] Failed to update description:', error);\n alert('更新失败: ' + error.message);\n renderMockList(currentMocks, true);\n }\n };\n \n input.addEventListener('blur', saveEdit);\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') {\n saveEdit();\n } else if (e.key === 'Escape') {\n renderMockList(currentMocks, true);\n }\n });\n \n li.appendChild(input);\n input.focus();\n input.select();\n }\n\n async function enableAllMocks() {\n const promises = currentMocks.map(mock => \n toggleMockEnable(mock.key, true)\n );\n await Promise.all(promises);\n // 重新获取列表以更新 UI\n currentMocks = await fetchMocks();\n renderMockList(currentMocks, true);\n }\n\n async function disableAllMocks() {\n const promises = currentMocks.map(mock => \n toggleMockEnable(mock.key, false)\n );\n await Promise.all(promises);\n // 重新获取列表以更新 UI\n currentMocks = await fetchMocks();\n renderMockList(currentMocks, true);\n }\n\n // Initialize sidebar resizer functionality\n function initSidebarResizer() {\n const sidebar = document.getElementById('sidebar');\n const resizer = document.getElementById('resizer');\n const main = document.querySelector('main');\n\n if (!sidebar || !resizer || !main) {\n console.warn('[Inspector] Sidebar resizer elements not found');\n return;\n }\n\n // Load saved width from localStorage\n const savedWidth = localStorage.getItem('mockInspectorSidebarWidth');\n if (savedWidth) {\n const width = Math.max(200, Math.min(800, parseInt(savedWidth, 10)));\n main.style.setProperty('--sidebar-width', width + 'px');\n }\n\n let isResizing = false;\n let startX = 0;\n let startWidth = 0;\n\n resizer.addEventListener('mousedown', (e) => {\n isResizing = true;\n startX = e.clientX;\n startWidth = sidebar.offsetWidth;\n resizer.classList.add('active');\n document.body.style.cursor = 'col-resize';\n document.body.style.userSelect = 'none';\n e.preventDefault();\n });\n\n document.addEventListener('mousemove', (e) => {\n if (!isResizing) return;\n\n const deltaX = e.clientX - startX;\n const newWidth = Math.max(200, Math.min(800, startWidth + deltaX));\n main.style.setProperty('--sidebar-width', newWidth + 'px');\n });\n\n document.addEventListener('mouseup', () => {\n if (isResizing) {\n isResizing = false;\n resizer.classList.remove('active');\n document.body.style.cursor = '';\n document.body.style.userSelect = '';\n\n // Save width to localStorage\n const currentWidth = sidebar.offsetWidth;\n localStorage.setItem('mockInspectorSidebarWidth', currentWidth.toString());\n }\n });\n\n // Touch support for mobile devices\n resizer.addEventListener('touchstart', (e) => {\n const touch = e.touches[0];\n isResizing = true;\n startX = touch.clientX;\n startWidth = sidebar.offsetWidth;\n resizer.classList.add('active');\n e.preventDefault();\n }, { passive: false });\n\n document.addEventListener('touchmove', (e) => {\n if (!isResizing) return;\n\n const touch = e.touches[0];\n const deltaX = touch.clientX - startX;\n const newWidth = Math.max(200, Math.min(800, startWidth + deltaX));\n main.style.setProperty('--sidebar-width', newWidth + 'px');\n }, { passive: false });\n\n document.addEventListener('touchend', () => {\n if (isResizing) {\n isResizing = false;\n resizer.classList.remove('active');\n\n // Save width to localStorage\n const currentWidth = sidebar.offsetWidth;\n localStorage.setItem('mockInspectorSidebarWidth', currentWidth.toString());\n }\n });\n }\n\n async function bootstrap() {\n // Initialize sidebar resizer\n initSidebarResizer();\n\n try {\n currentMocks = await fetchMocks();\n renderMockList(currentMocks, true);\n \n // 绑定全局控制按钮\n const enableAllBtn = document.getElementById('enable-all');\n const disableAllBtn = document.getElementById('disable-all');\n if (enableAllBtn) {\n enableAllBtn.addEventListener('click', async () => {\n enableAllBtn.disabled = true;\n enableAllBtn.textContent = '处理中...';\n await enableAllMocks();\n enableAllBtn.disabled = false;\n enableAllBtn.textContent = '✓ 开启所有';\n });\n }\n if (disableAllBtn) {\n disableAllBtn.addEventListener('click', async () => {\n disableAllBtn.disabled = true;\n disableAllBtn.textContent = '处理中...';\n await disableAllMocks();\n disableAllBtn.disabled = false;\n disableAllBtn.textContent = '✗ 关闭所有';\n });\n }\n \n // 绑定新建API按钮\n const newApiBtn = document.getElementById('new-api-btn');\n const newApiModal = document.getElementById('new-api-modal');\n const newApiForm = document.getElementById('new-api-form');\n const cancelNewApi = document.getElementById('cancel-new-api');\n \n if (newApiBtn && newApiModal) {\n newApiBtn.addEventListener('click', () => {\n newApiModal.classList.add('show');\n document.getElementById('new-api-path').focus();\n });\n }\n \n if (cancelNewApi && newApiModal) {\n cancelNewApi.addEventListener('click', () => {\n newApiModal.classList.remove('show');\n newApiForm.reset();\n });\n }\n \n // 点击背景关闭模态框\n if (newApiModal) {\n newApiModal.addEventListener('click', (e) => {\n if (e.target === newApiModal) {\n newApiModal.classList.remove('show');\n newApiForm.reset();\n }\n });\n }\n \n // 处理表单提交\n if (newApiForm) {\n newApiForm.addEventListener('submit', async (e) => {\n e.preventDefault();\n \n const method = document.getElementById('new-api-method').value;\n const path = document.getElementById('new-api-path').value.trim();\n const description = document.getElementById('new-api-description').value.trim();\n const dataText = document.getElementById('new-api-data').value.trim();\n \n if (!path) {\n alert('请输入 API 路径');\n return;\n }\n \n // 验证JSON\n let data;\n try {\n data = JSON.parse(dataText || '{}');\n } catch (err) {\n alert('Response Data 不是有效的 JSON: ' + err.message);\n return;\n }\n \n // 发送创建请求\n try {\n const submitBtn = newApiForm.querySelector('button[type=\"submit\"]');\n const originalText = submitBtn.textContent;\n submitBtn.disabled = true;\n submitBtn.textContent = 'Creating...';\n \n const res = await fetch(apiBase + '/create', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n method: method,\n path: path,\n description: description || path,\n data: data\n })\n });\n \n const result = await res.json();\n \n if (!res.ok) {\n throw new Error(result.error || 'Failed to create API');\n }\n \n // 关闭模态框\n newApiModal.classList.remove('show');\n newApiForm.reset();\n \n // 刷新列表\n currentMocks = await fetchMocks();\n renderMockList(currentMocks, true);\n \n // 自动选中新创建的 API\n if (result.mock && result.mock.key) {\n const button = document.querySelector('aside button[data-key=\"' + result.mock.key + '\"]');\n if (button) {\n await selectMock(result.mock.key, button);\n }\n }\n \n alert('✅ API Mock 创建成功!');\n \n submitBtn.disabled = false;\n submitBtn.textContent = originalText;\n } catch (err) {\n alert('创建失败: ' + err.message);\n const submitBtn = newApiForm.querySelector('button[type=\"submit\"]');\n submitBtn.disabled = false;\n submitBtn.textContent = 'Create';\n }\n });\n }\n } catch (error) {\n console.error('[Inspector] Bootstrap failed:', error);\n }\n }\n\n bootstrap();\n </script>\n\n <!-- Delete Confirmation Modal -->\n <div id=\"delete-confirm-modal\" class=\"modal-overlay\">\n <div class=\"modal\" style=\"max-width: 400px;\">\n <h2><span class=\"btn-icon-cross\" style=\"color: var(--accent-rose);\">!</span> 确认删除</h2>\n <p id=\"delete-confirm-message\" style=\"margin-bottom: 1rem; color: var(--text-secondary);\"></p>\n <label style=\"display: flex; align-items: center; gap: 0.5rem; margin-bottom: 1rem; cursor: pointer; user-select: none;\">\n <input type=\"checkbox\" id=\"delete-never-ask\" style=\"flex-shrink: 0;\" />\n <span>不再提醒</span>\n </label>\n <div style=\"display: flex; gap: 0.75rem; justify-content: flex-end;\">\n <button id=\"delete-cancel-btn\" class=\"secondary\">取消</button>\n <button id=\"delete-confirm-btn\" class=\"primary\" style=\"background: var(--accent-rose);\">删除</button>\n </div>\n </div>\n </div>\n</body>\n</html>`;\n\n res.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n res.end(html);\n}\n\nasync function handleInspectorApi({\n res,\n req,\n pathname,\n mockDir,\n apiPrefix,\n inspectorConfig,\n getMockFileMap,\n}: InspectorApiContext): Promise<void> {\n try {\n const mockFileMap = getMockFileMap();\n\n if (pathname === \"list\") {\n const list = await Promise.all(\n Array.from(mockFileMap.entries()).map(async ([key, filePath]) => {\n const info = await parseMockModule(filePath);\n return {\n key,\n file: path.relative(mockDir, filePath),\n method: key.split(\"/\").pop()?.replace(/\\.js$/, \"\") ?? \"get\",\n path: key.replace(/\\/[^/]+\\.js$/, \"\"),\n config: info.config,\n editable: info.serializable,\n description: info.description,\n dataText: info.dataText,\n };\n }),\n );\n sendJson(res, { mocks: list });\n return;\n }\n\n if (pathname === \"detail\") {\n const url = new URL(req.url || \"\", \"http://localhost\");\n const key = url.searchParams.get(\"key\");\n if (!key) {\n sendJson(res, { error: \"Missing key\" }, 400);\n return;\n }\n const filePath = mockFileMap.get(key);\n if (!filePath) {\n sendJson(res, { error: \"Mock not found\" }, 404);\n return;\n }\n const info = await parseMockModule(filePath);\n sendJson(res, {\n mock: {\n key,\n file: path.relative(mockDir, filePath),\n method: key.split(\"/\").pop()?.replace(/\\.js$/, \"\") ?? \"get\",\n path: key.replace(/\\/[^/]+\\.js$/, \"\"),\n config: info.config,\n editable: info.serializable,\n description: info.description,\n dataText: info.dataText,\n },\n });\n return;\n }\n\n if (pathname === \"update\") {\n if (req.method !== \"POST\") {\n sendJson(res, { error: \"Method not allowed\" }, 405);\n return;\n }\n const body = await readBody(req);\n let payload: InspectorPayload;\n try {\n payload = JSON.parse(body);\n } catch (error) {\n sendJson(res, { error: \"Invalid JSON body\" }, 400);\n return;\n }\n\n const { key, config, description } = payload || {};\n if (!key) {\n sendJson(res, { error: \"Invalid payload: missing key\" }, 400);\n return;\n }\n\n const filePath = mockFileMap.get(key);\n if (!filePath) {\n sendJson(res, { error: \"Mock not found\" }, 404);\n return;\n }\n\n const info = await parseMockModule(filePath);\n\n // 处理 config 更新\n let nextConfig = info.config;\n if (config && typeof config === \"object\") {\n if (!inspectorConfig.enableToggle && config.enable !== undefined) {\n delete config.enable;\n }\n nextConfig = {\n ...info.config,\n ...config,\n };\n }\n\n // 处理 description 更新\n const nextDescription =\n description !== undefined ? description : info.description;\n\n await writeMockFile(filePath, {\n headerComment: info.headerComment,\n config: nextConfig,\n description: nextDescription,\n });\n\n const updatedInfo = await parseMockModule(filePath);\n sendJson(res, {\n mock: {\n key,\n file: path.relative(mockDir, filePath),\n method: key.split(\"/\").pop()?.replace(/\\.js$/, \"\") ?? \"get\",\n path: key.replace(/\\/[^/]+\\.js$/, \"\"),\n config: updatedInfo.config,\n editable: updatedInfo.serializable,\n description: updatedInfo.description,\n dataText: updatedInfo.dataText,\n },\n });\n return;\n }\n\n if (pathname === \"create\") {\n if (req.method !== \"POST\") {\n sendJson(res, { error: \"Method not allowed\" }, 405);\n return;\n }\n const body = await readBody(req);\n let payload: {\n method: string;\n path: string;\n description?: string;\n data?: unknown;\n };\n try {\n payload = JSON.parse(body);\n } catch (error) {\n sendJson(res, { error: \"Invalid JSON body\" }, 400);\n return;\n }\n\n const { method, path: apiPath, description, data } = payload || {};\n if (!method || !apiPath) {\n sendJson(\n res,\n { error: \"Invalid payload: missing method or path\" },\n 400,\n );\n return;\n }\n\n // 导入 saveMockData\n const { saveMockData } = await import(\"./mockFileUtils\");\n\n // 构造完整的 URL(包含 apiPrefix)\n const fullUrl = apiPrefix + apiPath;\n\n // 调用 saveMockData 创建文件\n try {\n // Convert data to JSON string if it's an object\n let dataToSave: Buffer | string;\n if (typeof data === \"object\" && data !== null) {\n dataToSave = JSON.stringify(data, null, 2);\n } else {\n dataToSave = typeof data === \"string\" ? data : \"\";\n }\n\n await saveMockData(\n fullUrl,\n method.toLowerCase(),\n dataToSave,\n mockDir,\n 200,\n );\n\n // 重新构建 mock 文件索引(因为有新文件)\n const { buildMockIndex } = await import(\"./mockFileUtils\");\n const newMockFileMap = await buildMockIndex(mockDir);\n\n // 更新 mockFileMap\n for (const [key, value] of newMockFileMap.entries()) {\n mockFileMap.set(key, value);\n }\n\n // 查找新创建的 mock 的 key\n const key = apiPath + \"/\" + method.toLowerCase() + \".js\";\n const filePath = mockFileMap.get(key);\n\n if (!filePath) {\n sendJson(\n res,\n { error: \"Mock file created but not found in map\" },\n 500,\n );\n return;\n }\n\n const info = await parseMockModule(filePath);\n sendJson(res, {\n success: true,\n mock: {\n key,\n file: path.relative(mockDir, filePath),\n method: method.toLowerCase(),\n path: apiPath,\n config: info.config,\n editable: info.serializable,\n description: info.description,\n dataText: info.dataText,\n },\n });\n return;\n } catch (error: unknown) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n sendJson(res, { error: \"Failed to create mock: \" + errorMessage }, 500);\n return;\n }\n }\n\n if (pathname === \"delete\") {\n const url = new URL(req.url || \"\", \"http://localhost\");\n const key = url.searchParams.get(\"key\");\n if (!key) {\n sendJson(res, { error: \"Missing key\" }, 400);\n return;\n }\n\n const filePath = mockFileMap.get(key);\n if (!filePath) {\n sendJson(res, { error: \"Mock not found\" }, 404);\n return;\n }\n\n // Delete the mock file or directory recursively\n const { unlink, rm } = await import(\"fs/promises\");\n const { stat } = await import(\"fs/promises\");\n try {\n const stats = await stat(filePath);\n if (stats.isDirectory()) {\n // Delete directory recursively\n await rm(filePath, { recursive: true, force: true });\n // Remove all files in this directory from mockFileMap\n for (const [mapKey, mapPath] of mockFileMap.entries()) {\n if (\n mapPath.startsWith(filePath + path.sep) ||\n mapPath === filePath\n ) {\n mockFileMap.delete(mapKey);\n }\n }\n } else {\n // Delete single file\n await unlink(filePath);\n // Remove from mockFileMap\n mockFileMap.delete(key);\n }\n sendJson(res, { success: true });\n } catch (error: unknown) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n sendJson(res, { error: \"Failed to delete: \" + errorMessage }, 500);\n }\n return;\n }\n\n sendJson(res, { error: \"Unknown inspector endpoint\" }, 404);\n } catch (error) {\n console.error(\"❌ [automock] Inspector request failed:\", error);\n sendJson(res, { error: \"Inspector failed\" }, 500);\n }\n}\n\nfunction sendJson(res: ServerResponse, payload: unknown, status = 200): void {\n res.statusCode = status;\n res.setHeader(\"Content-Type\", \"application/json; charset=utf-8\");\n res.end(JSON.stringify(payload));\n}\n\nfunction readBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk) => chunks.push(chunk));\n req.on(\"end\", () => resolve(Buffer.concat(chunks).toString(\"utf-8\")));\n req.on(\"error\", reject);\n });\n}\n","import path from 'path'\nimport fs from 'fs-extra'\n\nimport { buildMockIndex, parseMockModule, type MockFileConfig } from './mockFileUtils'\n\nexport interface MockBundleData {\n enable: boolean\n data: unknown\n delay: number\n status: number\n isBinary?: boolean\n}\n\nexport interface MockBundle {\n [key: string]: MockBundleData\n}\n\nexport interface BundleOptions {\n mockDir: string\n includeDisabled?: boolean\n log?: boolean\n}\n\nconst DEFAULT_BUNDLE_OPTIONS: Partial<BundleOptions> = {\n includeDisabled: true,\n log: true\n}\n\nasync function bundleMockFiles(options: BundleOptions): Promise<MockBundle> {\n const opts = { ...DEFAULT_BUNDLE_OPTIONS, ...options }\n const { mockDir, log } = opts\n\n if (!fs.existsSync(mockDir)) {\n if (log) {\n console.log(`[mock-bundler] Mock directory does not exist: ${mockDir}`)\n }\n return {}\n }\n\n const mockFileMap = buildMockIndex(mockDir)\n const bundle: MockBundle = {}\n let bundledCount = 0\n let skippedCount = 0\n\n for (const [key, filePath] of mockFileMap) {\n try {\n const mockInfo = await parseMockModule(filePath)\n\n if (!mockInfo.serializable) {\n skippedCount++\n if (log) {\n console.log(`[mock-bundler] Skipping non-serializable mock: ${key}`)\n }\n continue\n }\n\n bundle[key] = {\n enable: mockInfo.config.enable,\n data: mockInfo.config.data,\n delay: mockInfo.config.delay,\n status: mockInfo.config.status,\n isBinary: mockInfo.isBinary\n }\n\n bundledCount++\n } catch (error) {\n if (log) {\n console.error(`[mock-bundler] Failed to bundle mock ${key}:`, error)\n }\n }\n }\n\n if (log) {\n console.log(`[mock-bundler] Bundled ${bundledCount} mocks, skipped ${skippedCount} non-serializable mocks`)\n }\n\n return bundle\n}\n\nfunction writeMockBundle(bundle: MockBundle, outputPath: string): void {\n const outputDir = path.dirname(outputPath)\n\n if (!fs.existsSync(outputDir)) {\n fs.ensureDirSync(outputDir)\n }\n\n fs.writeFileSync(\n outputPath,\n JSON.stringify(bundle, null, 2),\n 'utf-8'\n )\n}\n\nexport { bundleMockFiles, writeMockBundle }\n","import type {\n AxiosInstance,\n AxiosRequestConfig,\n InternalAxiosRequestConfig,\n} from \"axios\";\n\n/**\n * Helper function to safely get environment variables\n * Works in both ESM and CJS environments\n */\nfunction getEnvVar(name: string): string | undefined {\n // Try ESM environment (Vite)\n if (typeof import.meta !== \"undefined\" && import.meta.env) {\n return import.meta.env[name as keyof ImportMetaEnv];\n }\n // Fallback to CommonJS (Node.js)\n if (typeof process !== \"undefined\" && process.env) {\n return process.env[name];\n }\n return undefined;\n}\n\nexport type MockBundleData = {\n enable: boolean;\n data: unknown;\n delay?: number;\n status?: number;\n isBinary?: boolean;\n};\n\nexport type MockInterceptorOptions = {\n mockData: Record<string, MockBundleData>;\n enabled?: boolean | (() => boolean);\n onMockHit?: (url: string, method: string, mock: MockBundleData) => void;\n onBypass?: (url: string, method: string, reason: string) => void;\n};\n\ntype MockEnabledState = boolean | undefined;\n\ndeclare global {\n interface Window {\n __MOCK_ENABLED__?: MockEnabledState;\n }\n}\n\nclass MockInterceptor {\n private options: MockInterceptorOptions;\n\n constructor(options: MockInterceptorOptions) {\n this.options = options;\n }\n\n /**\n * Check if mock is enabled\n */\n isEnabled(): boolean {\n // Check runtime flag first\n if (window.__MOCK_ENABLED__ !== undefined) {\n return window.__MOCK_ENABLED__;\n }\n // Check environment variable\n if (typeof this.options.enabled === \"function\") {\n return this.options.enabled();\n }\n return this.options.enabled ?? false;\n }\n\n /**\n * Find matching mock data for the request\n * Supports both formats:\n * - \"GET /api/v1/asset/xxx\" (HTTP method + URL)\n * - \"/api/v1/asset/xxx/get.js\" (File path format from automock plugin)\n */\n private findMock(url: string, method: string): MockBundleData | null {\n const methodUpper = method.toUpperCase();\n const methodLower = method.toLowerCase();\n\n // Try HTTP method + URL format first (for compatibility)\n const httpMethodKey = `${methodUpper} ${url}`;\n if (this.options.mockData[httpMethodKey]) {\n return this.options.mockData[httpMethodKey];\n }\n\n // Try file path format from automock plugin: /api/v1/asset/xxx/get.js\n const filePathKey = `${url}/${methodLower}.js`;\n if (this.options.mockData[filePathKey]) {\n return this.options.mockData[filePathKey];\n }\n\n return null;\n }\n\n /**\n * Determine if request should be mocked\n */\n shouldMock(url: string, method: string): boolean {\n if (!this.isEnabled()) {\n return false;\n }\n\n const mock = this.findMock(url, method);\n return mock !== null && mock.enable;\n }\n\n /**\n * Get mock data for request\n */\n getMock(url: string, method: string): MockBundleData | null {\n if (!this.shouldMock(url, method)) {\n return null;\n }\n return this.findMock(url, method);\n }\n\n /**\n * Setup axios interceptor\n */\n setupAxios(axiosInstance: AxiosInstance): void {\n axiosInstance.interceptors.request.use(\n async (config: InternalAxiosRequestConfig) => {\n const url = config.url || \"\";\n const method = config.method?.toUpperCase() || \"GET\";\n\n const mock = this.getMock(url, method);\n\n if (mock) {\n // Trigger mock hit callback\n this.options.onMockHit?.(url, method, mock);\n\n // Set adapter to return mock data\n (config as any).adapter = async () => {\n const { data, delay = 0, status = 200 } = mock;\n\n // Simulate delay\n if (delay > 0) {\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n\n return {\n data,\n status,\n statusText: \"OK\",\n headers: {},\n config,\n };\n };\n } else {\n // Trigger bypass callback\n const reason = !this.isEnabled()\n ? \"Mock disabled globally\"\n : \"No matching mock found or mock disabled\";\n this.options.onBypass?.(url, method, reason);\n }\n\n return config;\n },\n (error: unknown) => Promise.reject(error),\n );\n }\n}\n\n/**\n * Create mock interceptor instance\n */\nexport function createMockInterceptor(\n options: MockInterceptorOptions,\n): MockInterceptor {\n return new MockInterceptor(options);\n}\n\n/**\n * Load mock data from bundled JSON file\n */\nexport async function loadMockData(): Promise<Record<string, MockBundleData>> {\n try {\n // In production, mock-data.json is generated by automock plugin\n // Use absolute path to load it from dist directory\n const response = await fetch(\"/mock-data.json\");\n if (!response.ok) {\n console.warn(\n \"[MockInterceptor] Failed to load mock-data.json:\",\n response.statusText,\n );\n return {};\n }\n const data = (await response.json()) as Record<string, MockBundleData>;\n return data;\n } catch (error) {\n console.warn(\"[MockInterceptor] Failed to load mock-data.json:\", error);\n return {};\n }\n}\n\n/**\n * Initialize mock interceptor with default settings\n */\nexport async function initMockInterceptor(\n axiosInstance?: AxiosInstance,\n): Promise<void> {\n const mockData = await loadMockData();\n\n if (!axiosInstance) {\n throw new Error(\n \"[MockInterceptor] axiosInstance is required. Please provide an axios instance.\",\n );\n }\n\n // Check if mock is enabled via environment variable\n const isEnvEnabled = getEnvVar(\"VITE_USE_MOCK\") === \"true\";\n\n const interceptor = createMockInterceptor({\n mockData,\n enabled: isEnvEnabled,\n onMockHit: (url: string, method: string) => {\n console.log(`[MOCK HIT] ${method} ${url}`);\n },\n onBypass: (url: string, method: string, reason: string) => {\n console.log(`[MOCK BYPASS] ${method} ${url} - ${reason}`);\n },\n });\n\n // Setup interceptor on the axios instance\n interceptor.setupAxios(axiosInstance);\n}\n\n/**\n * Set mock enabled state at runtime\n */\nexport function setMockEnabled(enabled: boolean): void {\n window.__MOCK_ENABLED__ = enabled;\n}\n\n/**\n * Get current mock enabled state\n */\nexport function isMockEnabled(): boolean {\n return !!window.__MOCK_ENABLED__;\n}\n\n/**\n * Get the http instance (PureHttp wrapper)\n * This is a placeholder - users should provide their own http instance\n * or use initMockInterceptorForPureHttp with their specific setup\n */\ntype HttpInstance = {\n constructor: { axiosInstance: AxiosInstance };\n};\n\n// This is a reference to the user's http instance\n// It should be set by the user's application code\nlet httpInstanceRef: HttpInstance | undefined;\n\n/**\n * Register the http instance for PureHttp compatibility\n * Call this before initMockInterceptorForPureHttp\n */\nexport function registerHttpInstance(http: HttpInstance): void {\n httpInstanceRef = http;\n}\n\n/**\n * Initialize mock interceptor for PureHttp\n * Should be called after PureHttp is instantiated and registered\n */\nexport async function initMockInterceptorForPureHttp(): Promise<void> {\n if (!httpInstanceRef) {\n throw new Error(\n \"[MockInterceptor] http instance not registered. Call registerHttpInstance(http) first.\",\n );\n }\n\n const mockData = await loadMockData();\n\n // Check if mock is enabled via environment variable\n const isEnvEnabled = getEnvVar(\"VITE_USE_MOCK\") === \"true\";\n\n const interceptor = createMockInterceptor({\n mockData,\n enabled: isEnvEnabled,\n onMockHit: (url: string, method: string) => {\n console.log(`[MOCK HIT] ${method} ${url}`);\n },\n onBypass: (url: string, method: string, reason: string) => {\n console.log(`[MOCK BYPASS] ${method} ${url} - ${reason}`);\n },\n });\n\n // Get axios instance from PureHttp and setup interceptor\n interceptor.setupAxios(httpInstanceRef.constructor.axiosInstance);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8BA,SAAS,YAAY,GAAmB;AACtC,SAAO,EAAE,QAAQ,OAAO,GAAG;AAC7B;AAhCA,iBACA,iBACA,iBAoBa,gBAaP,kBA6BA,kBAmBA,kBAgDA,cA8KA,uBAqBA,gBAkCA,iBAwDA;AAhaN;AAAA;AAAA;AAAA,kBAAiB;AACjB,sBAAe;AACf,sBAAqB;AAoBd,IAAM,iBAAiC;AAAA,MAC5C,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,OAAO;AAAA,MACP,QAAQ;AAAA,IACV;AAQA,IAAM,mBAAmB,CACvB,aACA,SACY;AACZ,UAAI,CAAC,YAAa,QAAO;AAEzB,YAAM,cAAc;AAAA,QAClB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAEA,aACE,YAAY,KAAK,CAAC,SAAS,YAAY,YAAY,EAAE,SAAS,IAAI,CAAC,KACnE,CAAC,iBAAiB,IAAI;AAAA,IAE1B;AAGA,IAAM,mBAAmB,CAAC,WAA4B;AACpD,UAAI;AAEF,cAAM,SAAS,OAAO,MAAM,GAAG,GAAG;AAClC,cAAM,OAAO,OAAO,SAAS,MAAM;AAGnC,cAAM,YAAY,CAAC,GAAG,MAAM,EAAE,OAAO,CAAC,MAAM,MAAM,CAAC,EAAE;AACrD,cAAM,eAAe,CAAC,GAAG,MAAM,EAAE;AAAA,UAC/B,CAAC,MAAM,IAAI,MAAM,MAAM,KAAK,MAAM,MAAM,MAAM;AAAA,QAChD,EAAE;AAEF,eAAO,cAAc,KAAK,eAAe;AAAA,MAC3C,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAGA,IAAM,mBAAmB,CACvB,aACA,QACW;AAEX,YAAM,UAAkC;AAAA,QACtC,oBAAoB;AAAA,QACpB,mBAAmB;AAAA,QACnB,mBAAmB;AAAA,QACnB,4BAA4B;AAAA,QAC5B,qEAAqE;AAAA,QACrE,sBAAsB;AAAA,QACtB,2EACE;AAAA,QACF,iCAAiC;AAAA,QACjC,6EACE;AAAA,QACF,cAAc;AAAA,QACd,aAAa;AAAA,QACb,aAAa;AAAA,QACb,cAAc;AAAA,QACd,aAAa;AAAA,QACb,YAAY;AAAA,QACZ,0BAA0B;AAAA,QAC1B,YAAY;AAAA,MACd;AAEA,UAAI,eAAe,QAAQ,YAAY,YAAY,CAAC,GAAG;AACrD,eAAO,QAAQ,YAAY,YAAY,CAAC;AAAA,MAC1C;AAGA,UAAI;AACF,cAAM,SAAS,IAAI,IAAI,KAAK,kBAAkB;AAC9C,cAAM,WAAW,OAAO,aAAa,IAAI,WAAW;AACpD,YAAI,UAAU;AACZ,gBAAM,iBAAiB,SAAS,MAAM,mBAAmB;AACzD,cAAI,gBAAgB;AAClB,mBAAO,eAAe,CAAC;AAAA,UACzB;AAAA,QACF;AAAA,MACF,SAAS,OAAO;AAAA,MAEhB;AAEA,aAAO;AAAA,IACT;AAEA,IAAM,eAAe,OACnB,KACA,QACA,MACA,SACA,YACA,gBAC2B;AAC3B,UAAI;AACF,cAAM,kBAAkB,YAAAA,QAAK,WAAW,OAAO,IAC3C,UACA,YAAAA,QAAK,QAAQ,QAAQ,IAAI,GAAG,OAAO;AAGvC,YAAI;AACJ,YAAI;AAEJ,YAAI;AAEF,cAAI,IAAI,WAAW,MAAM,GAAG;AAC1B,kBAAM,SAAS,IAAI,IAAI,GAAG;AAC1B,uBAAW,OAAO;AAClB,qBAAS,OAAO;AAAA,UAClB,OAAO;AAEL,kBAAM,SAAS,IAAI,IAAI,KAAK,kBAAkB;AAC9C,uBAAW,OAAO;AAClB,qBAAS,OAAO;AAAA,UAClB;AAAA,QACF,SAAS,OAAO;AAEd,gBAAM,CAAC,UAAU,GAAG,UAAU,IAAI,IAAI,MAAM,GAAG;AAC/C,qBAAW;AACX,mBAAS,WAAW,SAAS,IAAI,MAAM,WAAW,KAAK,GAAG,IAAI;AAAA,QAChE;AAEA,cAAM,WAAW,YAAAA,QAAK;AAAA,UACpB;AAAA,UACA,SAAS,QAAQ,OAAO,EAAE;AAAA,UAC1B,OAAO,YAAY,IAAI;AAAA,QACzB;AAEA,cAAM,MAAM,YAAAA,QAAK,QAAQ,QAAQ;AACjC,wBAAAC,QAAG,cAAc,GAAG;AAGpB,cAAM,WAAW,OAAO,SAAS,IAAI;AACrC,cAAM,aAAa,WAAW,OAAO,OAAO,KAAK,QAAQ,EAAE;AAC3D,cAAM,WAAW,iBAAiB,aAAa,UAAU;AAEzD,YAAI,UAAU;AAEZ,gBAAM,YAAY,iBAAiB,aAAa,GAAG;AACnD,gBAAM,iBAAiB,SAAS,QAAQ,SAAS,MAAM,SAAS;AAGhE,cAAI,gBAAAA,QAAG,WAAW,cAAc,GAAG;AACjC,mBAAO;AAAA,UACT;AAGA,0BAAAA,QAAG,cAAc,gBAAgB,UAAU;AAG3C,gBAAM,gBAAgB;AAAA,mBACT,QAAQ,KAAK,OAAO,YAAY,CAAC,IAAI,UAAU,EAAE;AAAA,kBAClD,QAAQ,GAAG,UAAU,EAAE,mBAAmB,SAAS;AAAA,mBACnD,oBAAI,KAAK,GAAE,YAAY,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,qBAKrB,SAAS;AAAA,uBACP,QAAQ;AAAA,wBACP,MAAM;AAAA,sBACR,QAAQ,GAAG,UAAU,EAAE;AAAA,sBACvB,WAAW;AAAA,kBACf,WAAW,MAAM;AAAA;AAAA;AAAA,YAGvB,cAAc,GAAG;AAAA;AAGvB,cAAI;AACF,kBAAM,gBAAgB,MAAM,gBAAAC,QAAS,OAAO,eAAe;AAAA,cACzD,QAAQ;AAAA,YACV,CAAC;AACD,4BAAAD,QAAG,cAAc,UAAU,eAAe,OAAO;AAAA,UACnD,SAAS,OAAO;AACd,4BAAAA,QAAG,cAAc,UAAU,eAAe,OAAO;AAAA,UACnD;AAEA,iBAAO;AAAA,QACT,OAAO;AAGL,cAAI,gBAAAA,QAAG,WAAW,QAAQ,GAAG;AAC3B,mBAAO;AAAA,UACT;AAEA,gBAAM,UAAU,WAAW,KAAK,SAAS,MAAM,IAAI,QAAQ;AAC3D,cAAI;AAEJ,cAAI,CAAC,WAAW,QAAQ,KAAK,MAAM,IAAI;AACrC,uBAAW;AAAA,cACT,OAAO;AAAA,cACP,SAAS,mBAAmB,cAAc,gBAAgB;AAAA,cAC1D,QAAQ,cAAc;AAAA,cACtB,MAAM;AAAA,YACR;AAAA,UACF,OAAO;AACL,gBAAI;AACF,yBAAW,KAAK,MAAM,OAAO;AAE7B,kBAAI,cAAc,cAAc,KAAK;AACnC,oBAAI,OAAO,aAAa,YAAY,aAAa,MAAM;AACrD,6BAAW;AAAA,oBACT,GAAG;AAAA,oBACH,kBAAkB;AAAA,oBAClB,mBAAmB;AAAA,kBACrB;AAAA,gBACF,OAAO;AACL,6BAAW;AAAA,oBACT,cAAc;AAAA,oBACd,kBAAkB;AAAA,oBAClB,mBAAmB;AAAA,kBACrB;AAAA,gBACF;AAAA,cACF;AAAA,YACF,QAAQ;AAEN,yBAAW;AAAA,gBACT,OAAO;AAAA,gBACP,SAAS,sBAAsB,cAAc,gBAAgB;AAAA,gBAC7D,QAAQ,cAAc;AAAA,gBACtB,MAAM;AAAA,gBACN,kBAAkB;AAAA,gBAClB,mBAAmB;AAAA,cACrB;AAAA,YACF;AAAA,UACF;AAEA,gBAAM,UAAU;AAAA,mBACH,QAAQ,KAAK,OAAO,YAAY,CAAC,IAAI,UAAU,EAAE;AAAA,kBAClD,QAAQ,GAAG,UAAU,EAAE;AAAA,mBACvB,oBAAI,KAAK,GAAE,YAAY,CAAC;AAAA;AAAA;AAAA;AAAA,UAIhC,KAAK,UAAU,QAAQ,CAAC;AAAA;AAAA,YAEtB,cAAc,GAAG;AAAA;AAGvB,cAAI;AACF,kBAAM,gBAAgB,MAAM,gBAAAC,QAAS,OAAO,SAAS;AAAA,cACnD,QAAQ;AAAA,YACV,CAAC;AACD,4BAAAD,QAAG,cAAc,UAAU,eAAe,OAAO;AACjD,mBAAO;AAAA,UACT,SAAS,OAAO;AACd,4BAAAA,QAAG,cAAc,UAAU,SAAS,OAAO;AAC3C,mBAAO;AAAA,UACT;AAAA,QACF;AAAA,MACF,SAAS,OAAO;AACd,gBAAQ,MAAM,gCAAgC,GAAG,KAAK,KAAK;AAC3D,gBAAQ;AAAA,UACN,oBAAoB,GAAG,YAAY,MAAM,gBAAgB,UAAU,iBAAiB,WAAW;AAAA,QACjG;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAEA,IAAM,wBAAwB,CAAC,QAA0B;AACvD,UAAI,CAAC,gBAAAA,QAAG,WAAW,GAAG,EAAG,QAAO,CAAC;AAEjC,YAAM,QAAkB,CAAC;AACzB,UAAI;AACF,cAAM,OAAO,gBAAAA,QAAG,YAAY,GAAG;AAC/B,aAAK,QAAQ,CAAC,SAAS;AACrB,gBAAM,WAAW,YAAAD,QAAK,KAAK,KAAK,IAAI;AACpC,gBAAM,OAAO,gBAAAC,QAAG,SAAS,QAAQ;AACjC,cAAI,KAAK,YAAY,GAAG;AACtB,kBAAM,KAAK,GAAG,sBAAsB,QAAQ,CAAC;AAAA,UAC/C,OAAO;AACL,kBAAM,KAAK,QAAQ;AAAA,UACrB;AAAA,QACF,CAAC;AAAA,MACH,SAAS,OAAO;AACd,gBAAQ,MAAM,2BAA2B,GAAG,KAAK,KAAK;AAAA,MACxD;AACA,aAAO;AAAA,IACT;AAEA,IAAM,iBAAiB,CAAC,YAAyC;AAC/D,YAAM,cAAc,oBAAI,IAAoB;AAE5C,UAAI,CAAC,gBAAAA,QAAG,WAAW,OAAO,GAAG;AAC3B,wBAAAA,QAAG,cAAc,OAAO;AACxB,eAAO;AAAA,MACT;AAEA,YAAM,QAAQ,sBAAsB,OAAO;AAE3C,YAAM,QAAQ,CAAC,aAAa;AAC1B,YAAI,CAAC,SAAS,SAAS,KAAK,GAAG;AAC7B;AAAA,QACF;AAEA,YAAI;AACF,gBAAM,eAAe,YAAAD,QAAK,SAAS,SAAS,QAAQ;AACpD,gBAAM,SAAS,YAAAA,QAAK,SAAS,UAAU,KAAK;AAC5C,gBAAM,UAAU,YAAAA,QAAK,QAAQ,YAAY;AACzC,gBAAM,UAAU,MAAM,YAAY,OAAO;AACzC,gBAAM,eAAe,YAAAA,QAAK,WAAW,QAAQ,IACzC,WACA,YAAAA,QAAK,QAAQ,QAAQ,IAAI,GAAG,QAAQ;AACxC,gBAAM,MAAM,GAAG,OAAO,IAAI,MAAM,MAAM,YAAY;AAElD,sBAAY,IAAI,KAAK,YAAY;AAAA,QACnC,SAAS,OAAO;AACd,kBAAQ,MAAM,0DAAuB,QAAQ,KAAK,KAAK;AAAA,QACzD;AAAA,MACF,CAAC;AAED,aAAO;AAAA,IACT;AAEA,IAAM,kBAAkB,OAAO,aAA4C;AACzE,YAAM,eAAe,YAAAA,QAAK,WAAW,QAAQ,IACzC,WACA,YAAAA,QAAK,QAAQ,QAAQ,IAAI,GAAG,QAAQ;AAExC,UAAI,CAAC,gBAAAC,QAAG,WAAW,YAAY,GAAG;AAChC,cAAM,IAAI,MAAM,6BAA6B,YAAY,EAAE;AAAA,MAC7D;AAEA,YAAM,UAAU,gBAAAA,QAAG,aAAa,cAAc,OAAO;AACrD,YAAM,qBAAqB,QAAQ,MAAM,uBAAuB;AAChE,YAAM,gBAAgB,qBAAqB,mBAAmB,CAAC,IAAI;AAGnE,UAAI;AACJ,UAAI,eAAe;AACjB,cAAM,YAAY,cAAc,MAAM,kCAAkC;AACxE,YAAI,WAAW;AACb,wBAAc,UAAU,CAAC,EAAE,KAAK;AAAA,QAClC;AAAA,MACF;AAEA,YAAM,EAAE,SAAS,WAAW,IAAI,MAAM,OACpC,GAAG,YAAY,MAAM,KAAK,IAAI,CAAC;AAGjC,YAAM,iBACJ,OAAO,eAAe,aAAa,WAAW,IAAI;AACpD,YAAM,iBAAiB,OAAO,gBAAgB,SAAS;AACvD,YAAM,SAAyB;AAAA,QAC7B,GAAG;AAAA,QACH,GAAI,kBAAkB,CAAC;AAAA,MACzB;AAEA,YAAM,eAAe,CAAC;AACtB,YAAM,UACJ,OAAO,OAAO,SAAS,YAAY,OAAO,SAAS,OAC9C,OAAO,OACR;AACN,YAAM,WAAW,YAAY,QAAQ,kBAAkB;AAEvD,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU,WACN,mBAAmB,SAAS,YAAY,QACxC,eACE,KAAK,UAAU,OAAO,QAAQ,MAAM,MAAM,CAAC,IAC3C;AAAA,QACN;AAAA,MACF;AAAA,IACF;AAEA,IAAM,gBAAgB,OACpB,UACA,aACkB;AAClB,YAAM,eAAe,YAAAD,QAAK,WAAW,QAAQ,IACzC,WACA,YAAAA,QAAK,QAAQ,QAAQ,IAAI,GAAG,QAAQ;AAExC,UAAI,SAAS,SAAS,iBAAiB;AAGvC,UAAI,SAAS,gBAAgB,QAAW;AACtC,YAAI,QAAQ;AAEV,cAAI,eAAe,KAAK,MAAM,GAAG;AAC/B,qBAAS,OAAO;AAAA,cACd;AAAA,cACA,gBAAgB,SAAS,WAAW;AAAA,YACtC;AAAA,UACF,OAAO;AAEL,qBAAS,OAAO;AAAA,cACd;AAAA,cACA,mBAAmB,SAAS,WAAW;AAAA;AAAA,YACzC;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,YAAM,UAAU,SAAS,GAAG,MAAM;AAAA,IAAO;AACzC,YAAM,eAAe,GAAG,OAAO,kBAAkB,KAAK,UAAU,SAAS,QAAQ,MAAM,CAAC,CAAC;AAAA;AAEzF,UAAI;AACF,cAAM,gBAAgB,MAAM,gBAAAE,QAAS,OAAO,cAAc;AAAA,UACxD,QAAQ;AAAA,QACV,CAAC;AACD,wBAAAD,QAAG,cAAc,cAAc,eAAe,OAAO;AAAA,MACvD,SAAS,OAAO;AACd,gBAAQ,MAAM,wCAAwC,KAAK;AAC3D,wBAAAA,QAAG,cAAc,cAAc,cAAc,OAAO;AAAA,MACtD;AAAA,IACF;AAAA;AAAA;;;ACzcA;AAAA;AAAA,kBAAAE;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAAC,eAAiB;AACjB,IAAAC,mBAAe;;;ACDf,sBAAqB;AACrB,oBAAqB;AACrB,IAAAC,eAAiB;AACjB,IAAAC,mBAAe;AACf,kBAAiB;AACjB,mBAAkB;AAElB;;;ACPA,IAAAC,eAAiB;AACjB;AAgCO,IAAM,gBAAgB;AAE7B,SAAS,oBAAoB,OAAuB;AAClD,SAAO,MAAM,SAAS,GAAG,IAAI,QAAQ,GAAG,KAAK;AAC/C;AAEA,SAAS,WAAW,OAAuB;AACzC,SAAO,MACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ,EACtB,QAAQ,MAAM,OAAO;AAC1B;AAEO,SAAS,yBACd,OAC4B;AAC5B,MAAI,UAAU,SAAS,UAAU,QAAW;AAC1C,WAAO,EAAE,OAAO,eAAe,cAAc,KAAK;AAAA,EACpD;AACA,MAAI,UAAU,MAAM;AAClB,WAAO,EAAE,OAAO,eAAe,cAAc,KAAK;AAAA,EACpD;AACA,SAAO;AAAA,IACL,OAAO,oBAAoB,MAAM,SAAS,aAAa;AAAA,IACvD,cAAc,MAAM,gBAAgB;AAAA,EACtC;AACF;AAOO,SAAS,uBACd,SACgC;AAChC,MAAI,CAAC,QAAQ,WAAW;AACtB,WAAO;AAAA,EACT;AAEA,QAAM,kBAAkB,yBAAyB,QAAQ,SAAS;AAClE,QAAM,iBAAiB,oBAAoB,gBAAgB,KAAK;AAEhE,SAAO,OAAO,KAAK,QAAQ;AACzB,QAAI,CAAC,IAAI,KAAK;AACZ,aAAO;AAAA,IACT;AACA,UAAM,MAAM,IAAI,IAAI,IAAI,KAAK,kBAAkB;AAC/C,QAAI,CAAC,IAAI,SAAS,WAAW,cAAc,GAAG;AAC5C,aAAO;AAAA,IACT;AAEA,UAAM,uBAAuB;AAAA,MAC3B;AAAA,MACA;AAAA,MACA,SAAS,QAAQ;AAAA,MACjB;AAAA,MACA,WAAW,QAAQ;AAAA,MACnB;AAAA,MACA,gBAAgB,QAAQ;AAAA,IAC1B,CAAC;AAED,WAAO;AAAA,EACT;AACF;AAEA,eAAe,uBACb,SACe;AACf,QAAM,EAAE,KAAK,KAAK,eAAe,IAAI;AACrC,QAAM,MAAM,IAAI,IAAI,IAAI,OAAO,gBAAgB,kBAAkB;AACjE,QAAM,kBAAkB,oBAAoB,cAAc;AAE1D,MACE,IAAI,aAAa,gBAAgB,MAAM,GAAG,EAAE,KAC5C,IAAI,aAAa,iBACjB;AACA,UAAM,mBAAmB,OAAO;AAChC;AAAA,EACF;AAEA,QAAM,eAAe,IAAI,SAAS,WAAW,eAAe,IACxD,IAAI,SAAS,MAAM,gBAAgB,MAAM,IACzC;AAEJ,MAAI,gBAAgB,aAAa,WAAW,MAAM,GAAG;AACnD,UAAM,mBAAmB,EAAE,GAAG,SAAS,UAAU,aAAa,MAAM,CAAC,EAAE,CAAC;AACxE;AAAA,EACF;AAEA,MAAI,aAAa;AACjB,MAAI,IAAI,WAAW;AACrB;AAEA,eAAe,mBAAmB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAoC;AAClC,QAAM,YAAY,KAAK,UAAU,oBAAoB,cAAc,CAAC;AACpE,QAAM,kBAAkB,KAAK,UAAU,gBAAgB,YAAY;AACnE,QAAM,mBAAmB,WAAW,SAAS;AAE7C,QAAM,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BA03BW,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6BAuDb,SAAS;AAAA;AAAA,0BAEZ,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA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","names":["path","fs","prettier","automock","import_path","import_fs_extra","import_path","import_fs_extra","import_path","path","saveMockData","buildMockIndex","key","path","fs","debounce","chokidar","sendProxyRequest","https","http","import_path","import_fs_extra","fs","path","http","automock","path","fs"]}