qat-cli 0.2.7 → 0.2.8

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.
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/commands/init.ts","../src/services/detector.ts","../src/services/config.ts","../src/services/mock-server.ts","../src/ai/noop-provider.ts","../src/ai/openai-provider.ts","../src/ai/provider.ts","../src/services/test-reviewer.ts","../src/services/source-analyzer.ts","../src/services/template.ts","../src/services/global-config.ts","../src/commands/create.ts","../src/commands/run.ts","../src/runners/vitest-runner.ts","../src/runners/playwright-runner.ts","../src/runners/lighthouse-runner.ts","../src/commands/mock.ts","../src/commands/report.ts","../src/services/reporter.ts","../src/commands/visual.ts","../src/services/visual.ts","../src/commands/setup.ts","../src/commands/status.ts","../src/commands/change.ts"],"sourcesContent":["#!/usr/bin/env node\r\n\r\n/**\r\n * QAT CLI 入口\r\n * 面向Vue项目的自动化测试命令行工具\r\n */\r\n\r\nimport { Command } from 'commander';\r\nimport chalk from 'chalk';\r\nimport { registerInitCommand } from './commands/init.js';\r\nimport { registerCreateCommand } from './commands/create.js';\r\nimport { registerRunCommand } from './commands/run.js';\r\nimport { registerMockCommand } from './commands/mock.js';\r\nimport { registerReportCommand } from './commands/report.js';\r\nimport { registerVisualCommand } from './commands/visual.js';\r\nimport { registerSetupCommand } from './commands/setup.js';\r\nimport { registerStatusCommand } from './commands/status.js';\r\nimport { registerChangeCommand } from './commands/change.js';\r\n\r\nconst VERSION = '0.2.7';\r\n\r\n/** 打印 QAT Logo */\r\nfunction printLogo(): void {\r\n const logo = `\r\n ${chalk.bold.cyan(' ___ _ _ _ _ _____ _ _ ')}\r\n ${chalk.bold.cyan(' / _ \\\\ _ _ (_) ___ | | __ / \\\\ _ _ | |_ ___ |_ _| ___ ___ | |_ (_) _ __ __ _ ')}\r\n ${chalk.bold.cyan(' | | | | | | | | | | / __| | |/ / / _ \\\\ | | | | | __| / _ \\\\ | | / _ \\\\ / __| | __| | | | \\'_ \\\\ / _` |')}\r\n ${chalk.bold.cyan(' | |_| | | |_| | | | | (__ | < / ___ \\\\ | |_| | | |_ | (_) | | | | __/ \\\\__ \\\\ | |_ | | | | | | | (_| |')}\r\n ${chalk.bold.cyan(' \\\\__\\\\_\\\\ \\\\__,_| |_| \\\\___| |_|\\\\_\\\\ /_/ \\\\_\\\\ \\\\__,_| \\\\__| \\\\___/ |_| \\\\___| |___/ \\\\__| |_| |_| |_| \\\\__, |')}\r\n ${chalk.bold.cyan(' |___/ ')}\r\n ${chalk.gray(' CLI自动化测试工具 v')}${chalk.green(VERSION)}\r\n`;\r\n console.log(logo);\r\n}\r\n\r\nconst program = new Command();\r\n\r\nprogram\r\n .name('qat')\r\n .description('CLI自动化测试工具 - 面向Vue项目,集成Vitest、Playwright,覆盖测试全流程')\r\n .version(VERSION)\r\n .option('-c, --config <path>', '指定配置文件路径')\r\n .option('-v, --verbose', '显示详细输出')\r\n .hook('preAction', async (thisCommand) => {\r\n // 打印 Logo\r\n printLogo();\r\n\r\n const opts = thisCommand.opts();\r\n if (opts.verbose) {\r\n process.env.QAT_VERBOSE = 'true';\r\n }\r\n if (opts.config) {\r\n process.env.QAT_CONFIG_PATH = opts.config;\r\n }\r\n\r\n // 加载用户自定义框架注册文件\r\n const { loadExternalFrameworks } = await import('./services/framework-registry.js');\r\n const loadResult = await loadExternalFrameworks(process.cwd());\r\n\r\n if (loadResult.loaded > 0 && opts.verbose) {\r\n console.log(chalk.gray(` [ext] 已加载 ${loadResult.loaded} 个外部扩展文件: ${loadResult.files.join(', ')}`));\r\n }\r\n\r\n if (loadResult.errors.length > 0) {\r\n for (const err of loadResult.errors) {\r\n console.log(chalk.yellow(` [ext] 警告: ${err.file} - ${err.error}`));\r\n }\r\n }\r\n });\r\n\r\n// 注册所有命令\r\nregisterInitCommand(program);\r\nregisterCreateCommand(program);\r\nregisterRunCommand(program);\r\nregisterMockCommand(program);\r\nregisterReportCommand(program);\r\nregisterVisualCommand(program);\r\nregisterSetupCommand(program);\r\nregisterStatusCommand(program);\r\nregisterChangeCommand(program);\r\n\r\n// 未知命令提示\r\nprogram.on('command:*', (operands) => {\r\n console.error(chalk.red(`\\n 未知命令: ${operands[0]}`));\r\n console.log(chalk.gray(` 使用 qat --help 查看可用命令\\n`));\r\n process.exit(1);\r\n});\r\n\r\nprogram.parse();\r\n","/**\r\n * init 命令 - 检测项目结构,生成配置、目录和测试用例\r\n * AI 配置从全局 ~/.qat/ai.json 读取\r\n * 按文件类型智能选择测试类型:.vue → 组件测试, utils/composables → 单元测试, api → API测试\r\n */\r\n\r\nimport { Command } from 'commander';\r\nimport chalk from 'chalk';\r\nimport inquirer from 'inquirer';\r\nimport ora from 'ora';\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport type { InitOptions, QATConfig, TestType } from '../types/index.js';\r\nimport { detectProject, discoverVueComponents, discoverUtilityFiles } from '../services/detector.js';\r\nimport { writeConfigFile, DEFAULT_CONFIG } from '../services/config.js';\r\nimport { initMockRoutesDir } from '../services/mock-server.js';\r\nimport { testAIConnection, getAIProvider, isAIAvailable } from '../ai/provider.js';\r\nimport { generateWithReview, printReviewReport, type ReviewResult, type ReviewReportEntry } from '../services/test-reviewer.js';\r\nimport { scanAPICalls, generateMockRoutesFromAPICalls, analyzeFile } from '../services/source-analyzer.js';\r\nimport type { PropInfo, EmitInfo } from '../services/source-analyzer.js';\r\nimport { renderTemplate } from '../services/template.js';\r\nimport {\r\n loadGlobalAIConfig,\r\n saveGlobalAIConfig,\r\n isGlobalAIConfigured,\r\n toAIConfig,\r\n maskApiKey,\r\n getAIConfigPath,\r\n} from '../services/global-config.js';\r\n\r\n/** 测试类型到输出子目录的映射 */\r\nconst TEST_TYPE_DIR: Record<TestType, string> = {\r\n unit: 'tests/unit',\r\n component: 'tests/component',\r\n e2e: 'tests/e2e',\r\n api: 'tests/api',\r\n visual: 'tests/visual',\r\n performance: 'tests/e2e',\r\n};\r\n\r\n/**\r\n * 根据文件路径智能判断应该使用哪种测试类型\r\n * - .vue 文件 → component 组件测试\r\n * - composables/hooks → unit 单元测试\r\n * - utils/helpers/services → unit 单元测试\r\n * - api/ 页面级 vue → api 测试\r\n */\r\nfunction inferTestType(filePath: string): TestType {\r\n const normalized = filePath.replace(/\\\\/g, '/').toLowerCase();\r\n\r\n // API 文件\r\n if (/\\/api\\//.test(normalized) || /api\\.(ts|js)$/.test(normalized)) {\r\n return 'api';\r\n }\r\n\r\n // Vue 组件\r\n if (normalized.endsWith('.vue')) {\r\n return 'component';\r\n }\r\n\r\n // composables / hooks\r\n if (/\\/composables?\\//.test(normalized) || /\\/hooks?\\//.test(normalized) || /^use[A-Z]/.test(path.basename(filePath))) {\r\n return 'unit';\r\n }\r\n\r\n // utils / helpers / services / lib\r\n if (/\\/(utils|helpers|services|lib)\\//.test(normalized)) {\r\n return 'unit';\r\n }\r\n\r\n // 默认 unit\r\n return 'unit';\r\n}\r\n\r\nexport function registerInitCommand(program: Command): void {\r\n program\r\n .command('init')\r\n .description('初始化测试项目 - 检测项目、生成配置、自动创建测试用例')\r\n .option('-f, --force', '强制覆盖已有配置文件')\r\n .action(async (options: InitOptions) => {\r\n try {\r\n await executeInit(options);\r\n } catch (error) {\r\n console.error(chalk.red(`\\n ✗ ${error instanceof Error ? error.message : String(error)}\\n`));\r\n process.exit(1);\r\n }\r\n });\r\n}\r\n\r\nasync function executeInit(options: InitOptions): Promise<void> {\r\n // 1. 检测项目\r\n const spinner = ora('正在检测项目结构...').start();\r\n const projectInfo = detectProject();\r\n spinner.stop();\r\n\r\n // 2. 显示检测结果\r\n displayProjectInfo(projectInfo);\r\n\r\n // 3. 非Vue项目警告\r\n if (!projectInfo.isVue) {\r\n const { proceed } = await inquirer.prompt([\r\n {\r\n type: 'confirm',\r\n name: 'proceed',\r\n message: '未检测到 Vue 项目,是否继续初始化?',\r\n default: false,\r\n },\r\n ]);\r\n if (!proceed) {\r\n console.log(chalk.gray('\\n 已取消初始化\\n'));\r\n return;\r\n }\r\n }\r\n\r\n // 4. 检查全局 AI 配置,未配置则引导配置\r\n let globalAI = loadGlobalAIConfig();\r\n if (!globalAI) {\r\n console.log(chalk.cyan(' AI 模型配置 (首次使用需配置,之后可通过 qat change 修改)\\n'));\r\n globalAI = await promptAIConfig();\r\n saveGlobalAIConfig(globalAI);\r\n console.log(chalk.gray(` 配置已保存至 ${getAIConfigPath()}\\n`));\r\n } else {\r\n // 同一行显示当前大模型\r\n console.log(chalk.green(` ✓ 当前 AI 模型: ${chalk.white(globalAI.model)} @ ${chalk.gray(globalAI.baseUrl)} (${maskApiKey(globalAI.apiKey)})\\n`));\r\n }\r\n\r\n const aiConfig = toAIConfig(globalAI);\r\n\r\n // 5. AI 连通性测试\r\n if (aiConfig.apiKey || aiConfig.baseUrl) {\r\n const testSpinner = ora(`正在测试 AI 连通性 (${globalAI.model})...`).start();\r\n try {\r\n const result = await testAIConnection(aiConfig);\r\n if (result.ok) {\r\n testSpinner.succeed(`AI 连通正常 ${chalk.gray(`${globalAI.model} (${result.latencyMs}ms)`)}`);\r\n } else {\r\n testSpinner.fail(`AI 连通异常: ${result.message}`);\r\n console.log(chalk.yellow(' 可运行 qat change 修改 AI 配置。'));\r\n }\r\n } catch (error) {\r\n testSpinner.fail(`AI 连通测试失败: ${error instanceof Error ? error.message : String(error)}`);\r\n }\r\n }\r\n\r\n // 6. 组装项目配置(AI 不写入 qat.config.js)\r\n const config = buildProjectConfig(projectInfo);\r\n\r\n // 7. 生成配置文件\r\n let configPath: string;\r\n const existingConfigPath = path.join(process.cwd(), 'qat.config.js');\r\n const existingTsPath = path.join(process.cwd(), 'qat.config.ts');\r\n const configExists = fs.existsSync(existingConfigPath) || fs.existsSync(existingTsPath);\r\n\r\n if (configExists && !options.force) {\r\n const { overwrite } = await inquirer.prompt([\r\n {\r\n type: 'confirm',\r\n name: 'overwrite',\r\n message: '配置文件 qat.config.js 已存在,是否覆盖?',\r\n default: true,\r\n },\r\n ]);\r\n if (!overwrite) {\r\n console.log(chalk.gray(' 保留现有配置文件,继续后续步骤...'));\r\n configPath = existingConfigPath;\r\n } else {\r\n const fileSpinner = ora('正在覆盖配置文件...').start();\r\n try {\r\n configPath = await writeConfigFile(process.cwd(), config, true);\r\n fileSpinner.succeed('配置文件已覆盖');\r\n } catch (error) {\r\n fileSpinner.fail('配置文件覆盖失败');\r\n throw error;\r\n }\r\n }\r\n } else {\r\n const fileSpinner = ora('正在生成配置文件...').start();\r\n try {\r\n configPath = await writeConfigFile(process.cwd(), config, options.force);\r\n fileSpinner.succeed('配置文件已生成');\r\n } catch (error) {\r\n fileSpinner.fail('配置文件生成失败');\r\n throw error;\r\n }\r\n }\r\n\r\n // 8. 创建测试目录结构\r\n const dirSpinner = ora('正在创建测试目录...').start();\r\n const createdDirs = createTestDirectories(config);\r\n dirSpinner.succeed('测试目录已创建');\r\n\r\n // 9. 初始化Mock路由\r\n if (config.mock?.enabled !== false) {\r\n const mockDir = config.mock?.routesDir || DEFAULT_CONFIG.mock.routesDir;\r\n initMockRoutesDir(mockDir);\r\n\r\n const srcDir = config.project?.srcDir || 'src';\r\n const apiCalls = scanAPICalls(srcDir);\r\n\r\n if (apiCalls.length > 0) {\r\n const mockRoutes = generateMockRoutesFromAPICalls(apiCalls);\r\n const mockFilePath = path.join(process.cwd(), mockDir, 'auto-generated.json');\r\n\r\n if (!fs.existsSync(mockFilePath)) {\r\n fs.writeFileSync(mockFilePath, JSON.stringify(mockRoutes, null, 2), 'utf-8');\r\n console.log(chalk.green(` 自动发现 ${apiCalls.length} 个 API 接口,已生成 Mock 路由`));\r\n }\r\n } else {\r\n console.log(chalk.gray(' 未发现 API 调用,已生成示例 Mock 路由'));\r\n }\r\n }\r\n\r\n // 10. 扫描源码 + 用户选择文件 + 生成测试用例\r\n const useAI = isAIAvailable(aiConfig);\r\n const generatedFiles = await autoGenerateTests(config, projectInfo, aiConfig, useAI);\r\n\r\n // 11. 输出结果\r\n displayResult(configPath, createdDirs, generatedFiles, projectInfo);\r\n}\r\n\r\n// ─── AI 配置向导 ────────────────────────────────────────────\r\n\r\nasync function promptAIConfig() {\r\n const answers = await inquirer.prompt([\r\n {\r\n type: 'input',\r\n name: 'apiKey',\r\n message: 'API Key (Ollama 本地可留空):',\r\n default: '',\r\n },\r\n {\r\n type: 'input',\r\n name: 'baseUrl',\r\n message: 'API Base URL:',\r\n default: 'https://api.deepseek.com/v1',\r\n validate: (input: string) => {\r\n if (!input.trim()) return 'Base URL 不能为空';\r\n if (!input.trim().startsWith('http')) return 'URL 必须以 http(s):// 开头';\r\n return true;\r\n },\r\n },\r\n {\r\n type: 'input',\r\n name: 'model',\r\n message: '模型名称:',\r\n default: 'deepseek-chat',\r\n validate: (input: string) => {\r\n if (!input.trim()) return '模型名称不能为空';\r\n return true;\r\n },\r\n },\r\n ]);\r\n\r\n return {\r\n provider: 'openai',\r\n apiKey: answers.apiKey?.trim() || '',\r\n baseUrl: answers.baseUrl?.trim() || 'https://api.deepseek.com/v1',\r\n model: answers.model?.trim() || 'deepseek-chat',\r\n };\r\n}\r\n\r\n// ─── 组装项目配置(不含 AI) ──────────────────────────────\r\n\r\nfunction buildProjectConfig(projectInfo: ReturnType<typeof detectProject>): Partial<QATConfig> {\r\n return {\r\n project: {\r\n framework: projectInfo.framework,\r\n uiLibrary: projectInfo.uiLibrary !== 'none' ? projectInfo.uiLibrary : undefined,\r\n monorepo: projectInfo.monorepo !== 'none' ? projectInfo.monorepo : undefined,\r\n vite: projectInfo.isVite,\r\n srcDir: projectInfo.srcDir,\r\n appDir: projectInfo.appDirs.length > 0 ? projectInfo.appDirs[0] : undefined,\r\n },\r\n vitest: {\r\n enabled: true,\r\n coverage: true,\r\n globals: true,\r\n environment: 'happy-dom',\r\n },\r\n playwright: {\r\n enabled: true,\r\n browsers: ['chromium'],\r\n baseURL: 'http://localhost:5173',\r\n screenshot: 'only-on-failure',\r\n },\r\n visual: {\r\n enabled: true,\r\n threshold: 0.1,\r\n baselineDir: 'tests/visual/baseline',\r\n diffDir: 'tests/visual/diff',\r\n },\r\n lighthouse: {\r\n enabled: true,\r\n urls: ['http://localhost:5173'],\r\n runs: 3,\r\n thresholds: {\r\n performance: 80,\r\n accessibility: 90,\r\n },\r\n },\r\n mock: {\r\n enabled: true,\r\n port: 3456,\r\n routesDir: 'tests/mock/routes',\r\n },\r\n };\r\n}\r\n\r\n// ─── 自动扫描 + 用户选择 + AI 生成测试用例 ────────────────\r\n\r\nasync function autoGenerateTests(\r\n config: Partial<QATConfig>,\r\n projectInfo: ReturnType<typeof detectProject>,\r\n aiConfig: import('../types/index.js').AIConfig,\r\n useAI: boolean,\r\n): Promise<string[]> {\r\n const srcDir = config.project?.srcDir || 'src';\r\n\r\n // 扫描源码文件\r\n const components = discoverVueComponents(process.cwd(), srcDir);\r\n const utilities = discoverUtilityFiles(process.cwd(), srcDir);\r\n\r\n if (components.length === 0 && utilities.length === 0) {\r\n console.log(chalk.yellow('\\n 未发现可测试的源码文件,跳过测试用例生成。'));\r\n return [];\r\n }\r\n\r\n // 合并所有文件并智能分类\r\n const allTargets: { filePath: string; testType: TestType }[] = [];\r\n\r\n for (const compPath of components.slice(0, 30)) {\r\n allTargets.push({ filePath: compPath, testType: inferTestType(compPath) });\r\n }\r\n\r\n for (const utilPath of utilities.slice(0, 30)) {\r\n allTargets.push({ filePath: utilPath, testType: inferTestType(utilPath) });\r\n }\r\n\r\n // 让用户多选要生成测试的文件\r\n const selectedTargets = await selectTargetFiles(allTargets);\r\n if (selectedTargets.length === 0) {\r\n console.log(chalk.gray('\\n 未选择任何文件,跳过测试用例生成。'));\r\n return [];\r\n }\r\n\r\n const total = selectedTargets.length;\r\n const genSpinner = ora(`正在生成测试用例 [0/${total}] ...`).start();\r\n\r\n const generatedFiles: string[] = [];\r\n const typeCount: Record<string, number> = {};\r\n const reviewReport: ReviewReportEntry[] = [];\r\n let current = 0;\r\n let failed = 0;\r\n\r\n for (const { filePath, testType } of selectedTargets) {\r\n current++;\r\n const fileLabel = path.basename(filePath);\r\n genSpinner.text = `正在生成测试用例 [${current}/${total}] ${chalk.cyan(fileLabel)} ...`;\r\n\r\n try {\r\n const result = await generateTestForTarget(\r\n testType,\r\n filePath,\r\n config,\r\n projectInfo,\r\n aiConfig,\r\n useAI,\r\n );\r\n if (result) {\r\n generatedFiles.push(result.filePath);\r\n typeCount[testType] = (typeCount[testType] || 0) + 1;\r\n if (result.reviewEntry) {\r\n reviewReport.push(result.reviewEntry);\r\n }\r\n }\r\n } catch {\r\n failed++;\r\n }\r\n }\r\n\r\n if (generatedFiles.length > 0) {\r\n const summary = Object.entries(typeCount)\r\n .map(([type, count]) => `${count} ${type}`)\r\n .join(', ');\r\n const approvedCount = reviewReport.filter((r) => r.approved).length;\r\n let msg = `已生成 ${generatedFiles.length}/${total} 个测试用例 (${summary}) — AI 审计 ${approvedCount}/${reviewReport.length} 通过`;\r\n if (failed > 0) msg += chalk.yellow(` ${failed} 个失败`);\r\n genSpinner.succeed(msg);\r\n } else {\r\n genSpinner.warn(`未生成测试用例 (${failed} 个失败)`);\r\n }\r\n\r\n // 打印审计报告\r\n if (reviewReport.length > 0) {\r\n printReviewReport(reviewReport);\r\n }\r\n\r\n return generatedFiles;\r\n}\r\n\r\n/**\r\n * 让用户多选要生成测试的文件\r\n */\r\nasync function selectTargetFiles(\r\n allTargets: { filePath: string; testType: TestType }[],\r\n): Promise<{ filePath: string; testType: TestType }[]> {\r\n const testTypeLabels: Record<TestType, string> = {\r\n unit: chalk.blue('[unit]'),\r\n component: chalk.magenta('[comp]'),\r\n e2e: chalk.green('[e2e]'),\r\n api: chalk.yellow('[api]'),\r\n visual: chalk.cyan('[visual]'),\r\n performance: chalk.gray('[perf]'),\r\n };\r\n\r\n const choices = allTargets.map(({ filePath, testType }) => ({\r\n name: `${testTypeLabels[testType]} ${filePath}`,\r\n value: filePath,\r\n short: filePath,\r\n }));\r\n\r\n // 添加手动输入选项\r\n choices.push({\r\n name: chalk.gray('✎ 手动输入文件/目录路径'),\r\n value: '__manual__',\r\n short: '手动输入',\r\n });\r\n\r\n const { selected } = await inquirer.prompt([\r\n {\r\n type: 'checkbox',\r\n name: 'selected',\r\n message: '选择要生成测试用例的文件 (空格选择/取消,回车确认):',\r\n choices,\r\n pageSize: 15,\r\n },\r\n ]);\r\n\r\n // 处理手动输入\r\n const manualPaths: string[] = [];\r\n if ((selected as string[]).includes('__manual__')) {\r\n const { manualInput } = await inquirer.prompt([\r\n {\r\n type: 'input',\r\n name: 'manualInput',\r\n message: '输入文件或目录路径(多个用逗号分隔):',\r\n default: '',\r\n filter: (input: string) =>\r\n input\r\n .split(',')\r\n .map((s: string) => s.trim())\r\n .filter(Boolean),\r\n },\r\n ]);\r\n\r\n // 验证路径是否存在,展开目录\r\n for (const p of manualInput as string[]) {\r\n const resolved = path.resolve(process.cwd(), p);\r\n if (fs.existsSync(resolved)) {\r\n const stat = fs.statSync(resolved);\r\n if (stat.isDirectory()) {\r\n // 展开目录下的 .vue / .ts / .js 文件\r\n const dirFiles = walkDirForTestableFiles(resolved);\r\n manualPaths.push(...dirFiles);\r\n } else if (stat.isFile()) {\r\n manualPaths.push(p.replace(/\\\\/g, '/'));\r\n }\r\n } else {\r\n console.log(chalk.yellow(` 路径不存在,已跳过: ${p}`));\r\n }\r\n }\r\n }\r\n\r\n // 根据用户选择过滤(排除手动输入标记)\r\n const selectedPaths = (selected as string[]).filter((s) => s !== '__manual__');\r\n const selectedSet = new Set(selectedPaths);\r\n\r\n const result = allTargets.filter((t) => selectedSet.has(t.filePath));\r\n\r\n // 添加手动输入的文件(推断测试类型)\r\n const existingPaths = new Set(result.map((t) => t.filePath));\r\n for (const mp of manualPaths) {\r\n if (!existingPaths.has(mp)) {\r\n result.push({ filePath: mp, testType: inferTestType(mp) });\r\n }\r\n }\r\n\r\n return result;\r\n}\r\n\r\n/**\r\n * 递归扫描目录中的可测试文件(.vue / .ts / .js)\r\n */\r\nfunction walkDirForTestableFiles(dir: string): string[] {\r\n const files: string[] = [];\r\n try {\r\n const entries = fs.readdirSync(dir, { withFileTypes: true });\r\n for (const entry of entries) {\r\n if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name.startsWith('.')) continue;\r\n const fullPath = path.join(dir, entry.name);\r\n if (entry.isDirectory()) {\r\n files.push(...walkDirForTestableFiles(fullPath));\r\n } else if (entry.isFile() && /\\.(vue|ts|js)$/.test(entry.name)) {\r\n files.push(path.relative(process.cwd(), fullPath).replace(/\\\\/g, '/'));\r\n }\r\n }\r\n } catch {\r\n // 权限不足等情况跳过\r\n }\r\n return files;\r\n}\r\n\r\n/**\r\n * 为单个目标文件生成测试(含 AI 审计)\r\n */\r\nasync function generateTestForTarget(\r\n testType: TestType,\r\n targetPath: string,\r\n config: Partial<QATConfig>,\r\n projectInfo: ReturnType<typeof detectProject>,\r\n aiConfig: import('../types/index.js').AIConfig,\r\n useAI: boolean,\r\n): Promise<{ filePath: string; reviewEntry?: ReviewReportEntry } | null> {\r\n const basename = path.basename(targetPath, path.extname(targetPath));\r\n const name = basename.replace(/[^a-zA-Z0-9]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') || `${testType}-test`;\r\n const outputDir = TEST_TYPE_DIR[testType];\r\n const fileName = `${name}.${testType === 'e2e' ? 'spec' : 'test'}.ts`;\r\n const filePath = path.join(process.cwd(), outputDir, fileName);\r\n\r\n // 不覆盖已有文件\r\n if (fs.existsSync(filePath)) return null;\r\n\r\n let content: string;\r\n let reviewEntry: ReviewReportEntry | undefined;\r\n\r\n if (useAI) {\r\n // AI 生成 + 审计员审核\r\n const result = await generateWithAIAndReview(testType, name, targetPath, aiConfig, projectInfo);\r\n content = result.code;\r\n reviewEntry = result.reviewEntry;\r\n } else {\r\n // 源码分析 + 模板渲染\r\n const analysis = analyzeFile(targetPath);\r\n content = renderTemplate(testType, {\r\n name,\r\n target: targetPath,\r\n framework: projectInfo.framework,\r\n vueVersion: projectInfo.vueVersion,\r\n typescript: projectInfo.typescript,\r\n uiLibrary: projectInfo.uiLibrary,\r\n extraImports: projectInfo.componentTestSetup?.extraImports,\r\n globalPlugins: projectInfo.componentTestSetup?.globalPlugins,\r\n globalStubs: projectInfo.componentTestSetup?.globalStubs,\r\n mountOptions: projectInfo.componentTestSetup?.mountOptions,\r\n exports: analysis.exports.map((e) => ({\r\n name: e.name,\r\n kind: e.kind,\r\n params: e.params,\r\n isAsync: e.isAsync,\r\n returnType: e.returnType,\r\n })),\r\n props: analysis.vueAnalysis?.props.map((p: PropInfo) => ({\r\n name: p.name,\r\n type: p.type,\r\n required: p.required,\r\n defaultValue: p.defaultValue,\r\n })),\r\n emits: analysis.vueAnalysis?.emits.map((e: EmitInfo) => ({\r\n name: e.name,\r\n params: e.params,\r\n })),\r\n methods: analysis.vueAnalysis?.methods,\r\n computed: analysis.vueAnalysis?.computed,\r\n });\r\n }\r\n\r\n // 确保目录存在\r\n if (!fs.existsSync(path.join(process.cwd(), outputDir))) {\r\n fs.mkdirSync(path.join(process.cwd(), outputDir), { recursive: true });\r\n }\r\n\r\n fs.writeFileSync(filePath, content, 'utf-8');\r\n return {\r\n filePath: path.relative(process.cwd(), filePath).replace(/\\\\/g, '/'),\r\n reviewEntry,\r\n };\r\n}\r\n\r\n/**\r\n * AI 辅助生成测试用例 + 审计员审核\r\n */\r\nasync function generateWithAIAndReview(\r\n type: TestType,\r\n name: string,\r\n target: string,\r\n aiConfig: import('../types/index.js').AIConfig,\r\n projectInfo: ReturnType<typeof detectProject>,\r\n): Promise<{ code: string; reviewEntry: ReviewReportEntry }> {\r\n // 读取源码\r\n const fullPath = path.resolve(process.cwd(), target);\r\n let sourceCode = '';\r\n if (fs.existsSync(fullPath)) {\r\n sourceCode = fs.readFileSync(fullPath, 'utf-8');\r\n }\r\n\r\n // 源码分析 - 给 AI 更精准的上下文\r\n const analysis = analyzeFile(target);\r\n\r\n const analysisSummary = analysis.exports.length > 0 || analysis.vueAnalysis ? {\r\n exports: analysis.exports.map((e) => ({\r\n name: e.name,\r\n kind: e.kind,\r\n params: e.params,\r\n isAsync: e.isAsync,\r\n })),\r\n props: analysis.vueAnalysis?.props.map((p: PropInfo) => ({\r\n name: p.name,\r\n type: p.type,\r\n required: p.required,\r\n })),\r\n emits: analysis.vueAnalysis?.emits.map((e: EmitInfo) => ({\r\n name: e.name,\r\n params: e.params,\r\n })),\r\n methods: analysis.vueAnalysis?.methods,\r\n computed: analysis.vueAnalysis?.computed,\r\n } : undefined;\r\n\r\n // 使用审计流程生成\r\n const reviewResult = await generateWithReview({\r\n testType: type,\r\n targetPath: target,\r\n sourceCode,\r\n analysis: analysisSummary,\r\n aiConfig,\r\n framework: 'vue',\r\n });\r\n\r\n // 构建文件头注释\r\n const headerComment = [\r\n `// AI Generated Test - ${name}`,\r\n `// 审计: ${reviewResult.approved ? '通过' : '未通过'} (${(reviewResult.reviewScore * 100).toFixed(0)}%) — ${reviewResult.attempts}次尝试`,\r\n reviewResult.reviewFeedback ? `// 审计意见: ${reviewResult.reviewFeedback}` : '',\r\n '',\r\n ].filter(Boolean).join('\\n');\r\n\r\n const code = `${headerComment}\\n${reviewResult.code}`;\r\n\r\n // 构建审计报告条目\r\n const reviewEntry: ReviewReportEntry = {\r\n target,\r\n testType: type,\r\n approved: reviewResult.approved,\r\n attempts: reviewResult.attempts,\r\n score: reviewResult.reviewScore,\r\n feedback: reviewResult.reviewFeedback,\r\n issues: reviewResult.approved ? [] : reviewResult.reviewIssues,\r\n };\r\n\r\n return { code, reviewEntry };\r\n}\r\n\r\n// ─── 显示项目检测信息 ──────────────────────────────────────\r\n\r\nfunction displayProjectInfo(info: ReturnType<typeof detectProject>): void {\r\n console.log(chalk.cyan('\\n 项目检测结果:'));\r\n console.log(chalk.gray(' ─────────────────────────────'));\r\n\r\n const items: [string, string | boolean | number | undefined][] = [\r\n ['项目名称', info.name],\r\n ['框架', info.frameworkConfidence > 0.5\r\n ? `${info.frameworkDisplayName} (置信度 ${Math.round(info.frameworkConfidence * 100)}%)`\r\n : info.frameworkDisplayName],\r\n ['Vue 项目', info.isVue ? `是 (v${info.vueVersion})` : '否'],\r\n ['UI 组件库', info.uiLibrary !== 'none' ? info.uiLibrary : '未检测到'],\r\n ['Vite 构建', info.isVite ? '是' : '否'],\r\n ['TypeScript', info.typescript ? '是' : '否'],\r\n ['包管理器', info.packageManager],\r\n ['Monorepo', info.monorepo !== 'none' ? info.monorepo : '否'],\r\n ['源码目录', info.srcDir],\r\n ];\r\n\r\n if (info.appDirs.length > 0) {\r\n items.push(['子项目', info.appDirs.join(', ')]);\r\n }\r\n\r\n if (info.testFrameworks.length > 0) {\r\n items.push(['已有测试框架', info.testFrameworks.join(', ')]);\r\n }\r\n\r\n if (info.componentDirs.length > 0) {\r\n items.push(['组件目录', info.componentDirs.join(', ')]);\r\n }\r\n\r\n for (const [label, value] of items) {\r\n const displayValue = value === true ? chalk.green('✓') : value === false ? chalk.red('✗') : String(value);\r\n console.log(` ${chalk.white(label.padEnd(12))} ${displayValue}`);\r\n }\r\n\r\n console.log();\r\n}\r\n\r\n// ─── 创建测试目录结构 ──────────────────────────────────────\r\n\r\nfunction createTestDirectories(config: Partial<QATConfig>): string[] {\r\n const dirs: string[] = [];\r\n\r\n const dirMap: Record<string, boolean> = {\r\n 'tests': true,\r\n 'tests/unit': config.vitest?.enabled !== false,\r\n 'tests/component': config.vitest?.enabled !== false,\r\n 'tests/e2e': config.playwright?.enabled !== false,\r\n 'tests/api': config.mock?.enabled !== false,\r\n 'tests/visual': config.visual?.enabled !== false,\r\n 'tests/visual/baseline': config.visual?.enabled !== false,\r\n 'tests/visual/diff': config.visual?.enabled !== false,\r\n 'tests/mock': config.mock?.enabled !== false,\r\n 'tests/mock/routes': config.mock?.enabled !== false,\r\n };\r\n\r\n for (const [dir, shouldCreate] of Object.entries(dirMap)) {\r\n if (shouldCreate) {\r\n const fullPath = path.join(process.cwd(), dir);\r\n if (!fs.existsSync(fullPath)) {\r\n fs.mkdirSync(fullPath, { recursive: true });\r\n dirs.push(dir);\r\n }\r\n }\r\n }\r\n\r\n return dirs;\r\n}\r\n\r\n// ─── 显示最终结果 ──────────────────────────────────────────\r\n\r\nfunction displayResult(\r\n configPath: string,\r\n createdDirs: string[],\r\n generatedFiles: string[],\r\n projectInfo: ReturnType<typeof detectProject>,\r\n): void {\r\n const relativeConfig = path.relative(process.cwd(), configPath);\r\n\r\n console.log(chalk.green('\\n ✓ 项目初始化完成!\\n'));\r\n\r\n // 配置文件\r\n console.log(chalk.white(' 已生成配置:'));\r\n console.log(chalk.gray(` ${relativeConfig}`));\r\n console.log();\r\n\r\n // 生成的测试用例(按类型分组)\r\n if (generatedFiles.length > 0) {\r\n console.log(chalk.white(' 已生成测试用例:'));\r\n for (const file of generatedFiles) {\r\n const typeLabel = file.includes('/unit/') ? chalk.blue('[unit]')\r\n : file.includes('/component/') ? chalk.magenta('[comp]')\r\n : file.includes('/api/') ? chalk.yellow('[api]')\r\n : chalk.gray('[other]');\r\n console.log(` ${typeLabel} ${chalk.gray(file)}`);\r\n }\r\n console.log();\r\n }\r\n\r\n // 下一步建议\r\n console.log(chalk.cyan(' 下一步:'));\r\n if (generatedFiles.length > 0) {\r\n console.log(chalk.gray(' 1. qat run 执行测试'));\r\n console.log(chalk.gray(' 2. qat create 添加更多测试用例'));\r\n console.log(chalk.gray(' 3. qat status 查看 AI 模型状态'));\r\n } else {\r\n console.log(chalk.gray(' 1. qat change 配置 AI 模型'));\r\n console.log(chalk.gray(' 2. qat create 创建测试用例'));\r\n }\r\n\r\n if (projectInfo.testFrameworks.length === 0) {\r\n console.log();\r\n console.log(chalk.yellow(' ⚠ 未检测到测试框架依赖,建议运行:'));\r\n console.log(chalk.gray(` qat setup`));\r\n }\r\n\r\n console.log();\r\n}\r\n\r\n/**\r\n * 构建目录树字符串\r\n */\r\nfunction buildDirectoryTree(dirs: string[]): string {\r\n if (dirs.length === 0) return ' (无新目录)';\r\n\r\n const tree: Record<string, string[]> = {};\r\n for (const dir of dirs) {\r\n const parts = dir.split('/');\r\n if (parts.length >= 2 && parts[0] === 'tests') {\r\n const parent = parts[0];\r\n const child = parts.slice(1).join('/');\r\n if (!tree[parent]) tree[parent] = [];\r\n tree[parent].push(child);\r\n } else {\r\n if (!tree[dir]) tree[dir] = [];\r\n }\r\n }\r\n\r\n let result = '';\r\n const topDirs = Object.keys(tree).sort();\r\n topDirs.forEach((dir, i) => {\r\n const isLast = i === topDirs.length - 1;\r\n const prefix = isLast ? '└── ' : '├── ';\r\n result += ` ${prefix}${dir}/\\n`;\r\n\r\n const children = tree[dir].sort();\r\n children.forEach((child, j) => {\r\n const isLastChild = j === children.length - 1;\r\n const childPrefix = isLast ? ' ' : '│ ';\r\n const connector = isLastChild ? '└── ' : '├── ';\r\n result += ` ${childPrefix}${connector}${child}/\\n`;\r\n });\r\n });\r\n\r\n return result;\r\n}\r\n","/**\r\n * 项目检测服务 - 识别框架版本、依赖、目录结构、组件发现\r\n * 集成框架注册表,支持 Vue / Vben / Nuxt 等框架自动检测\r\n */\r\n\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport type { FrameworkType, UILibrary, MonorepoType } from '../types/index.js';\r\nimport { detectFramework, discoverAppDirs, detectUILibrary, detectMonorepo } from './framework-registry.js';\r\nimport type { ComponentTestSetup } from './framework-registry.js';\r\n\r\nexport interface ProjectInfo {\r\n /** 是否为 Vue 项目 */\r\n isVue: boolean;\r\n /** 是否使用 Vite 构建 */\r\n isVite: boolean;\r\n /** Vue 主版本号 */\r\n vueVersion?: 2 | 3;\r\n /** 是否使用 TypeScript */\r\n typescript: boolean;\r\n /** 源码目录 */\r\n srcDir: string;\r\n /** 包管理器 */\r\n packageManager: 'npm' | 'yarn' | 'pnpm' | 'bun';\r\n /** 所有依赖名列表 */\r\n dependencies: string[];\r\n /** 组件目录列表 */\r\n componentDirs: string[];\r\n /** 页面/视图目录列表 */\r\n pageDirs: string[];\r\n /** 是否已有测试目录 */\r\n hasTests: boolean;\r\n /** 已有的测试框架 */\r\n testFrameworks: string[];\r\n /** 项目名称 */\r\n name: string;\r\n /** 检测到的框架类型 */\r\n framework: FrameworkType;\r\n /** 框架显示名称 */\r\n frameworkDisplayName: string;\r\n /** UI 组件库 */\r\n uiLibrary: UILibrary;\r\n /** Monorepo 类型 */\r\n monorepo: MonorepoType;\r\n /** Monorepo 子项目路径 */\r\n appDirs: string[];\r\n /** 框架检测置信度 */\r\n frameworkConfidence: number;\r\n /** 组件测试配置 */\r\n componentTestSetup?: ComponentTestSetup;\r\n}\r\n\r\n/**\r\n * 检测 Vue 版本 — 兼容 Monorepo,从子项目 package.json 中读取\r\n * 优先级:子项目 vue 依赖 > 根目录 vue 依赖\r\n */\r\nfunction detectVueVersion(cwd: string, rootDeps: Record<string, string>): 2 | 3 | null {\r\n // 1. 先尝试根目录\r\n if (rootDeps['vue']) {\r\n const v = parseVueMajorVersion(rootDeps['vue']);\r\n if (v) return v;\r\n }\r\n\r\n // 2. Monorepo: 遍历子项目查找 vue 依赖\r\n const appDirs = discoverAppDirs(cwd);\r\n for (const appDir of appDirs) {\r\n const subPkgPath = path.join(cwd, appDir, 'package.json');\r\n if (fs.existsSync(subPkgPath)) {\r\n try {\r\n const subPkg = JSON.parse(fs.readFileSync(subPkgPath, 'utf-8'));\r\n const subDeps = {\r\n ...(subPkg.dependencies as Record<string, string>),\r\n ...(subPkg.devDependencies as Record<string, string>),\r\n };\r\n if (subDeps['vue']) {\r\n const v = parseVueMajorVersion(subDeps['vue']);\r\n if (v) return v;\r\n }\r\n } catch {\r\n // 解析失败跳过\r\n }\r\n }\r\n }\r\n\r\n // 3. 检查 packages/ 目录下的子包\r\n const packagesPath = path.join(cwd, 'packages');\r\n if (fs.existsSync(packagesPath)) {\r\n try {\r\n const entries = fs.readdirSync(packagesPath, { withFileTypes: true });\r\n for (const entry of entries) {\r\n if (!entry.isDirectory()) continue;\r\n const subPkgPath = path.join(packagesPath, entry.name, 'package.json');\r\n if (fs.existsSync(subPkgPath)) {\r\n try {\r\n const subPkg = JSON.parse(fs.readFileSync(subPkgPath, 'utf-8'));\r\n const subDeps = {\r\n ...(subPkg.dependencies as Record<string, string>),\r\n ...(subPkg.devDependencies as Record<string, string>),\r\n };\r\n if (subDeps['vue']) {\r\n const v = parseVueMajorVersion(subDeps['vue']);\r\n if (v) return v;\r\n }\r\n } catch {\r\n // 解析失败跳过\r\n }\r\n }\r\n }\r\n } catch {\r\n // 目录读取失败跳过\r\n }\r\n }\r\n\r\n return null;\r\n}\r\n\r\n/**\r\n * 从 vue 依赖版本字符串中解析主版本号\r\n */\r\nfunction parseVueMajorVersion(versionStr: string): 2 | 3 | null {\r\n const clean = versionStr.replace(/^[\\^~>=<\\s]+/, '');\r\n // 处理 workspace:* 或 file: 协议 — 无法从版本号判断,返回 null 让后续逻辑推断\r\n if (clean === '*' || clean.startsWith('workspace:') || clean.startsWith('file:')) {\r\n return null;\r\n }\r\n const major = parseInt(clean.split('.')[0], 10);\r\n if (isNaN(major)) return null;\r\n return major >= 3 ? 3 : 2;\r\n}\r\n\r\n/**\r\n * 当版本号无法直接解析时,通过其他线索推断 Vue 主版本号\r\n * - 检查 vue-demi(Vue 2/3 通用库,通常与 Vue 2 组合式 API 一起使用)\r\n * - 检查 @vue/composition-api(仅 Vue 2)\r\n * - 检查 nuxt(Nuxt 3 = Vue 3,Nuxt 2 = Vue 2)\r\n * - 默认推断为 Vue 3(2024+ 新项目的主流)\r\n */\r\nfunction inferVueVersion(cwd: string, allDeps: Record<string, string>): 2 | 3 {\r\n // Nuxt 3 = Vue 3\r\n if (allDeps['nuxt']) {\r\n const nuxtVer = allDeps['nuxt'].replace(/^[\\^~>=<\\s]+/, '');\r\n const major = parseInt(nuxtVer.split('.')[0], 10);\r\n if (!isNaN(major) && major >= 3) return 3;\r\n if (!isNaN(major) && major < 3) return 2;\r\n }\r\n\r\n // @vue/composition-api 仅 Vue 2 使用\r\n if (allDeps['@vue/composition-api']) return 2;\r\n\r\n // vue-demi 的存在暗示可能使用了组合式 API,但不确定版本\r\n // 检查是否有 Vue 3 特有的依赖\r\n const vue3Indicators = [\r\n 'vue-tsc', // Vue 3 TypeScript 支持\r\n '@vue/compiler-sfc', // Vue 3 SFC 编译器\r\n 'unplugin-vue', // Vue 3 Vite 插件\r\n 'vite-plugin-vue', // Vue 3 Vite 插件\r\n ];\r\n for (const dep of vue3Indicators) {\r\n if (allDeps[dep]) return 3;\r\n }\r\n\r\n // 检查 vite.config 文件中是否有 vue 插件\r\n const viteConfigPaths = ['vite.config.ts', 'vite.config.js'];\r\n for (const cfg of viteConfigPaths) {\r\n const cfgPath = path.join(cwd, cfg);\r\n if (fs.existsSync(cfgPath)) {\r\n try {\r\n const content = fs.readFileSync(cfgPath, 'utf-8');\r\n // @vitejs/plugin-vue2 或 vite-plugin-vue2 明确是 Vue 2\r\n if (content.includes('plugin-vue2') || content.includes('vite-plugin-vue2')) return 2;\r\n // @vitejs/plugin-vue 是 Vue 3\r\n if (content.includes('@vitejs/plugin-vue') || content.includes('unplugin-vue')) return 3;\r\n } catch {\r\n // 读取失败跳过\r\n }\r\n }\r\n }\r\n\r\n // 默认推断为 Vue 3\r\n return 3;\r\n}\r\n\r\n/**\r\n * 检测项目信息\r\n */\r\nexport function detectProject(cwd: string = process.cwd()): ProjectInfo {\r\n const info: ProjectInfo = {\r\n isVue: false,\r\n isVite: false,\r\n typescript: false,\r\n srcDir: 'src',\r\n packageManager: 'npm',\r\n dependencies: [],\r\n componentDirs: [],\r\n pageDirs: [],\r\n hasTests: false,\r\n testFrameworks: [],\r\n name: path.basename(cwd),\r\n framework: 'vue',\r\n frameworkDisplayName: 'Vue',\r\n uiLibrary: 'none',\r\n monorepo: 'none',\r\n appDirs: [],\r\n frameworkConfidence: 0,\r\n };\r\n\r\n // 读取 package.json\r\n const pkgPath = path.join(cwd, 'package.json');\r\n let pkg: Record<string, unknown> = {};\r\n let allDeps: Record<string, string> = {};\r\n\r\n if (fs.existsSync(pkgPath)) {\r\n try {\r\n pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));\r\n } catch {\r\n // package.json 解析失败,跳过\r\n }\r\n\r\n allDeps = {\r\n ...(pkg.dependencies as Record<string, string>),\r\n ...(pkg.devDependencies as Record<string, string>),\r\n };\r\n\r\n info.dependencies = Object.keys(allDeps);\r\n info.name = (pkg.name as string) || info.name;\r\n\r\n // 检测 Vue(优先从子项目 package.json 读取,兼容 Monorepo)\r\n const vueVersion = detectVueVersion(cwd, allDeps);\r\n if (vueVersion) {\r\n info.isVue = true;\r\n info.vueVersion = vueVersion;\r\n } else if (allDeps['vue']) {\r\n // vue 依赖存在但版本号无法解析(如 workspace:*),仍标记为 Vue 项目\r\n info.isVue = true;\r\n info.vueVersion = inferVueVersion(cwd, allDeps);\r\n } else {\r\n // 无 vue 依赖,尝试从文件系统检测(项目中有 .vue 文件)\r\n info.isVue = detectVueByFileSystem(cwd);\r\n if (info.isVue) {\r\n info.vueVersion = inferVueVersion(cwd, allDeps);\r\n }\r\n }\r\n\r\n // 检测 Vite\r\n info.isVite = !!allDeps['vite'] || fs.existsSync(path.join(cwd, 'vite.config.ts')) || fs.existsSync(path.join(cwd, 'vite.config.js'));\r\n\r\n // 检测 TypeScript\r\n info.typescript =\r\n !!allDeps['typescript'] ||\r\n fs.existsSync(path.join(cwd, 'tsconfig.json')) ||\r\n fs.existsSync(path.join(cwd, 'tsconfig.app.json'));\r\n\r\n // 检测已有测试框架\r\n if (allDeps['vitest']) info.testFrameworks.push('vitest');\r\n if (allDeps['@playwright/test']) info.testFrameworks.push('playwright');\r\n if (allDeps['jest']) info.testFrameworks.push('jest');\r\n if (allDeps['cypress']) info.testFrameworks.push('cypress');\r\n if (allDeps['@vue/test-utils']) info.testFrameworks.push('@vue/test-utils');\r\n }\r\n\r\n // 获取根目录文件列表(用于框架检测)\r\n const rootFiles = getRootFiles(cwd);\r\n\r\n // 使用框架注册表检测\r\n const frameworkResult = detectFramework({\r\n cwd,\r\n dependencies: allDeps,\r\n rootFiles,\r\n });\r\n\r\n if (frameworkResult) {\r\n info.framework = frameworkResult.framework;\r\n info.frameworkDisplayName = frameworkResult.displayName;\r\n info.frameworkConfidence = frameworkResult.confidence;\r\n info.uiLibrary = frameworkResult.uiLibrary;\r\n info.monorepo = frameworkResult.monorepo;\r\n info.appDirs = frameworkResult.appDirs;\r\n info.srcDir = frameworkResult.srcDir;\r\n info.componentTestSetup = frameworkResult.componentTestSetup;\r\n\r\n // 框架注册表返回的目录\r\n info.componentDirs = frameworkResult.componentDirs.filter((d) =>\r\n fs.existsSync(path.join(cwd, d)),\r\n );\r\n info.pageDirs = frameworkResult.pageDirs.filter((d) =>\r\n fs.existsSync(path.join(cwd, d)),\r\n );\r\n\r\n // Vben / Nuxt 本质也是 Vue\r\n if (frameworkResult.framework === 'vben' || frameworkResult.framework === 'nuxt') {\r\n info.isVue = true;\r\n info.vueVersion = 3;\r\n }\r\n\r\n // 框架注册表检测到 Vue 类型时,确保 isVue 被标记\r\n if (frameworkResult.framework === 'vue' && !info.isVue) {\r\n info.isVue = true;\r\n }\r\n\r\n // 框架注册表检测到 Vue 但 vueVersion 未设置时,推断版本\r\n if (info.isVue && !info.vueVersion) {\r\n info.vueVersion = detectVueVersion(cwd, allDeps) || inferVueVersion(cwd, allDeps);\r\n }\r\n } else {\r\n // 回退到原始检测逻辑\r\n info.srcDir = detectSrcDir(cwd);\r\n info.uiLibrary = detectUILibrary(allDeps);\r\n info.monorepo = detectMonorepo(cwd, rootFiles);\r\n info.appDirs = discoverAppDirs(cwd);\r\n info.componentDirs = discoverComponentDirs(cwd, info.srcDir);\r\n info.pageDirs = discoverPageDirs(cwd, info.srcDir);\r\n }\r\n\r\n // 检测已有测试目录\r\n const possibleTestDirs = ['tests', 'test', '__tests__', 'spec'];\r\n for (const dir of possibleTestDirs) {\r\n if (fs.existsSync(path.join(cwd, dir))) {\r\n info.hasTests = true;\r\n break;\r\n }\r\n }\r\n\r\n // 检测包管理器\r\n if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) {\r\n info.packageManager = 'pnpm';\r\n } else if (fs.existsSync(path.join(cwd, 'yarn.lock'))) {\r\n info.packageManager = 'yarn';\r\n } else if (fs.existsSync(path.join(cwd, 'bun.lockb'))) {\r\n info.packageManager = 'bun';\r\n }\r\n\r\n return info;\r\n}\r\n\r\n/**\r\n * 通过文件系统检测是否为 Vue 项目\r\n * 当 package.json 中没有 vue 依赖时,检查源码目录中是否存在 .vue 文件\r\n */\r\nfunction detectVueByFileSystem(cwd: string): boolean {\r\n const possibleSrcDirs = ['src', 'lib', 'app'];\r\n for (const srcDir of possibleSrcDirs) {\r\n const srcPath = path.join(cwd, srcDir);\r\n if (fs.existsSync(srcPath)) {\r\n try {\r\n if (hasVueFilesInDir(srcPath, 2)) return true;\r\n } catch {\r\n // 权限不足等情况跳过\r\n }\r\n }\r\n }\r\n\r\n // 也检查根目录下的 components / pages\r\n const rootVueDirs = ['components', 'pages', 'views'];\r\n for (const dir of rootVueDirs) {\r\n const dirPath = path.join(cwd, dir);\r\n if (fs.existsSync(dirPath)) {\r\n try {\r\n if (hasVueFilesInDir(dirPath, 1)) return true;\r\n } catch {\r\n // 权限不足等情况跳过\r\n }\r\n }\r\n }\r\n\r\n return false;\r\n}\r\n\r\n/**\r\n * 递归检查目录中是否存在 .vue 文件(限制递归深度防止性能问题)\r\n */\r\nfunction hasVueFilesInDir(dir: string, maxDepth: number): boolean {\r\n if (maxDepth < 0) return false;\r\n try {\r\n const entries = fs.readdirSync(dir, { withFileTypes: true });\r\n for (const entry of entries) {\r\n if (entry.name === 'node_modules' || entry.name === 'dist') continue;\r\n if (entry.isFile() && entry.name.endsWith('.vue')) return true;\r\n if (entry.isDirectory()) {\r\n if (hasVueFilesInDir(path.join(dir, entry.name), maxDepth - 1)) return true;\r\n }\r\n }\r\n } catch {\r\n // 权限不足等情况跳过\r\n }\r\n return false;\r\n}\r\n\r\n/**\r\n * 获取根目录文件和目录列表\r\n */\r\nfunction getRootFiles(cwd: string): string[] {\r\n try {\r\n return fs.readdirSync(cwd);\r\n } catch {\r\n return [];\r\n }\r\n}\r\n\r\n/**\r\n * 检测源码目录(回退逻辑)\r\n */\r\nfunction detectSrcDir(cwd: string): string {\r\n const possibleSrcDirs = ['src', 'lib', 'app'];\r\n for (const dir of possibleSrcDirs) {\r\n if (fs.existsSync(path.join(cwd, dir))) {\r\n return dir;\r\n }\r\n }\r\n return 'src';\r\n}\r\n\r\n/**\r\n * 发现 Vue 组件目录(回退逻辑)\r\n */\r\nfunction discoverComponentDirs(cwd: string, srcDir: string): string[] {\r\n const dirs: string[] = [];\r\n const srcPath = path.join(cwd, srcDir);\r\n\r\n if (!fs.existsSync(srcPath)) return dirs;\r\n\r\n const commonDirs = [\r\n 'components',\r\n 'src/components',\r\n 'src/views/components',\r\n 'src/shared/components',\r\n ];\r\n\r\n for (const dir of commonDirs) {\r\n const fullPath = path.join(cwd, dir);\r\n if (fs.existsSync(fullPath)) {\r\n dirs.push(dir);\r\n }\r\n }\r\n\r\n // 递归查找含 .vue 文件的目录\r\n try {\r\n const entries = fs.readdirSync(srcPath, { withFileTypes: true });\r\n for (const entry of entries) {\r\n if (entry.isDirectory() && !dirs.includes(`${srcDir}/${entry.name}`)) {\r\n const subDir = path.join(srcPath, entry.name);\r\n const hasVueFiles = fs.readdirSync(subDir).some((f) => f.endsWith('.vue'));\r\n if (hasVueFiles && entry.name !== 'node_modules') {\r\n dirs.push(`${srcDir}/${entry.name}`);\r\n }\r\n }\r\n }\r\n } catch {\r\n // 权限不足等情况跳过\r\n }\r\n\r\n return dirs;\r\n}\r\n\r\n/**\r\n * 发现页面/视图目录(回退逻辑)\r\n */\r\nfunction discoverPageDirs(cwd: string, _srcDir: string): string[] {\r\n const dirs: string[] = [];\r\n const commonDirs = ['pages', 'views', 'src/pages', 'src/views'];\r\n\r\n for (const dir of commonDirs) {\r\n if (fs.existsSync(path.join(cwd, dir))) {\r\n dirs.push(dir);\r\n }\r\n }\r\n\r\n return dirs;\r\n}\r\n\r\n/**\r\n * 发现项目中的 Vue 组件文件\r\n * @returns 组件文件相对路径列表\r\n */\r\nexport function discoverVueComponents(cwd: string, srcDir: string): string[] {\r\n const components: string[] = [];\r\n const searchPaths = [path.join(cwd, srcDir)];\r\n\r\n // Monorepo: 也扫描子项目\r\n const appDirs = discoverAppDirs(cwd);\r\n for (const appDir of appDirs) {\r\n const appSrcPath = path.join(cwd, appDir, 'src');\r\n if (fs.existsSync(appSrcPath)) {\r\n searchPaths.push(appSrcPath);\r\n }\r\n }\r\n\r\n for (const searchPath of searchPaths) {\r\n if (!fs.existsSync(searchPath)) continue;\r\n try {\r\n walkForVueFiles(searchPath, cwd, components);\r\n } catch {\r\n // 权限不足等情况跳过\r\n }\r\n }\r\n\r\n return components;\r\n}\r\n\r\n/**\r\n * 递归查找 .vue 文件\r\n */\r\nfunction walkForVueFiles(dir: string, cwd: string, result: string[]): void {\r\n const entries = fs.readdirSync(dir, { withFileTypes: true });\r\n for (const entry of entries) {\r\n const fullPath = path.join(dir, entry.name);\r\n if (entry.isDirectory() && entry.name !== 'node_modules' && entry.name !== 'dist') {\r\n walkForVueFiles(fullPath, cwd, result);\r\n } else if (entry.isFile() && entry.name.endsWith('.vue')) {\r\n result.push(path.relative(cwd, fullPath).replace(/\\\\/g, '/'));\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * 发现项目中的工具函数/服务文件\r\n * @returns 文件相对路径列表\r\n */\r\nexport function discoverUtilityFiles(cwd: string, srcDir: string): string[] {\r\n const files: string[] = [];\r\n const searchPaths = [path.join(cwd, srcDir)];\r\n\r\n // Monorepo: 也扫描子项目和共享包\r\n const appDirs = discoverAppDirs(cwd);\r\n for (const appDir of appDirs) {\r\n const appSrcPath = path.join(cwd, appDir, 'src');\r\n if (fs.existsSync(appSrcPath)) {\r\n searchPaths.push(appSrcPath);\r\n }\r\n }\r\n\r\n const utilityPatterns = [\r\n /^(utils|helpers|services|composables|hooks|api|lib)/,\r\n ];\r\n\r\n for (const searchPath of searchPaths) {\r\n if (!fs.existsSync(searchPath)) continue;\r\n try {\r\n walkForUtilityFiles(searchPath, cwd, searchPath, utilityPatterns, files);\r\n } catch {\r\n // 权限不足等情况跳过\r\n }\r\n }\r\n\r\n return files;\r\n}\r\n\r\n/**\r\n * 递归查找工具函数文件\r\n */\r\nfunction walkForUtilityFiles(\r\n dir: string,\r\n cwd: string,\r\n rootSearchPath: string,\r\n patterns: RegExp[],\r\n result: string[],\r\n): void {\r\n const entries = fs.readdirSync(dir, { withFileTypes: true });\r\n for (const entry of entries) {\r\n const fullPath = path.join(dir, entry.name);\r\n if (entry.isDirectory()) {\r\n if (entry.name !== 'node_modules' && entry.name !== 'dist' && entry.name !== 'components') {\r\n const relativePath = path.relative(rootSearchPath, fullPath).replace(/\\\\/g, '/');\r\n if (patterns.some((p) => p.test(relativePath))) {\r\n walkForUtilityFiles(fullPath, cwd, rootSearchPath, patterns, result);\r\n }\r\n }\r\n } else if (entry.isFile() && (entry.name.endsWith('.ts') || entry.name.endsWith('.js'))) {\r\n result.push(path.relative(cwd, fullPath).replace(/\\\\/g, '/'));\r\n }\r\n }\r\n}\r\n","/**\r\n * 配置管理服务 - 读取/解析/校验/生成 qat.config.ts\r\n */\r\n\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport chalk from 'chalk';\r\nimport type { QATConfig } from '../types/index.js';\r\n\r\n/** 默认配置 */\r\nexport const DEFAULT_CONFIG: QATConfig = {\r\n project: {\r\n framework: 'vue',\r\n vite: true,\r\n srcDir: 'src',\r\n },\r\n vitest: {\r\n enabled: true,\r\n coverage: true,\r\n globals: true,\r\n environment: 'happy-dom',\r\n },\r\n playwright: {\r\n enabled: true,\r\n browsers: ['chromium'],\r\n baseURL: 'http://localhost:5173',\r\n screenshot: 'only-on-failure',\r\n },\r\n visual: {\r\n enabled: true,\r\n threshold: 0.1,\r\n baselineDir: 'tests/visual/baseline',\r\n diffDir: 'tests/visual/diff',\r\n },\r\n lighthouse: {\r\n enabled: true,\r\n urls: ['http://localhost:5173'],\r\n runs: 3,\r\n thresholds: {\r\n performance: 80,\r\n accessibility: 90,\r\n },\r\n },\r\n mock: {\r\n enabled: true,\r\n port: 3456,\r\n routesDir: 'tests/mock/routes',\r\n },\r\n report: {\r\n outputDir: 'qat-report',\r\n open: false,\r\n },\r\n};\r\n\r\n/** 配置缓存 */\r\nlet cachedConfig: QATConfig | null = null;\r\n\r\n/**\r\n * defineConfig 辅助函数 - 提供类型提示,用于用户 qat.config.ts\r\n */\r\nexport function defineConfig(config: Partial<QATConfig>): Partial<QATConfig> {\r\n return config;\r\n}\r\n\r\n/**\r\n * 自动查找配置文件 (优先 .js,兼容 .ts)\r\n */\r\nfunction findConfigFile(): string {\r\n const cwd = process.cwd();\r\n const jsPath = path.join(cwd, 'qat.config.js');\r\n const tsPath = path.join(cwd, 'qat.config.ts');\r\n if (fs.existsSync(jsPath)) return jsPath;\r\n if (fs.existsSync(tsPath)) return tsPath;\r\n return 'qat.config.js';\r\n}\r\n\r\n/**\r\n * 加载配置文件\r\n * @param configPath 配置文件路径,默认为当前目录下 qat.config.ts\r\n * @param forceReload 强制重新加载(跳过缓存)\r\n */\r\nexport async function loadConfig(configPath?: string, forceReload = false): Promise<QATConfig> {\r\n if (cachedConfig && !forceReload) {\r\n return cachedConfig;\r\n }\r\n\r\n const filePath = configPath || process.env.QAT_CONFIG_PATH || findConfigFile();\r\n\r\n try {\r\n const configFile = await importConfig(filePath);\r\n const config = validateConfig(configFile);\r\n cachedConfig = config;\r\n return config;\r\n } catch (error) {\r\n if (isFileNotFoundError(error)) {\r\n if (process.env.QAT_VERBOSE === 'true') {\r\n console.log(chalk.yellow('未找到配置文件,使用默认配置'));\r\n }\r\n cachedConfig = { ...DEFAULT_CONFIG };\r\n return cachedConfig;\r\n }\r\n throw new Error(`配置文件加载失败: ${error instanceof Error ? error.message : String(error)}`);\r\n }\r\n}\r\n\r\n/**\r\n * 清除配置缓存\r\n */\r\nexport function clearConfigCache(): void {\r\n cachedConfig = null;\r\n}\r\n\r\n/**\r\n * 动态导入配置文件\r\n */\r\nasync function importConfig(filePath: string): Promise<Partial<QATConfig>> {\r\n const { resolve } = await import('node:path');\r\n const { pathToFileURL } = await import('node:url');\r\n const absolutePath = resolve(process.cwd(), filePath);\r\n\r\n if (!fs.existsSync(absolutePath)) {\r\n throw new Error(`Cannot find module '${absolutePath}'`);\r\n }\r\n\r\n // Windows 兼容:ESM import() 需要 file:// URL,不能直接用绝对路径\r\n const fileUrl = pathToFileURL(absolutePath).href;\r\n\r\n try {\r\n const module = await import(fileUrl);\r\n return module.default || module;\r\n } catch {\r\n // 尝试 .js 扩展名(.ts 文件可能无法直接加载)\r\n const jsPath = absolutePath.replace(/\\.ts$/, '.js');\r\n if (jsPath !== absolutePath && fs.existsSync(jsPath)) {\r\n const jsUrl = pathToFileURL(jsPath).href;\r\n const module = await import(jsUrl);\r\n return module.default || module;\r\n }\r\n throw new Error(`无法加载配置文件: ${absolutePath}`);\r\n }\r\n}\r\n\r\n/**\r\n * 校验配置结构 - 深度合并默认配置并校验字段合法性\r\n */\r\nexport function validateConfig(config: Partial<QATConfig>): QATConfig {\r\n // 深度合并\r\n const merged: QATConfig = {\r\n project: { ...DEFAULT_CONFIG.project, ...config.project },\r\n vitest: { ...DEFAULT_CONFIG.vitest, ...config.vitest },\r\n playwright: { ...DEFAULT_CONFIG.playwright, ...config.playwright },\r\n visual: { ...DEFAULT_CONFIG.visual, ...config.visual },\r\n lighthouse: { ...DEFAULT_CONFIG.lighthouse, ...config.lighthouse },\r\n mock: { ...DEFAULT_CONFIG.mock, ...config.mock },\r\n report: { ...DEFAULT_CONFIG.report, ...config.report },\r\n };\r\n\r\n // AI 配置 - 始终合并,确保存在\r\n if (config.ai) {\r\n merged.ai = {\r\n provider: config.ai.provider || 'openai',\r\n apiKey: config.ai.apiKey,\r\n baseUrl: config.ai.baseUrl,\r\n model: config.ai.model,\r\n };\r\n }\r\n\r\n // 校验必填字段\r\n if (!merged.project.srcDir) {\r\n throw new Error('配置校验失败: project.srcDir 不能为空');\r\n }\r\n\r\n // 校验数值范围\r\n if (merged.visual.threshold < 0 || merged.visual.threshold > 1) {\r\n throw new Error('配置校验失败: visual.threshold 必须在 0-1 之间');\r\n }\r\n\r\n if (merged.lighthouse.runs < 1) {\r\n throw new Error('配置校验失败: lighthouse.runs 不能小于 1');\r\n }\r\n\r\n if (merged.mock.port < 1 || merged.mock.port > 65535) {\r\n throw new Error('配置校验失败: mock.port 必须在 1-65535 之间');\r\n }\r\n\r\n // 校验浏览器列表\r\n const validBrowsers = ['chromium', 'firefox', 'webkit'];\r\n for (const browser of merged.playwright.browsers) {\r\n if (!validBrowsers.includes(browser)) {\r\n throw new Error(`配置校验失败: 不支持的浏览器 \"${browser}\",可选值: ${validBrowsers.join(', ')}`);\r\n }\r\n }\r\n\r\n return merged;\r\n}\r\n\r\n/**\r\n * 生成 qat.config.js 文件内容\r\n * AI 配置存储在 ~/.qat/ai.json,不在此文件中展示\r\n */\r\nexport function generateConfigFile(overrides: Partial<QATConfig> = {}): string {\r\n const config = validateConfig(overrides);\r\n\r\n return `// @ts-check\r\n/**\r\n * QAT 配置文件 - Quick Auto Testing\r\n * 修改后无需重启,下次运行 qat 命令时自动生效\r\n *\r\n * AI 模型配置请运行: qat change\r\n * AI 状态查看请运行: qat status\r\n */\r\nimport { defineConfig } from 'qat-cli';\r\n\r\nexport default defineConfig({\r\n project: {\r\n framework: '${config.project.framework}',\r\n vite: ${config.project.vite ?? true},\r\n srcDir: '${config.project.srcDir}',\r\n },\r\n vitest: {\r\n enabled: ${config.vitest.enabled},\r\n coverage: ${config.vitest.coverage},\r\n globals: ${config.vitest.globals},\r\n environment: '${config.vitest.environment}',\r\n },\r\n playwright: {\r\n enabled: ${config.playwright.enabled},\r\n browsers: [${config.playwright.browsers.map(b => `'${b}'`).join(', ')}],\r\n baseURL: '${config.playwright.baseURL}',\r\n screenshot: '${config.playwright.screenshot}',\r\n },\r\n visual: {\r\n enabled: ${config.visual.enabled},\r\n threshold: ${config.visual.threshold},\r\n baselineDir: '${config.visual.baselineDir}',\r\n diffDir: '${config.visual.diffDir}',\r\n },\r\n lighthouse: {\r\n enabled: ${config.lighthouse.enabled},\r\n urls: [${config.lighthouse.urls.map(u => `'${u}'`).join(', ')}],\r\n runs: ${config.lighthouse.runs},\r\n thresholds: {${Object.entries(config.lighthouse.thresholds)\r\n .map(([k, v]) => `\\n ${k}: ${v},`)\r\n .join('')}\\n },\r\n },\r\n mock: {\r\n enabled: ${config.mock.enabled},\r\n port: ${config.mock.port},\r\n routesDir: '${config.mock.routesDir}',\r\n },\r\n report: {\r\n outputDir: '${config.report.outputDir}',\r\n open: ${config.report.open},\r\n },\r\n});\r\n`;\r\n}\r\n\r\n/**\r\n * 写入配置文件到磁盘\r\n */\r\nexport async function writeConfigFile(\r\n cwd: string,\r\n overrides: Partial<QATConfig> = {},\r\n force = false,\r\n): Promise<string> {\r\n const configPath = path.join(cwd, 'qat.config.js');\r\n\r\n if (fs.existsSync(configPath) && !force) {\r\n throw new Error(`配置文件已存在: ${configPath},使用 --force 覆盖`);\r\n }\r\n\r\n const content = generateConfigFile(overrides);\r\n fs.writeFileSync(configPath, content, 'utf-8');\r\n\r\n return configPath;\r\n}\r\n\r\nfunction isFileNotFoundError(error: unknown): boolean {\r\n if (error instanceof Error) {\r\n return (\r\n error.message.includes('Cannot find') ||\r\n error.message.includes('ENOENT') ||\r\n error.message.includes('无法加载配置文件')\r\n );\r\n }\r\n return false;\r\n}\r\n","/**\r\n * Mock服务 - Express服务器,路由管理,数据模板\r\n */\r\n\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport type { Request, Response, NextFunction } from 'express';\r\n\r\n/** Mock路由配置 */\r\nexport interface MockRoute {\r\n method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\r\n path: string;\r\n status?: number;\r\n response?: unknown;\r\n delay?: number;\r\n headers?: Record<string, string>;\r\n}\r\n\r\n/** Mock服务器状态 */\r\nexport interface MockServerState {\r\n running: boolean;\r\n port: number;\r\n routes: MockRoute[];\r\n pid?: number;\r\n}\r\n\r\n/** 全局Mock服务器状态 */\r\nlet serverState: MockServerState = {\r\n running: false,\r\n port: 3456,\r\n routes: [],\r\n};\r\n\r\n/** 服务器实例引用 */\r\nlet serverInstance: ReturnType<typeof import('http').createServer> | null = null;\r\n\r\n/**\r\n * 获取Mock服务器状态\r\n */\r\nexport function getMockServerState(): MockServerState {\r\n return { ...serverState };\r\n}\r\n\r\n/**\r\n * 加载Mock路由配置\r\n * 支持从目录加载 .json 和 .js/.ts 路由文件\r\n */\r\nexport async function loadMockRoutes(routesDir: string): Promise<MockRoute[]> {\r\n const absDir = path.resolve(process.cwd(), routesDir);\r\n\r\n if (!fs.existsSync(absDir)) {\r\n return [];\r\n }\r\n\r\n const routes: MockRoute[] = [];\r\n const entries = fs.readdirSync(absDir, { withFileTypes: true });\r\n\r\n for (const entry of entries) {\r\n if (!entry.isFile()) continue;\r\n\r\n const filePath = path.join(absDir, entry.name);\r\n const ext = path.extname(entry.name);\r\n\r\n try {\r\n if (ext === '.json') {\r\n const content = fs.readFileSync(filePath, 'utf-8');\r\n const parsed = JSON.parse(content);\r\n if (Array.isArray(parsed)) {\r\n routes.push(...parsed);\r\n } else if (parsed.method && parsed.path) {\r\n routes.push(parsed as MockRoute);\r\n }\r\n } else if (ext === '.js' || ext === '.mjs') {\r\n const module = await import(filePath);\r\n const exported = module.default || module;\r\n if (Array.isArray(exported)) {\r\n routes.push(...exported);\r\n } else if (exported.method && exported.path) {\r\n routes.push(exported as MockRoute);\r\n }\r\n }\r\n } catch (error) {\r\n console.warn(`加载路由文件失败 ${entry.name}: ${error instanceof Error ? error.message : String(error)}`);\r\n }\r\n }\r\n\r\n return routes;\r\n}\r\n\r\n/**\r\n * 创建默认Mock路由\r\n */\r\nexport function createDefaultRoutes(): MockRoute[] {\r\n return [\r\n {\r\n method: 'GET',\r\n path: '/api/health',\r\n status: 200,\r\n response: { status: 'ok', timestamp: Date.now() },\r\n },\r\n {\r\n method: 'GET',\r\n path: '/api/*',\r\n status: 200,\r\n response: { message: 'Mock API response', data: null },\r\n delay: 100,\r\n },\r\n {\r\n method: 'POST',\r\n path: '/api/*',\r\n status: 201,\r\n response: { message: 'Created successfully', data: null },\r\n },\r\n {\r\n method: 'PUT',\r\n path: '/api/*',\r\n status: 200,\r\n response: { message: 'Updated successfully', data: null },\r\n },\r\n {\r\n method: 'DELETE',\r\n path: '/api/*',\r\n status: 204,\r\n response: null,\r\n },\r\n ];\r\n}\r\n\r\n/**\r\n * 启动Mock服务器\r\n */\r\nexport async function startMockServer(port: number, routes: MockRoute[]): Promise<void> {\r\n if (serverState.running) {\r\n throw new Error(`Mock服务器已在运行 (端口: ${serverState.port})`);\r\n }\r\n\r\n const express = await import('express');\r\n const app = express.default();\r\n\r\n // 中间件\r\n app.use(express.default.json());\r\n\r\n // 请求日志\r\n app.use((req: Request, _res: Response, next: NextFunction) => {\r\n if (process.env.QAT_VERBOSE === 'true') {\r\n console.log(` [Mock] ${req.method} ${req.url}`);\r\n }\r\n next();\r\n });\r\n\r\n // 注册路由\r\n const allRoutes = routes.length > 0 ? routes : createDefaultRoutes();\r\n\r\n for (const route of allRoutes) {\r\n const handler = async (req: Request, res: Response) => {\r\n // 模拟延迟\r\n if (route.delay && route.delay > 0) {\r\n await new Promise((resolve) => setTimeout(resolve, route.delay));\r\n }\r\n\r\n // 设置自定义响应头\r\n if (route.headers) {\r\n for (const [key, value] of Object.entries(route.headers)) {\r\n res.setHeader(key, value);\r\n }\r\n }\r\n\r\n res.setHeader('X-Mock-Server', 'qat');\r\n\r\n // 替换响应中的请求参数\r\n let response = route.response;\r\n if (typeof response === 'object' && response !== null) {\r\n const responseStr = JSON.stringify(response)\r\n .replace(/\\{\\{params\\.(\\w+)\\}\\}/g, (_match, key: string) => (req.params[key] as string) || '')\r\n .replace(/\\{\\{query\\.(\\w+)\\}\\}/g, (_match, key: string) => (req.query[key] as string) || '')\r\n .replace(/\\{\\{body\\.(\\w+)\\}\\}/g, (_match, key: string) => String((req.body as Record<string, unknown>)?.[key] ?? ''));\r\n try {\r\n response = JSON.parse(responseStr);\r\n } catch {\r\n // 保持原始响应\r\n }\r\n }\r\n\r\n res.status(route.status || 200).json(response);\r\n };\r\n\r\n // 根据方法注册路由\r\n const expressRoute = route.path;\r\n switch (route.method) {\r\n case 'GET':\r\n app.get(expressRoute, handler);\r\n break;\r\n case 'POST':\r\n app.post(expressRoute, handler);\r\n break;\r\n case 'PUT':\r\n app.put(expressRoute, handler);\r\n break;\r\n case 'DELETE':\r\n app.delete(expressRoute, handler);\r\n break;\r\n case 'PATCH':\r\n app.patch(expressRoute, handler);\r\n break;\r\n }\r\n }\r\n\r\n // 404 兜底\r\n app.use((req, res) => {\r\n res.status(404).json({\r\n error: 'Not Found',\r\n message: `No mock route defined for ${req.method} ${req.url}`,\r\n hint: '在 tests/mock/routes/ 目录下添加路由配置',\r\n });\r\n });\r\n\r\n // 启动服务器\r\n return new Promise((resolve, reject) => {\r\n serverInstance = app.listen(port, () => {\r\n serverState = {\r\n running: true,\r\n port,\r\n routes: allRoutes,\r\n pid: process.pid,\r\n };\r\n resolve();\r\n });\r\n\r\n serverInstance.on('error', (err: Error) => {\r\n reject(new Error(`Mock服务器启动失败: ${err.message}`));\r\n });\r\n });\r\n}\r\n\r\n/**\r\n * 停止Mock服务器\r\n */\r\nexport async function stopMockServer(): Promise<void> {\r\n if (!serverInstance || !serverState.running) {\r\n serverState = { ...serverState, running: false };\r\n return;\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n serverInstance!.close((err) => {\r\n if (err) {\r\n reject(new Error(`Mock服务器停止失败: ${err.message}`));\r\n return;\r\n }\r\n\r\n serverInstance = null;\r\n serverState = {\r\n running: false,\r\n port: serverState.port,\r\n routes: [],\r\n };\r\n resolve();\r\n });\r\n });\r\n}\r\n\r\n/**\r\n * 生成Mock路由配置文件模板\r\n */\r\nexport function generateMockRouteTemplate(name: string): string {\r\n return `[\r\n {\r\n method: 'GET',\r\n path: '/api/${name}',\r\n status: 200,\r\n response: {\r\n message: 'Success',\r\n data: {\r\n id: 1,\r\n name: '${name}',\r\n },\r\n },\r\n delay: 100,\r\n },\r\n {\r\n method: 'POST',\r\n path: '/api/${name}',\r\n status: 201,\r\n response: {\r\n message: 'Created',\r\n data: {\r\n id: 2,\r\n name: '{{body.name}}',\r\n },\r\n },\r\n },\r\n {\r\n method: 'DELETE',\r\n path: '/api/${name}/:id',\r\n status: 204,\r\n response: null,\r\n },\r\n]\r\n`;\r\n}\r\n\r\n/**\r\n * 初始化Mock路由目录\r\n */\r\nexport function initMockRoutesDir(routesDir: string): void {\r\n const absDir = path.resolve(process.cwd(), routesDir);\r\n\r\n if (!fs.existsSync(absDir)) {\r\n fs.mkdirSync(absDir, { recursive: true });\r\n }\r\n\r\n // 生成示例路由文件\r\n const examplePath = path.join(absDir, 'example.json');\r\n if (!fs.existsSync(examplePath)) {\r\n const exampleRoutes: MockRoute[] = [\r\n {\r\n method: 'GET',\r\n path: '/api/users',\r\n status: 200,\r\n response: {\r\n message: 'Success',\r\n data: [\r\n { id: 1, name: 'Alice' },\r\n { id: 2, name: 'Bob' },\r\n ],\r\n },\r\n },\r\n {\r\n method: 'GET',\r\n path: '/api/users/:id',\r\n status: 200,\r\n response: {\r\n message: 'Success',\r\n data: { id: 1, name: 'Alice' },\r\n },\r\n },\r\n ];\r\n fs.writeFileSync(examplePath, JSON.stringify(exampleRoutes, null, 2), 'utf-8');\r\n }\r\n}\r\n","/**\r\n * 默认空实现 - 未配置AI时的占位Provider\r\n */\r\n\r\nimport type { AIProvider, AICapability, AIGenerateTestRequest, AIGenerateTestResponse, AIAnalyzeResultRequest, AIAnalyzeResultResponse, AIReviewTestRequest, AIReviewTestResponse } from '../types/ai.js';\r\nimport type { TestError } from '../types/index.js';\r\n\r\nconst NOOP_CAPABILITIES: AICapability = {\r\n generateTest: false,\r\n analyzeResult: false,\r\n suggestFix: false,\r\n};\r\n\r\nexport class NoopAIProvider implements AIProvider {\r\n readonly name = 'noop';\r\n readonly capabilities = NOOP_CAPABILITIES;\r\n\r\n async generateTest(_req: AIGenerateTestRequest): Promise<AIGenerateTestResponse> {\r\n throw new Error(\r\n 'AI功能未配置。请在 qat.config.ts 中配置 ai.provider 后使用此功能。',\r\n );\r\n }\r\n\r\n async analyzeResult(_req: AIAnalyzeResultRequest): Promise<AIAnalyzeResultResponse> {\r\n throw new Error(\r\n 'AI功能未配置。请在 qat.config.ts 中配置 ai.provider 后使用此功能。',\r\n );\r\n }\r\n\r\n async suggestFix(_error: TestError): Promise<string[]> {\r\n throw new Error(\r\n 'AI功能未配置。请在 qat.config.ts 中配置 ai.provider 后使用此功能。',\r\n );\r\n }\r\n\r\n async reviewTest(_req: AIReviewTestRequest): Promise<AIReviewTestResponse> {\r\n throw new Error(\r\n 'AI功能未配置。请在 qat.config.ts 中配置 ai.provider 后使用此功能。',\r\n );\r\n }\r\n}\r\n","/**\r\n * OpenAI 兼容 Provider - 支持 OpenAI / DeepSeek / Moonshot / Ollama 等\r\n */\r\n\r\nimport type {\r\n AIProvider,\r\n AICapability,\r\n AIGenerateTestRequest,\r\n AIGenerateTestResponse,\r\n AIAnalyzeResultRequest,\r\n AIAnalyzeResultResponse,\r\n AIReviewTestRequest,\r\n AIReviewTestResponse,\r\n} from '../types/ai.js';\r\nimport type { TestError } from '../types/index.js';\r\n\r\n/** OpenAI Chat Completion 响应格式 */\r\ninterface ChatCompletionResponse {\r\n choices: Array<{\r\n message: {\r\n content: string;\r\n };\r\n finish_reason: string;\r\n }>;\r\n usage?: {\r\n prompt_tokens: number;\r\n completion_tokens: number;\r\n total_tokens: number;\r\n };\r\n}\r\n\r\nexport class OpenAICompatibleProvider implements AIProvider {\r\n readonly name: string;\r\n readonly capabilities: AICapability = {\r\n generateTest: true,\r\n analyzeResult: true,\r\n suggestFix: true,\r\n };\r\n\r\n private apiKey: string;\r\n private model: string;\r\n private baseUrl: string;\r\n\r\n constructor(config: { provider: string; apiKey?: string; model?: string; baseUrl?: string }) {\r\n this.name = config.provider;\r\n this.apiKey = config.apiKey || '';\r\n this.model = config.model || this.getDefaultModel(config.provider);\r\n this.baseUrl = config.baseUrl || this.getDefaultBaseUrl(config.provider);\r\n }\r\n\r\n async generateTest(req: AIGenerateTestRequest): Promise<AIGenerateTestResponse> {\r\n const systemPrompt = this.buildGenerateTestSystemPrompt(req);\r\n const userPrompt = this.buildGenerateTestUserPrompt(req);\r\n\r\n const content = await this.chat(systemPrompt, userPrompt);\r\n return this.parseGenerateTestResponse(content);\r\n }\r\n\r\n async analyzeResult(req: AIAnalyzeResultRequest): Promise<AIAnalyzeResultResponse> {\r\n const systemPrompt = `你是一个专业的测试分析专家。分析测试运行结果,找出问题根因,给出具体可操作的改进建议。\r\n输出格式:\r\n1. 分析摘要(1-3句话)\r\n2. 改进建议列表(每条建议具体、可操作)`;\r\n\r\n const resultSummary = req.testResults.map((r) => {\r\n const failed = r.suites.flatMap((s) => s.tests.filter((t) => t.status === 'failed'));\r\n return `类型: ${r.type}, 状态: ${r.status}, 耗时: ${r.duration}ms, 失败用例: ${failed.length}`;\r\n }).join('\\n');\r\n\r\n const errorDetails = req.errorLogs?.join('\\n') || req.testResults\r\n .flatMap((r) => r.suites.flatMap((s) => s.tests.filter((t) => t.status === 'failed' && t.error)))\r\n .map((t) => `[${t.name}] ${t.error?.message}`)\r\n .join('\\n') || '无错误详情';\r\n\r\n const userPrompt = `测试结果:\\n${resultSummary}\\n\\n错误详情:\\n${errorDetails}`;\r\n\r\n const content = await this.chat(systemPrompt, userPrompt);\r\n\r\n return {\r\n analysis: content.split('\\n')[0] || content.slice(0, 200),\r\n suggestions: content.split('\\n').filter((l) => l.trim().startsWith('-') || l.trim().startsWith('•') || l.trim().match(/^\\d+\\./)).map((l) => l.replace(/^[-•\\d.]+\\s*/, '').trim()).filter(Boolean),\r\n severity: req.testResults.some((r) => r.status === 'failed') ? 'error' : 'info',\r\n };\r\n }\r\n\r\n async suggestFix(error: TestError): Promise<string[]> {\r\n const systemPrompt = `你是一个专业的代码修复专家。根据测试错误信息,给出具体的修复建议。\r\n每条建议应该包含:\r\n1. 问题定位\r\n2. 修复方案\r\n3. 示例代码(如果适用)`;\r\n\r\n const userPrompt = `错误信息: ${error.message}\r\n${error.stack ? `堆栈: ${error.stack}` : ''}\r\n${error.expected ? `期望值: ${error.expected}` : ''}\r\n${error.actual ? `实际值: ${error.actual}` : ''}`;\r\n\r\n const content = await this.chat(systemPrompt, userPrompt);\r\n\r\n return content\r\n .split('\\n')\r\n .filter((l) => l.trim().startsWith('-') || l.trim().startsWith('•') || l.trim().match(/^\\d+\\./))\r\n .map((l) => l.replace(/^[-•\\d.]+\\s*/, '').trim())\r\n .filter(Boolean);\r\n }\r\n\r\n async reviewTest(req: AIReviewTestRequest): Promise<AIReviewTestResponse> {\r\n const systemPrompt = this.buildReviewTestSystemPrompt(req);\r\n const userPrompt = this.buildReviewTestUserPrompt(req);\r\n\r\n const content = await this.chat(systemPrompt, userPrompt);\r\n return this.parseReviewTestResponse(content);\r\n }\r\n\r\n // ─── 内部方法 ──────────────────────────────────────────────\r\n\r\n /**\r\n * 测试连通性 - 发送一条简单请求验证 API 可达\r\n * @returns 连通结果\r\n */\r\n async testConnection(): Promise<{ ok: boolean; message: string; latencyMs?: number }> {\r\n const url = `${this.baseUrl}/chat/completions`;\r\n const start = Date.now();\r\n\r\n const headers: Record<string, string> = {\r\n 'Content-Type': 'application/json',\r\n };\r\n\r\n if (this.apiKey) {\r\n headers['Authorization'] = `Bearer ${this.apiKey}`;\r\n }\r\n\r\n try {\r\n const response = await fetch(url, {\r\n method: 'POST',\r\n headers,\r\n body: JSON.stringify({\r\n model: this.model,\r\n messages: [{ role: 'user', content: 'Hi' }],\r\n max_tokens: 5,\r\n }),\r\n signal: AbortSignal.timeout(15000), // 15s timeout\r\n });\r\n\r\n const latencyMs = Date.now() - start;\r\n\r\n if (response.ok) {\r\n const data = (await response.json()) as ChatCompletionResponse;\r\n if (data.choices?.[0]?.message?.content !== undefined) {\r\n return { ok: true, message: `连通正常 (${latencyMs}ms)`, latencyMs };\r\n }\r\n return { ok: false, message: `API 返回格式异常: ${JSON.stringify(data).slice(0, 200)}`, latencyMs };\r\n }\r\n\r\n // 非 2xx\r\n const text = await response.text().catch(() => '');\r\n let detail = text.slice(0, 300);\r\n\r\n // 尝试提取错误信息\r\n try {\r\n const errObj = JSON.parse(text);\r\n if (errObj.error?.message) detail = errObj.error.message;\r\n } catch { /* ignore */ }\r\n\r\n if (response.status === 401) {\r\n return { ok: false, message: `认证失败: API Key 无效或已过期`, latencyMs };\r\n }\r\n if (response.status === 404) {\r\n return { ok: false, message: `接口不存在: 请检查 baseUrl 是否正确 (状态 404)`, latencyMs };\r\n }\r\n if (response.status === 429) {\r\n return { ok: false, message: `请求频率超限: 请稍后重试 (429)`, latencyMs };\r\n }\r\n\r\n return { ok: false, message: `HTTP ${response.status}: ${detail}`, latencyMs };\r\n } catch (error) {\r\n const latencyMs = Date.now() - start;\r\n if (error instanceof TypeError && error.message.includes('fetch')) {\r\n return { ok: false, message: `网络错误: 无法连接到 ${this.baseUrl},请检查地址是否正确`, latencyMs };\r\n }\r\n if (error instanceof Error && error.name === 'TimeoutError') {\r\n return { ok: false, message: `连接超时: ${this.baseUrl} 未在 15s 内响应`, latencyMs };\r\n }\r\n return { ok: false, message: `连接失败: ${error instanceof Error ? error.message : String(error)}`, latencyMs };\r\n }\r\n }\r\n\r\n private async chat(systemPrompt: string, userPrompt: string): Promise<string> {\r\n const url = `${this.baseUrl}/chat/completions`;\r\n\r\n const body = {\r\n model: this.model,\r\n messages: [\r\n { role: 'system', content: systemPrompt },\r\n { role: 'user', content: userPrompt },\r\n ],\r\n temperature: 0.3,\r\n max_tokens: 4096,\r\n };\r\n\r\n const headers: Record<string, string> = {\r\n 'Content-Type': 'application/json',\r\n };\r\n\r\n // Ollama 不需要 Authorization header\r\n if (this.apiKey) {\r\n headers['Authorization'] = `Bearer ${this.apiKey}`;\r\n }\r\n\r\n const response = await fetch(url, {\r\n method: 'POST',\r\n headers,\r\n body: JSON.stringify(body),\r\n signal: AbortSignal.timeout(60000), // 60s timeout\r\n });\r\n\r\n if (!response.ok) {\r\n const text = await response.text().catch(() => '');\r\n throw new Error(`AI API 请求失败 (${response.status}): ${text.slice(0, 500)}`);\r\n }\r\n\r\n const data = (await response.json()) as ChatCompletionResponse;\r\n\r\n if (!data.choices?.[0]?.message?.content) {\r\n throw new Error('AI API 返回空响应');\r\n }\r\n\r\n return data.choices[0].message.content;\r\n }\r\n\r\n private buildGenerateTestSystemPrompt(req: AIGenerateTestRequest): string {\r\n const typeMap: Record<string, string> = {\r\n unit: '单元测试(Vitest + @vue/test-utils)',\r\n component: '组件测试(Vitest + @vue/test-utils + mount)',\r\n e2e: 'E2E端到端测试(Playwright)',\r\n api: 'API接口测试(Vitest + fetch)',\r\n visual: '视觉回归测试(Playwright screenshot)',\r\n performance: '性能测试(Playwright + performance metrics)',\r\n };\r\n\r\n return `你是一个专业的前端测试工程师,擅长编写高质量的${typeMap[req.type] || req.type}。\r\n要求:\r\n1. 只输出测试代码,不要多余的解释\r\n2. 代码必须可直接运行,包含所有必要的 import\r\n3. 测试用例覆盖:正常路径、边界条件、错误处理\r\n4. 使用中文描述 it/test 块名称\r\n5. Vue 组件测试使用 @vue/test-utils 的 mount\r\n6. 如果有 props/emits 信息,务必针对每个 prop 和 emit 生成测试`;\r\n }\r\n\r\n private buildGenerateTestUserPrompt(req: AIGenerateTestRequest): string {\r\n let prompt = `请为以下文件生成${req.type}测试代码:\\n目标文件: ${req.target}\\n`;\r\n\r\n if (req.analysis) {\r\n prompt += '\\n源码分析结果:\\n';\r\n\r\n if (req.analysis.exports?.length > 0) {\r\n prompt += `导出项:\\n${req.analysis.exports.map((e) => {\r\n const params = e.params?.length ? `(${e.params.join(', ')})` : '';\r\n const asyncFlag = e.isAsync ? 'async ' : '';\r\n return ` - ${asyncFlag}${e.name}${params} [${e.kind}]`;\r\n }).join('\\n')}\\n`;\r\n }\r\n\r\n if (req.analysis.props?.length) {\r\n prompt += `Props:\\n${req.analysis.props.map((p) =>\r\n ` - ${p.name}: ${p.type}${p.required ? ' (必填)' : ' (可选)'}`,\r\n ).join('\\n')}\\n`;\r\n }\r\n\r\n if (req.analysis.emits?.length) {\r\n prompt += `Emits:\\n${req.analysis.emits.map((e) =>\r\n ` - ${e.name}${e.params?.length ? `(${e.params.join(', ')})` : ''}`,\r\n ).join('\\n')}\\n`;\r\n }\r\n\r\n if (req.analysis.methods?.length) {\r\n prompt += `Methods: ${req.analysis.methods.join(', ')}\\n`;\r\n }\r\n\r\n if (req.analysis.computed?.length) {\r\n prompt += `Computed: ${req.analysis.computed.join(', ')}\\n`;\r\n }\r\n }\r\n\r\n if (req.context) {\r\n prompt += `\\n源码内容:\\n\\`\\`\\`typescript\\n${req.context}\\n\\`\\`\\`\\n`;\r\n }\r\n\r\n if (req.framework) {\r\n prompt += `\\n框架: ${req.framework}`;\r\n }\r\n\r\n return prompt;\r\n }\r\n\r\n private parseGenerateTestResponse(content: string): AIGenerateTestResponse {\r\n // 提取代码块\r\n const codeBlockMatch = content.match(/```(?:typescript|ts|javascript|js)?\\s*\\n([\\s\\S]*?)```/);\r\n const code = codeBlockMatch\r\n ? codeBlockMatch[1].trim()\r\n : content.replace(/^(?:```[\\s\\S]*?\\n)?/, '').replace(/\\n?```$/, '').trim();\r\n\r\n // 提取描述(代码块之前的文字)\r\n const description = codeBlockMatch\r\n ? content.split('```')[0].trim().slice(0, 200)\r\n : 'AI generated test';\r\n\r\n return {\r\n code,\r\n description: description || 'AI generated test',\r\n confidence: 0.8,\r\n };\r\n }\r\n\r\n private buildReviewTestSystemPrompt(_req: AIReviewTestRequest): string {\r\n return `你是一位严谨的测试审计专家。你的职责是审查 AI 生成的测试用例是否与源码贴切且准确。\r\n\r\n审查标准:\r\n1. **测试类型匹配**:测试类型是否与源码文件性质匹配(如 .vue 应为组件测试,utils 应为单元测试)\r\n2. **覆盖完整性**:是否覆盖了源码中的核心导出项(函数、组件的 props/emits/methods)\r\n3. **断言有效性**:断言是否真实检验了被测行为,而非空断言或永真断言\r\n4. **导入正确性**:import 路径和模块是否正确\r\n5. **代码可运行性**:测试代码是否可直接运行,无语法错误\r\n\r\n输出格式(严格遵守):\r\nAPPROVED: true 或 false\r\nSCORE: 0.0 到 1.0 之间的评分\r\nFEEDBACK: 一句话审计意见\r\nISSUES: 问题列表(每行一个,格式 \"- 问题描述\")\r\nSUGGESTIONS: 改进建议列表(每行一个,格式 \"- 建议描述\")`;\r\n }\r\n\r\n private buildReviewTestUserPrompt(req: AIReviewTestRequest): string {\r\n let prompt = `请审查以下测试用例是否与源码贴切且准确。\r\n\r\n被测文件: ${req.target}\r\n测试类型: ${req.testType}\r\n\r\n--- 源码内容 ---\r\n\\`\\`\\`typescript\r\n${req.sourceCode}\r\n\\`\\`\\`\r\n\r\n--- 生成的测试代码 ---\r\n\\`\\`\\`typescript\r\n${req.testCode}\r\n\\`\\`\\``;\r\n\r\n if (req.analysis) {\r\n prompt += '\\n\\n--- 源码分析 ---';\r\n\r\n if (req.analysis.exports?.length > 0) {\r\n prompt += `\\n导出项:\\n${req.analysis.exports.map((e) => {\r\n const params = e.params?.length ? `(${e.params.join(', ')})` : '';\r\n const asyncFlag = e.isAsync ? 'async ' : '';\r\n return ` - ${asyncFlag}${e.name}${params} [${e.kind}]`;\r\n }).join('\\n')}`;\r\n }\r\n\r\n if (req.analysis.props?.length) {\r\n prompt += `\\nProps:\\n${req.analysis.props.map((p) =>\r\n ` - ${p.name}: ${p.type}${p.required ? ' (必填)' : ' (可选)'}`,\r\n ).join('\\n')}`;\r\n }\r\n\r\n if (req.analysis.emits?.length) {\r\n prompt += `\\nEmits:\\n${req.analysis.emits.map((e) =>\r\n ` - ${e.name}${e.params?.length ? `(${e.params.join(', ')})` : ''}`,\r\n ).join('\\n')}`;\r\n }\r\n }\r\n\r\n if (req.generationDescription) {\r\n prompt += `\\n\\n生成者说明: ${req.generationDescription}`;\r\n }\r\n\r\n return prompt;\r\n }\r\n\r\n private parseReviewTestResponse(content: string): AIReviewTestResponse {\r\n const approvedMatch = content.match(/APPROVED:\\s*(true|false)/i);\r\n const scoreMatch = content.match(/SCORE:\\s*([\\d.]+)/);\r\n const feedbackMatch = content.match(/FEEDBACK:\\s*(.+)/);\r\n\r\n const approved = approvedMatch ? approvedMatch[1].toLowerCase() === 'true' : false;\r\n const score = scoreMatch ? parseFloat(scoreMatch[1]) : (approved ? 0.7 : 0.3);\r\n const feedback = feedbackMatch ? feedbackMatch[1].trim() : '审计完成';\r\n\r\n // 提取问题列表\r\n const issues: string[] = [];\r\n const issuesMatch = content.match(/ISSUES:\\s*\\n([\\s\\S]*?)(?=SUGGESTIONS:|$)/);\r\n if (issuesMatch) {\r\n const lines = issuesMatch[1].split('\\n');\r\n for (const line of lines) {\r\n const trimmed = line.replace(/^[-•\\d.]+\\s*/, '').trim();\r\n if (trimmed) issues.push(trimmed);\r\n }\r\n }\r\n\r\n // 提取建议列表\r\n const suggestions: string[] = [];\r\n const suggestionsMatch = content.match(/SUGGESTIONS:\\s*\\n([\\s\\S]*?)$/);\r\n if (suggestionsMatch) {\r\n const lines = suggestionsMatch[1].split('\\n');\r\n for (const line of lines) {\r\n const trimmed = line.replace(/^[-•\\d.]+\\s*/, '').trim();\r\n if (trimmed) suggestions.push(trimmed);\r\n }\r\n }\r\n\r\n return {\r\n approved,\r\n score: Math.max(0, Math.min(1, score)),\r\n feedback,\r\n issues,\r\n suggestions,\r\n };\r\n }\r\n\r\n private getDefaultModel(provider: string): string {\r\n const modelMap: Record<string, string> = {\r\n openai: 'gpt-4o-mini',\r\n deepseek: 'deepseek-chat',\r\n moonshot: 'moonshot-v1-8k',\r\n zhipu: 'glm-4-flash',\r\n qwen: 'qwen-turbo',\r\n ollama: 'qwen2.5-coder:7b',\r\n };\r\n return modelMap[provider] || 'gpt-4o-mini';\r\n }\r\n\r\n private getDefaultBaseUrl(provider: string): string {\r\n const urlMap: Record<string, string> = {\r\n openai: 'https://api.openai.com/v1',\r\n deepseek: 'https://api.deepseek.com/v1',\r\n moonshot: 'https://api.moonshot.cn/v1',\r\n zhipu: 'https://open.bigmodel.cn/api/paas/v4',\r\n qwen: 'https://dashscope.aliyuncs.com/compatible-mode/v1',\r\n ollama: 'http://localhost:11434/v1',\r\n };\r\n return urlMap[provider] || 'https://api.openai.com/v1';\r\n }\r\n}\r\n","/**\r\n * AI Provider 管理中心 - 注册、加载、调度 AI Provider\r\n */\r\n\r\nimport type { AIConfig, AIPresetProvider } from '../types/ai.js';\r\nimport { NoopAIProvider } from './noop-provider.js';\r\nimport { OpenAICompatibleProvider } from './openai-provider.js';\r\n\r\n/** Provider 构造函数类型 */\r\nexport type AIProviderConstructor = new (config: AIConfig) => NoopAIProvider | OpenAICompatibleProvider;\r\n\r\n/** Provider 注册表 */\r\nconst providerRegistry = new Map<string, AIProviderConstructor>();\r\n\r\n/** 当前活跃的 Provider 实例 */\r\nlet activeProvider: NoopAIProvider | OpenAICompatibleProvider | null = null;\r\n\r\n// ─── 自动注册内置 Provider ──────────────────────────────────────\r\n\r\nconst BUILTIN_PROVIDERS: Array<{ id: string; ProviderClass: AIProviderConstructor }> = [\r\n { id: 'openai', ProviderClass: OpenAICompatibleProvider },\r\n { id: 'deepseek', ProviderClass: OpenAICompatibleProvider },\r\n { id: 'moonshot', ProviderClass: OpenAICompatibleProvider },\r\n { id: 'zhipu', ProviderClass: OpenAICompatibleProvider },\r\n { id: 'qwen', ProviderClass: OpenAICompatibleProvider },\r\n { id: 'ollama', ProviderClass: OpenAICompatibleProvider },\r\n];\r\n\r\nfor (const { id, ProviderClass } of BUILTIN_PROVIDERS) {\r\n providerRegistry.set(id, ProviderClass);\r\n}\r\n\r\n/** 预置 Provider 信息(用于 init 向导展示) */\r\nexport const AI_PRESET_PROVIDERS: AIPresetProvider[] = [\r\n { id: 'openai', name: 'OpenAI (GPT-4o)', baseUrl: 'https://api.openai.com/v1', defaultModel: 'gpt-4o-mini', requiresApiKey: true },\r\n { id: 'deepseek', name: 'DeepSeek', baseUrl: 'https://api.deepseek.com/v1', defaultModel: 'deepseek-chat', requiresApiKey: true },\r\n { id: 'moonshot', name: 'Moonshot (月之暗面)', baseUrl: 'https://api.moonshot.cn/v1', defaultModel: 'moonshot-v1-8k', requiresApiKey: true },\r\n { id: 'zhipu', name: '智谱 (GLM-4)', baseUrl: 'https://open.bigmodel.cn/api/paas/v4', defaultModel: 'glm-4-flash', requiresApiKey: true },\r\n { id: 'qwen', name: '通义千问 (Qwen)', baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', defaultModel: 'qwen-turbo', requiresApiKey: true },\r\n { id: 'ollama', name: 'Ollama (本地)', baseUrl: 'http://localhost:11434/v1', defaultModel: 'qwen2.5-coder:7b', requiresApiKey: false },\r\n];\r\n\r\n/**\r\n * 注册 AI Provider\r\n * @param name Provider 名称\r\n * @param ProviderClass Provider 类\r\n */\r\nexport function registerAIProvider(name: string, ProviderClass: AIProviderConstructor): void {\r\n providerRegistry.set(name, ProviderClass);\r\n}\r\n\r\n/**\r\n * 获取已注册的 Provider 名称列表\r\n */\r\nexport function getRegisteredProviders(): string[] {\r\n return Array.from(providerRegistry.keys());\r\n}\r\n\r\n/**\r\n * 获取 AI Provider 实例\r\n * @param config AI 配置,若未配置则返回 NoopProvider\r\n */\r\nexport function getAIProvider(config?: AIConfig): NoopAIProvider | OpenAICompatibleProvider {\r\n if (activeProvider) {\r\n return activeProvider;\r\n }\r\n\r\n if (!config || !config.provider) {\r\n activeProvider = new NoopAIProvider();\r\n return activeProvider;\r\n }\r\n\r\n const ProviderClass = providerRegistry.get(config.provider);\r\n if (!ProviderClass) {\r\n console.warn(\r\n `未找到 AI Provider \"${config.provider}\",已注册的 Provider: ${\r\n providerRegistry.size > 0 ? getRegisteredProviders().join(', ') : '无'\r\n }。将使用默认 Provider。`,\r\n );\r\n activeProvider = new NoopAIProvider();\r\n return activeProvider;\r\n }\r\n\r\n activeProvider = new ProviderClass(config);\r\n return activeProvider;\r\n}\r\n\r\n/**\r\n * 重置 Provider 实例(用于配置变更后重新初始化)\r\n */\r\nexport function resetAIProvider(): void {\r\n activeProvider = null;\r\n}\r\n\r\n/**\r\n * 检查 AI 功能是否可用\r\n */\r\nexport function isAIAvailable(config?: AIConfig): boolean {\r\n if (!config || !config.provider) return false;\r\n return providerRegistry.has(config.provider);\r\n}\r\n\r\n/**\r\n * 创建独立的 AI Provider 实例(不使用单例缓存)\r\n * 用于需要多个独立上下文的场景,如生成者+审计员双模型\r\n */\r\nexport function createAIProvider(config: AIConfig): NoopAIProvider | OpenAICompatibleProvider {\r\n if (!config || !config.provider) {\r\n return new NoopAIProvider();\r\n }\r\n\r\n const ProviderClass = providerRegistry.get(config.provider);\r\n if (!ProviderClass) {\r\n return new NoopAIProvider();\r\n }\r\n\r\n return new ProviderClass(config);\r\n}\r\n\r\n/**\r\n * 测试 AI 连通性\r\n */\r\nexport async function testAIConnection(config: AIConfig): Promise<{ ok: boolean; message: string; latencyMs?: number }> {\r\n const ProviderClass = providerRegistry.get(config.provider);\r\n if (!ProviderClass) {\r\n return { ok: false, message: `未注册的 Provider: ${config.provider},可选: ${getRegisteredProviders().join(', ')}` };\r\n }\r\n\r\n const provider = new ProviderClass(config);\r\n\r\n if (!(provider instanceof OpenAICompatibleProvider)) {\r\n return { ok: false, message: `${config.provider} 不支持连通性测试` };\r\n }\r\n\r\n return provider.testConnection();\r\n}\r\n\r\n// 重新导出类型\r\nexport type {\r\n AICapability,\r\n AIGenerateTestRequest,\r\n AIGenerateTestResponse,\r\n AIAnalyzeResultRequest,\r\n AIAnalyzeResultResponse,\r\n AIReviewTestRequest,\r\n AIReviewTestResponse,\r\n AIProvider,\r\n AIConfig,\r\n} from '../types/ai.js';\r\n","/**\r\n * 测试用例审计服务 - 生成者 AI 和审计员 AI 双模型审核流程\r\n *\r\n * 流程:\r\n * 1. 生成者 AI 生成测试用例\r\n * 2. 审计员 AI 审查是否贴切准确\r\n * 3. 审计不通过 → 带反馈重新生成,最多 MAX_RETRIES 次\r\n * 4. 最终结果写入测试文件,失败的记录在报告中\r\n *\r\n * 两个模型上下文完全独立:\r\n * - 生成者只接收源码和分析结果\r\n * - 审计员只接收源码 + 生成的测试代码,不接收生成者的内部状态\r\n */\r\n\r\nimport chalk from 'chalk';\r\nimport ora from 'ora';\r\nimport type { AIConfig, TestType } from '../types/index.js';\r\nimport type {\r\n AIGenerateTestResponse,\r\n AIReviewTestRequest,\r\n AIReviewTestResponse,\r\n SourceAnalysisSummary,\r\n} from '../types/ai.js';\r\nimport { createAIProvider } from '../ai/provider.js';\r\n\r\n/** 最大重试次数(避免死循环) */\r\nconst MAX_RETRIES = 3;\r\n\r\n/** 审计通过阈值 */\r\nconst REVIEW_THRESHOLD = 0.6;\r\n\r\n/** 审计结果 */\r\nexport interface ReviewResult {\r\n /** 最终测试代码 */\r\n code: string;\r\n /** 最终描述 */\r\n description: string;\r\n /** 置信度 */\r\n confidence: number;\r\n /** 是否通过审计 */\r\n approved: boolean;\r\n /** 尝试次数 */\r\n attempts: number;\r\n /** 审计评分 */\r\n reviewScore: number;\r\n /** 审计反馈 */\r\n reviewFeedback: string;\r\n /** 审计发现的问题 */\r\n reviewIssues: string[];\r\n /** 审计改进建议 */\r\n reviewSuggestions: string[];\r\n}\r\n\r\n/** 审计报告条目 */\r\nexport interface ReviewReportEntry {\r\n /** 被测文件 */\r\n target: string;\r\n /** 测试类型 */\r\n testType: TestType;\r\n /** 是否通过 */\r\n approved: boolean;\r\n /** 尝试次数 */\r\n attempts: number;\r\n /** 评分 */\r\n score: number;\r\n /** 失败原因/反馈 */\r\n feedback: string;\r\n /** 问题列表 */\r\n issues: string[];\r\n}\r\n\r\n/**\r\n * 带审计的测试生成流程\r\n *\r\n * 生成者和审计员使用同一个 AI 配置但独立的 Provider 实例,\r\n * 确保上下文不会互相污染。\r\n */\r\nexport async function generateWithReview(params: {\r\n testType: TestType;\r\n targetPath: string;\r\n sourceCode: string;\r\n analysis?: SourceAnalysisSummary;\r\n aiConfig: AIConfig;\r\n framework?: string;\r\n onAttempt?: (attempt: number, maxRetries: number) => void;\r\n}): Promise<ReviewResult> {\r\n const { testType, targetPath, sourceCode, analysis, aiConfig, framework, onAttempt } = params;\r\n\r\n // 创建两个独立的 Provider 实例,确保上下文隔离\r\n // 生成者和审计员使用同一个 AI 配置,但各自持有独立的实例\r\n const generatorProvider = createAIProvider(aiConfig);\r\n const reviewerProvider = createAIProvider(aiConfig);\r\n\r\n if (!generatorProvider.capabilities.generateTest) {\r\n throw new Error('当前 AI Provider 不支持测试生成');\r\n }\r\n\r\n let currentCode = '';\r\n let currentDescription = '';\r\n let currentConfidence = 0;\r\n let lastReview: AIReviewTestResponse | null = null;\r\n let approved = false;\r\n let attempts = 0;\r\n\r\n for (let i = 0; i < MAX_RETRIES; i++) {\r\n attempts = i + 1;\r\n onAttempt?.(attempts, MAX_RETRIES);\r\n\r\n // ─── Step 1: 生成者 AI 生成测试 ───\r\n const generationPrompt = i === 0\r\n ? undefined // 首次生成,无需额外提示\r\n : `上次生成的测试未通过审计,请根据以下反馈重新生成:\r\n审计评分: ${(lastReview!.score * 100).toFixed(0)}%\r\n审计意见: ${lastReview!.feedback}\r\n具体问题:\r\n${lastReview!.issues.map((issue) => `- ${issue}`).join('\\n')}\r\n改进建议:\r\n${lastReview!.suggestions.map((s) => `- ${s}`).join('\\n')}\r\n\r\n请针对以上问题重新生成更贴切、更准确的测试用例。`;\r\n\r\n const generateResponse: AIGenerateTestResponse = await generatorProvider.generateTest({\r\n type: testType,\r\n target: targetPath,\r\n context: i === 0 ? sourceCode : `${sourceCode}\\n\\n--- 审计反馈 ---\\n${generationPrompt}`,\r\n framework: (framework as 'vue') || undefined,\r\n analysis,\r\n });\r\n\r\n currentCode = generateResponse.code;\r\n currentDescription = generateResponse.description;\r\n currentConfidence = generateResponse.confidence;\r\n\r\n // ─── Step 2: 审计员 AI 审查 ───\r\n const reviewRequest: AIReviewTestRequest = {\r\n target: targetPath,\r\n sourceCode,\r\n analysis,\r\n testCode: currentCode,\r\n testType,\r\n generationDescription: currentDescription,\r\n };\r\n\r\n lastReview = await reviewerProvider.reviewTest(reviewRequest);\r\n approved = lastReview.approved && lastReview.score >= REVIEW_THRESHOLD;\r\n\r\n if (approved) {\r\n break;\r\n }\r\n }\r\n\r\n return {\r\n code: currentCode,\r\n description: currentDescription,\r\n confidence: currentConfidence,\r\n approved,\r\n attempts,\r\n reviewScore: lastReview?.score ?? 0,\r\n reviewFeedback: lastReview?.feedback ?? '',\r\n reviewIssues: lastReview?.issues ?? [],\r\n reviewSuggestions: lastReview?.suggestions ?? [],\r\n };\r\n}\r\n\r\n/**\r\n * 批量生成并审计测试用例\r\n */\r\nexport async function batchGenerateWithReview(\r\n targets: Array<{\r\n testType: TestType;\r\n targetPath: string;\r\n sourceCode: string;\r\n analysis?: SourceAnalysisSummary;\r\n }>,\r\n aiConfig: AIConfig,\r\n framework?: string,\r\n): Promise<{\r\n results: Map<string, ReviewResult>;\r\n report: ReviewReportEntry[];\r\n}> {\r\n const results = new Map<string, ReviewResult>();\r\n const report: ReviewReportEntry[] = [];\r\n\r\n const spinner = ora(`正在生成并审计测试用例 (0/${targets.length})...`).start();\r\n let completed = 0;\r\n\r\n for (const target of targets) {\r\n completed++;\r\n spinner.text = `正在生成并审计测试用例 (${completed}/${targets.length}) — ${target.targetPath}`;\r\n\r\n try {\r\n const result = await generateWithReview({\r\n testType: target.testType,\r\n targetPath: target.targetPath,\r\n sourceCode: target.sourceCode,\r\n analysis: target.analysis,\r\n aiConfig,\r\n framework,\r\n });\r\n\r\n results.set(target.targetPath, result);\r\n\r\n report.push({\r\n target: target.targetPath,\r\n testType: target.testType,\r\n approved: result.approved,\r\n attempts: result.attempts,\r\n score: result.reviewScore,\r\n feedback: result.reviewFeedback,\r\n issues: result.reviewIssues,\r\n });\r\n } catch (error) {\r\n report.push({\r\n target: target.targetPath,\r\n testType: target.testType,\r\n approved: false,\r\n attempts: 0,\r\n score: 0,\r\n feedback: error instanceof Error ? error.message : String(error),\r\n issues: [],\r\n });\r\n }\r\n }\r\n\r\n const approvedCount = report.filter((r) => r.approved).length;\r\n const failedCount = report.length - approvedCount;\r\n\r\n if (failedCount > 0) {\r\n spinner.warn(`已完成 ${report.length} 个测试用例审计 (${approvedCount} 通过, ${failedCount} 未通过)`);\r\n } else {\r\n spinner.succeed(`已完成 ${report.length} 个测试用例审计 (全部通过)`);\r\n }\r\n\r\n return { results, report };\r\n}\r\n\r\n/**\r\n * 打印审计报告\r\n */\r\nexport function printReviewReport(report: ReviewReportEntry[]): void {\r\n if (report.length === 0) return;\r\n\r\n const approved = report.filter((r) => r.approved);\r\n const failed = report.filter((r) => !r.approved);\r\n\r\n console.log();\r\n console.log(chalk.cyan(' 审计报告:'));\r\n console.log(chalk.gray(' ─────────────────────────────'));\r\n\r\n for (const entry of approved) {\r\n console.log(` ${chalk.green('✓')} ${chalk.gray(entry.target)} ${chalk.green(`(${(entry.score * 100).toFixed(0)}%`)}${entry.attempts > 1 ? chalk.yellow(` ${entry.attempts}次尝试`) : ''})`);\r\n }\r\n\r\n for (const entry of failed) {\r\n console.log(` ${chalk.red('✗')} ${chalk.gray(entry.target)} ${chalk.red(`(${(entry.score * 100).toFixed(0)}%)`)}`);\r\n if (entry.issues.length > 0) {\r\n for (const issue of entry.issues.slice(0, 3)) {\r\n console.log(chalk.gray(` - ${issue}`));\r\n }\r\n }\r\n if (entry.feedback) {\r\n console.log(chalk.gray(` 反馈: ${entry.feedback}`));\r\n }\r\n }\r\n\r\n console.log();\r\n}\r\n","/**\r\n * 源码分析器 - 扫描 TS/JS/Vue 文件,提取导出、Props、Events、API调用 等信息\r\n * 用于生成个性化的测试用例和 Mock 路由\r\n */\r\n\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\n\r\n// ─── 分析结果类型 ────────────────────────────────────────────\r\n\r\n/** 导出项类型 */\r\nexport type ExportKind = 'function' | 'class' | 'const' | 'type' | 'default' | 'component';\r\n\r\n/** 导出项信息 */\r\nexport interface ExportInfo {\r\n /** 导出名称 */\r\n name: string;\r\n /** 导出类型 */\r\n kind: ExportKind;\r\n /** 函数参数列表 */\r\n params: string[];\r\n /** 函数是否有返回值类型注解 */\r\n returnType?: string;\r\n /** 是否为异步函数 */\r\n isAsync: boolean;\r\n}\r\n\r\n/** Vue 组件 Props 信息 */\r\nexport interface PropInfo {\r\n /** prop 名称 */\r\n name: string;\r\n /** prop 类型(如 String, Number, Boolean, Array, Object) */\r\n type: string;\r\n /** 是否必填 */\r\n required: boolean;\r\n /** 默认值 */\r\n defaultValue?: string;\r\n}\r\n\r\n/** Vue 组件 Emits 信息 */\r\nexport interface EmitInfo {\r\n /** 事件名称 */\r\n name: string;\r\n /** 事件参数 */\r\n params: string[];\r\n}\r\n\r\n/** Vue 组件分析结果 */\r\nexport interface VueComponentAnalysis {\r\n /** 组件名称 */\r\n componentName: string;\r\n /** Props 列表 */\r\n props: PropInfo[];\r\n /** Emits 列表 */\r\n emits: EmitInfo[];\r\n /** 组件中定义的方法名 */\r\n methods: string[];\r\n /** 组件中定义的 computed 名 */\r\n computed: string[];\r\n /** 是否使用 setup 语法 */\r\n usesSetup: boolean;\r\n}\r\n\r\n/** API 调用信息 */\r\nexport interface APICallInfo {\r\n /** HTTP 方法 */\r\n method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\r\n /** 请求路径 */\r\n url: string;\r\n /** 来源文件 */\r\n sourceFile: string;\r\n}\r\n\r\n/** TS/JS 文件分析结果 */\r\nexport interface ModuleAnalysis {\r\n /** 文件路径 */\r\n filePath: string;\r\n /** 导出项列表 */\r\n exports: ExportInfo[];\r\n /** Vue 组件分析(仅 .vue 文件) */\r\n vueAnalysis?: VueComponentAnalysis;\r\n /** 发现的 API 调用 */\r\n apiCalls: APICallInfo[];\r\n}\r\n\r\n// ─── 主分析函数 ──────────────────────────────────────────────\r\n\r\n/**\r\n * 分析一个源码文件\r\n */\r\nexport function analyzeFile(filePath: string): ModuleAnalysis {\r\n const absolutePath = path.resolve(process.cwd(), filePath);\r\n\r\n if (!fs.existsSync(absolutePath)) {\r\n return { filePath, exports: [], apiCalls: [] };\r\n }\r\n\r\n const content = fs.readFileSync(absolutePath, 'utf-8');\r\n const ext = path.extname(filePath);\r\n\r\n const result: ModuleAnalysis = {\r\n filePath,\r\n exports: extractExports(content, ext),\r\n apiCalls: extractAPICalls(content, filePath),\r\n };\r\n\r\n // Vue 组件分析\r\n if (ext === '.vue') {\r\n result.vueAnalysis = analyzeVueComponent(content, path.basename(filePath, '.vue'));\r\n // Vue 文件的 <script> 部分也提取 API 调用\r\n const scriptMatch = content.match(/<script[^>]*>([\\s\\S]*?)<\\/script>/);\r\n if (scriptMatch) {\r\n result.apiCalls = extractAPICalls(scriptMatch[1], filePath);\r\n }\r\n }\r\n\r\n return result;\r\n}\r\n\r\n/**\r\n * 批量分析文件\r\n */\r\nexport function analyzeFiles(filePaths: string[]): ModuleAnalysis[] {\r\n return filePaths.map(analyzeFile);\r\n}\r\n\r\n// ─── 导出提取 ────────────────────────────────────────────────\r\n\r\n/**\r\n * 从源码中提取导出项\r\n */\r\nfunction extractExports(content: string, ext: string): ExportInfo[] {\r\n const exports: ExportInfo[] = [];\r\n\r\n // 命名导出: export function foo() / export const bar / export class Baz\r\n const namedExportRegex = /export\\s+(async\\s+)?function\\s+(\\w+)\\s*\\(([^)]*)\\)(\\s*:\\s*([^{]+))?/g;\r\n let match: RegExpExecArray | null;\r\n\r\n while ((match = namedExportRegex.exec(content)) !== null) {\r\n exports.push({\r\n name: match[2],\r\n kind: 'function',\r\n params: parseParams(match[3]),\r\n returnType: match[5]?.trim(),\r\n isAsync: !!match[1],\r\n });\r\n }\r\n\r\n // export const/let/var\r\n const constExportRegex = /export\\s+(const|let|var)\\s+(\\w+)\\s*(?::\\s*([^{=]+))?\\s*[={]/g;\r\n while ((match = constExportRegex.exec(content)) !== null) {\r\n const typeAnnotation = match[3]?.trim();\r\n const kind: ExportKind = typeAnnotation === 'DefineComponent' || isComponentType(typeAnnotation)\r\n ? 'component'\r\n : 'const';\r\n\r\n exports.push({\r\n name: match[2],\r\n kind,\r\n params: [],\r\n returnType: typeAnnotation,\r\n isAsync: false,\r\n });\r\n }\r\n\r\n // export class\r\n const classExportRegex = /export\\s+class\\s+(\\w+)/g;\r\n while ((match = classExportRegex.exec(content)) !== null) {\r\n exports.push({\r\n name: match[1],\r\n kind: 'class',\r\n params: [],\r\n isAsync: false,\r\n });\r\n }\r\n\r\n // export type/interface\r\n const typeExportRegex = /export\\s+(type|interface)\\s+(\\w+)/g;\r\n while ((match = typeExportRegex.exec(content)) !== null) {\r\n exports.push({\r\n name: match[2],\r\n kind: 'type',\r\n params: [],\r\n isAsync: false,\r\n });\r\n }\r\n\r\n // export default\r\n if (/export\\s+default\\s+/.test(content)) {\r\n // export default function\r\n const defaultFuncMatch = /export\\s+default\\s+(async\\s+)?function\\s+(\\w*)\\s*\\(([^)]*)\\)/.exec(content);\r\n if (defaultFuncMatch) {\r\n exports.push({\r\n name: defaultFuncMatch[2] || 'default',\r\n kind: 'default',\r\n params: parseParams(defaultFuncMatch[3]),\r\n isAsync: !!defaultFuncMatch[1],\r\n });\r\n } else {\r\n // export default expression\r\n exports.push({\r\n name: 'default',\r\n kind: ext === '.vue' ? 'component' : 'default',\r\n params: [],\r\n isAsync: false,\r\n });\r\n }\r\n }\r\n\r\n // re-export: export { foo, bar } from './module'\r\n // 这些我们不需要生成测试\r\n\r\n return exports;\r\n}\r\n\r\n/**\r\n * 解析函数参数字符串\r\n */\r\nfunction parseParams(paramsStr: string): string[] {\r\n if (!paramsStr.trim()) return [];\r\n\r\n return paramsStr\r\n .split(',')\r\n .map((p) => {\r\n // 提取参数名(去掉类型注解和默认值)\r\n const trimmed = p.trim();\r\n const nameMatch = trimmed.match(/(?:\\.\\.\\.)?(\\w+)/);\r\n return nameMatch ? nameMatch[1] : trimmed;\r\n })\r\n .filter((p) => p && p !== '_' && !p.startsWith('__'));\r\n}\r\n\r\n/**\r\n * 判断是否为组件类型\r\n */\r\nfunction isComponentType(typeStr?: string): boolean {\r\n if (!typeStr) return false;\r\n return /DefineComponent|FunctionalComponent|Component\\b/i.test(typeStr);\r\n}\r\n\r\n// ─── Vue 组件分析 ────────────────────────────────────────────\r\n\r\n/**\r\n * 分析 Vue 组件\r\n */\r\nfunction analyzeVueComponent(content: string, componentName: string): VueComponentAnalysis {\r\n const analysis: VueComponentAnalysis = {\r\n componentName,\r\n props: [],\r\n emits: [],\r\n methods: [],\r\n computed: [],\r\n usesSetup: false,\r\n };\r\n\r\n // 检测是否使用 <script setup>\r\n analysis.usesSetup = /<script[^>]*setup[^>]*>/.test(content);\r\n\r\n // 提取 <script> 部分内容\r\n const scriptMatch = content.match(/<script[^>]*>([\\s\\S]*?)<\\/script>/);\r\n if (!scriptMatch) return analysis;\r\n\r\n const scriptContent = scriptMatch[1];\r\n\r\n // 解析 Props\r\n analysis.props = extractProps(scriptContent, analysis.usesSetup);\r\n\r\n // 解析 Emits\r\n analysis.emits = extractEmits(scriptContent, analysis.usesSetup);\r\n\r\n // 解析 Methods(仅 Options API)\r\n if (!analysis.usesSetup) {\r\n analysis.methods = extractMethods(scriptContent);\r\n analysis.computed = extractComputed(scriptContent);\r\n }\r\n\r\n return analysis;\r\n}\r\n\r\n/**\r\n * 提取 Props\r\n */\r\nfunction extractProps(scriptContent: string, usesSetup: boolean): PropInfo[] {\r\n const props: PropInfo[] = [];\r\n\r\n if (usesSetup) {\r\n // defineProps 的对象形式: defineProps({ foo: { type: String, required: true } })\r\n const definePropsObjMatch = scriptContent.match(/defineProps\\s*<[^>]*>\\s*\\(\\s*\\)|defineProps\\s*\\(\\s*\\{([\\s\\S]*?)\\}\\s*\\)/);\r\n if (definePropsObjMatch && definePropsObjMatch[1]) {\r\n const objContent = definePropsObjMatch[1];\r\n // 匹配每个 prop 定义\r\n const propRegex = /(\\w+)\\s*:\\s*\\{([^}]*)\\}/g;\r\n let propMatch: RegExpExecArray | null;\r\n while ((propMatch = propRegex.exec(objContent)) !== null) {\r\n const propBody = propMatch[2];\r\n props.push({\r\n name: propMatch[1],\r\n type: extractPropType(propBody),\r\n required: /required\\s*:\\s*true/.test(propBody),\r\n defaultValue: extractPropDefault(propBody),\r\n });\r\n }\r\n }\r\n\r\n // defineProps 的数组形式: defineProps(['foo', 'bar'])\r\n const definePropsArrMatch = scriptContent.match(/defineProps\\s*\\(\\s*\\[([^\\]]*)\\]\\s*\\)/);\r\n if (definePropsArrMatch && definePropsArrMatch[1]) {\r\n const names = definePropsArrMatch[1].match(/'([^']+)'/g);\r\n if (names) {\r\n for (const name of names) {\r\n const cleanName = name.replace(/'/g, '');\r\n if (!props.some((p) => p.name === cleanName)) {\r\n props.push({\r\n name: cleanName,\r\n type: 'any',\r\n required: false,\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n // defineProps 的类型形式: defineProps<{ foo: string, bar?: number }>()\r\n const typePropsMatch = scriptContent.match(/defineProps\\s*<\\s*\\{([^}]*)\\}/);\r\n if (typePropsMatch && typePropsMatch[1] && props.length === 0) {\r\n const typeContent = typePropsMatch[1];\r\n const typePropRegex = /(\\??\\s*)(\\w+)\\s*:\\s*([^;,\\n]+)/g;\r\n let typeMatch: RegExpExecArray | null;\r\n while ((typeMatch = typePropRegex.exec(typeContent)) !== null) {\r\n props.push({\r\n name: typeMatch[2],\r\n type: typeMatch[3].trim(),\r\n required: !typeMatch[1].includes('?'),\r\n });\r\n }\r\n }\r\n } else {\r\n // Options API: props: { ... }\r\n const propsObjMatch = scriptContent.match(/props\\s*:\\s*\\{([\\s\\S]*?)\\}\\s*,?\\s*(?:emits|data|computed|methods|setup|components|watch|$)/);\r\n if (propsObjMatch && propsObjMatch[1]) {\r\n const propRegex = /(\\w+)\\s*:\\s*\\{([^}]*)\\}/g;\r\n let propMatch: RegExpExecArray | null;\r\n while ((propMatch = propRegex.exec(propsObjMatch[1])) !== null) {\r\n const propBody = propMatch[2];\r\n props.push({\r\n name: propMatch[1],\r\n type: extractPropType(propBody),\r\n required: /required\\s*:\\s*true/.test(propBody),\r\n defaultValue: extractPropDefault(propBody),\r\n });\r\n }\r\n }\r\n\r\n // Array 形式: props: ['foo', 'bar']\r\n const propsArrMatch = scriptContent.match(/props\\s*:\\s*\\[([^\\]]*)\\]/);\r\n if (propsArrMatch && propsArrMatch[1]) {\r\n const names = propsArrMatch[1].match(/'([^']+)'/g);\r\n if (names) {\r\n for (const name of names) {\r\n const cleanName = name.replace(/'/g, '');\r\n if (!props.some((p) => p.name === cleanName)) {\r\n props.push({\r\n name: cleanName,\r\n type: 'any',\r\n required: false,\r\n });\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return props;\r\n}\r\n\r\n/**\r\n * 提取 prop 类型\r\n */\r\nfunction extractPropType(propBody: string): string {\r\n const typeMatch = propBody.match(/type\\s*:\\s*(\\w+)/);\r\n if (typeMatch) return typeMatch[1];\r\n return 'any';\r\n}\r\n\r\n/**\r\n * 提取 prop 默认值\r\n */\r\nfunction extractPropDefault(propBody: string): string | undefined {\r\n const defaultMatch = propBody.match(/default\\s*:\\s*([^,}\\n]+)/);\r\n if (defaultMatch) return defaultMatch[1].trim();\r\n return undefined;\r\n}\r\n\r\n/**\r\n * 提取 Emits\r\n */\r\nfunction extractEmits(scriptContent: string, usesSetup: boolean): EmitInfo[] {\r\n const emits: EmitInfo[] = [];\r\n\r\n if (usesSetup) {\r\n // defineEmits(['foo', 'bar'])\r\n const arrMatch = scriptContent.match(/defineEmits\\s*\\(\\s*\\[([^\\]]*)\\]\\s*\\)/);\r\n if (arrMatch && arrMatch[1]) {\r\n const names = arrMatch[1].match(/'([^']+)'/g);\r\n if (names) {\r\n for (const name of names) {\r\n emits.push({\r\n name: name.replace(/'/g, ''),\r\n params: [],\r\n });\r\n }\r\n }\r\n }\r\n\r\n // defineEmits<{ foo: [...], bar: [...] }>()\r\n const typeMatch = scriptContent.match(/defineEmits\\s*<\\s*\\{([^}]*)\\}/);\r\n if (typeMatch && typeMatch[1] && emits.length === 0) {\r\n const emitRegex = /(\\w+)\\s*:\\s*\\(([^)]*)\\)/g;\r\n let emitMatch: RegExpExecArray | null;\r\n while ((emitMatch = emitRegex.exec(typeMatch[1])) !== null) {\r\n emits.push({\r\n name: emitMatch[1],\r\n params: parseParams(emitMatch[2]),\r\n });\r\n }\r\n }\r\n } else {\r\n // Options API: emits: ['foo', 'bar'] or emits: { ... }\r\n const arrMatch = scriptContent.match(/emits\\s*:\\s*\\[([^\\]]*)\\]/);\r\n if (arrMatch && arrMatch[1]) {\r\n const names = arrMatch[1].match(/'([^']+)'/g);\r\n if (names) {\r\n for (const name of names) {\r\n emits.push({\r\n name: name.replace(/'/g, ''),\r\n params: [],\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n return emits;\r\n}\r\n\r\n/**\r\n * 提取 Options API 方法\r\n */\r\nfunction extractMethods(scriptContent: string): string[] {\r\n const methods: string[] = [];\r\n const methodsMatch = scriptContent.match(/methods\\s*:\\s*\\{([\\s\\S]*?)\\}\\s*,?\\s*(?:computed|watch|components|mounted|created|setup|$)/);\r\n if (methodsMatch && methodsMatch[1]) {\r\n const methodRegex = /(?:async\\s+)?(\\w+)\\s*\\(/g;\r\n let match: RegExpExecArray | null;\r\n while ((match = methodRegex.exec(methodsMatch[1])) !== null) {\r\n const name = match[1];\r\n if (name !== 'constructor' && !methods.includes(name)) {\r\n methods.push(name);\r\n }\r\n }\r\n }\r\n return methods;\r\n}\r\n\r\n/**\r\n * 提取 computed 属性\r\n */\r\nfunction extractComputed(scriptContent: string): string[] {\r\n const computed: string[] = [];\r\n const computedMatch = scriptContent.match(/computed\\s*:\\s*\\{([\\s\\S]*?)\\}\\s*,?\\s*(?:methods|watch|components|mounted|created|setup|$)/);\r\n if (computedMatch && computedMatch[1]) {\r\n const propRegex = /(\\w+)\\s*(?:\\(\\)|\\{)/g;\r\n let match: RegExpExecArray | null;\r\n while ((match = propRegex.exec(computedMatch[1])) !== null) {\r\n const name = match[1];\r\n if (!computed.includes(name)) {\r\n computed.push(name);\r\n }\r\n }\r\n }\r\n return computed;\r\n}\r\n\r\n// ─── API 调用提取 ────────────────────────────────────────────\r\n\r\n/**\r\n * 从源码中提取 API 调用信息\r\n */\r\nfunction extractAPICalls(content: string, sourceFile: string): APICallInfo[] {\r\n const calls: APICallInfo[] = [];\r\n const seen = new Set<string>();\r\n\r\n // 1. fetch('url', { method: 'POST' }) 或 fetch(`url`)\r\n const fetchRegex = /fetch\\s*\\(\\s*[`'\"]([^`'\"]+)[`'\"]/g;\r\n let match: RegExpExecArray | null;\r\n while ((match = fetchRegex.exec(content)) !== null) {\r\n const url = cleanTemplateUrl(match[1]);\r\n // 检查同一行附近是否有 method 指定\r\n const afterFetch = content.slice(match.index, match.index + 300);\r\n const methodMatch = afterFetch.match(/method\\s*:\\s*['\"]?(GET|POST|PUT|DELETE|PATCH)['\"]?/i);\r\n const method = methodMatch ? methodMatch[1].toUpperCase() as APICallInfo['method'] : 'GET';\r\n const key = `${method}:${url}`;\r\n if (!seen.has(key) && url.startsWith('/')) {\r\n seen.add(key);\r\n calls.push({ method, url, sourceFile });\r\n }\r\n }\r\n\r\n // 2. axios.get('url') / axios.post('url') / axios.put / axios.delete / axios.patch\r\n const axiosRegex = /axios\\s*\\.\\s*(get|post|put|delete|patch)\\s*\\(\\s*[`'\"]([^`'\"]+)[`'\"]/gi;\r\n while ((match = axiosRegex.exec(content)) !== null) {\r\n const method = match[1].toUpperCase() as APICallInfo['method'];\r\n const url = cleanTemplateUrl(match[2]);\r\n const key = `${method}:${url}`;\r\n if (!seen.has(key) && url.startsWith('/')) {\r\n seen.add(key);\r\n calls.push({ method, url, sourceFile });\r\n }\r\n }\r\n\r\n // 3. useFetch('url') / useFetch('url', { method: 'POST' }) (Nuxt/composable)\r\n const useFetchRegex = /useFetch\\s*\\(\\s*[`'\"]([^`'\"]+)[`'\"]/g;\r\n while ((match = useFetchRegex.exec(content)) !== null) {\r\n const url = cleanTemplateUrl(match[1]);\r\n const afterUseFetch = content.slice(match.index, match.index + 300);\r\n const methodMatch = afterUseFetch.match(/method\\s*:\\s*['\"]?(GET|POST|PUT|DELETE|PATCH)['\"]?/i);\r\n const method = methodMatch ? methodMatch[1].toUpperCase() as APICallInfo['method'] : 'GET';\r\n const key = `${method}:${url}`;\r\n if (!seen.has(key) && url.startsWith('/')) {\r\n seen.add(key);\r\n calls.push({ method, url, sourceFile });\r\n }\r\n }\r\n\r\n // 4. $fetch('url', { method: 'POST' }) (Nuxt)\r\n const dollarFetchRegex = /\\$fetch\\s*\\(\\s*[`'\"]([^`'\"]+)[`'\"]/g;\r\n while ((match = dollarFetchRegex.exec(content)) !== null) {\r\n const url = cleanTemplateUrl(match[1]);\r\n const afterFetch = content.slice(match.index, match.index + 300);\r\n const methodMatch = afterFetch.match(/method\\s*:\\s*['\"]?(GET|POST|PUT|DELETE|PATCH)['\"]?/i);\r\n const method = methodMatch ? methodMatch[1].toUpperCase() as APICallInfo['method'] : 'GET';\r\n const key = `${method}:${url}`;\r\n if (!seen.has(key) && url.startsWith('/')) {\r\n seen.add(key);\r\n calls.push({ method, url, sourceFile });\r\n }\r\n }\r\n\r\n // 5. httpRequest.get('url') / http.post('url') 等自定义实例\r\n const httpRegex = /\\w+\\s*\\.\\s*(get|post|put|delete|patch)\\s*\\(\\s*[`'\"]([^`'\"]+)[`'\"]/gi;\r\n while ((match = httpRegex.exec(content)) !== null) {\r\n // 排除 axios 已匹配的\r\n const fullLine = content.slice(Math.max(0, match.index - 20), match.index + match[0].length);\r\n if (/axios/i.test(fullLine)) continue;\r\n const method = match[1].toUpperCase() as APICallInfo['method'];\r\n const url = cleanTemplateUrl(match[2]);\r\n const key = `${method}:${url}`;\r\n if (!seen.has(key) && url.startsWith('/')) {\r\n seen.add(key);\r\n calls.push({ method, url, sourceFile });\r\n }\r\n }\r\n\r\n return calls;\r\n}\r\n\r\n/**\r\n * 清理模板 URL 中的变量插值\r\n * `/api/users/${id}` → `/api/users/:id`\r\n */\r\nfunction cleanTemplateUrl(url: string): string {\r\n return url\r\n .replace(/\\$\\{[^}]*\\}/g, ':param')\r\n .replace(/\\/+/g, '/')\r\n .replace(/:param/g, (_match, offset, str) => {\r\n // 尝试从前面的路径推断参数名\r\n const before = str.slice(Math.max(0, offset - 20), offset);\r\n const nameMatch = before.match(/(\\w+)Id|by(\\w+)|(\\w+)Id/i);\r\n if (nameMatch) {\r\n const name = (nameMatch[1] || nameMatch[2] || nameMatch[3] || 'param').toLowerCase();\r\n return `:${name}`;\r\n }\r\n return ':id';\r\n });\r\n}\r\n\r\n// ─── 扫描目录提取 API ────────────────────────────────────────\r\n\r\n/**\r\n * 扫描项目源码目录,提取所有 API 调用\r\n */\r\nexport function scanAPICalls(srcDir: string): APICallInfo[] {\r\n const absDir = path.resolve(process.cwd(), srcDir);\r\n\r\n if (!fs.existsSync(absDir)) {\r\n return [];\r\n }\r\n\r\n const allCalls: APICallInfo[] = [];\r\n const seen = new Set<string>();\r\n\r\n function walk(dir: string): void {\r\n const entries = fs.readdirSync(dir, { withFileTypes: true });\r\n for (const entry of entries) {\r\n // 跳过 node_modules, dist, .git 等\r\n if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'dist') continue;\r\n\r\n const fullPath = path.join(dir, entry.name);\r\n if (entry.isDirectory()) {\r\n walk(fullPath);\r\n } else if (entry.isFile() && /\\.(ts|js|vue|mjs)$/.test(entry.name)) {\r\n try {\r\n const content = fs.readFileSync(fullPath, 'utf-8');\r\n const calls = extractAPICalls(content, path.relative(process.cwd(), fullPath));\r\n for (const call of calls) {\r\n const key = `${call.method}:${call.url}`;\r\n if (!seen.has(key)) {\r\n seen.add(key);\r\n allCalls.push(call);\r\n }\r\n }\r\n } catch {\r\n // 忽略读取失败\r\n }\r\n }\r\n }\r\n }\r\n\r\n walk(absDir);\r\n return allCalls;\r\n}\r\n\r\n/**\r\n * 根据 API 调用信息自动生成 Mock 路由配置\r\n */\r\nexport function generateMockRoutesFromAPICalls(apiCalls: APICallInfo[]): MockRouteCandidate[] {\r\n const routes: MockRouteCandidate[] = [];\r\n\r\n for (const call of apiCalls) {\r\n // 从 URL 路径推断资源名\r\n const segments = call.url.split('/').filter(Boolean);\r\n const resourceName = segments[segments.length - 1]?.replace(/^:/, '') || 'resource';\r\n\r\n let response: Record<string, unknown> | null;\r\n let status = 200;\r\n\r\n switch (call.method) {\r\n case 'GET':\r\n if (call.url.includes(':')) {\r\n // 单个资源\r\n response = { message: 'Success', data: { id: 1, name: `${resourceName}-item` } };\r\n } else {\r\n // 列表\r\n response = { message: 'Success', data: [{ id: 1, name: `${resourceName}-1` }], total: 1 };\r\n }\r\n break;\r\n case 'POST':\r\n status = 201;\r\n response = { message: 'Created', data: { id: 2, name: `{{body.name}}` } };\r\n break;\r\n case 'PUT':\r\n response = { message: 'Updated', data: { id: 1, name: `{{body.name}}` } };\r\n break;\r\n case 'DELETE':\r\n status = 204;\r\n response = null;\r\n break;\r\n case 'PATCH':\r\n response = { message: 'Updated', data: { id: 1, name: `{{body.name}}` } };\r\n break;\r\n default:\r\n response = { message: 'Success' };\r\n }\r\n\r\n routes.push({\r\n method: call.method,\r\n path: call.url,\r\n status,\r\n response,\r\n delay: 100,\r\n sourceFile: call.sourceFile,\r\n });\r\n }\r\n\r\n return routes;\r\n}\r\n\r\n/** 自动生成的 Mock 路由候选项 */\r\nexport interface MockRouteCandidate {\r\n method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\r\n path: string;\r\n status?: number;\r\n response?: unknown;\r\n delay?: number;\r\n /** 来源文件 */\r\n sourceFile: string;\r\n}\r\n","/**\r\n * 模板引擎 - Handlebars渲染测试用例模板,支持自定义模板注册和Vue版本适配\r\n */\r\n\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport { fileURLToPath } from 'node:url';\r\nimport Handlebars from 'handlebars';\r\nimport type { TestType, FrameworkType, UILibrary } from '../types/index.js';\r\n\r\nconst __filename = fileURLToPath(import.meta.url);\r\nconst __dirname = path.dirname(__filename);\r\n\r\n/** 导出项信息(简化版,用于模板上下文) */\r\nexport interface TemplateExportInfo {\r\n name: string;\r\n kind: string;\r\n params: string[];\r\n isAsync: boolean;\r\n returnType?: string;\r\n}\r\n\r\n/** Props 信息(简化版,用于模板上下文) */\r\nexport interface TemplatePropInfo {\r\n name: string;\r\n type: string;\r\n required: boolean;\r\n defaultValue?: string;\r\n}\r\n\r\n/** Emits 信息(简化版,用于模板上下文) */\r\nexport interface TemplateEmitInfo {\r\n name: string;\r\n params: string[];\r\n}\r\n\r\n/** 模板变量 */\r\nexport interface TemplateContext {\r\n /** 测试用例名称 */\r\n name: string;\r\n /** camelCase 格式名称(用于 import 变量名) */\r\n camelName?: string;\r\n /** PascalCase 格式名称(用于组件名) */\r\n pascalName?: string;\r\n /** 被测目标文件路径 */\r\n target: string;\r\n /** 前端框架 */\r\n framework: FrameworkType;\r\n /** Vue 主版本 */\r\n vueVersion?: 2 | 3;\r\n /** 是否使用 TypeScript */\r\n typescript?: boolean;\r\n /** UI 组件库 */\r\n uiLibrary?: UILibrary;\r\n /** 额外的导入语句 */\r\n imports?: string[];\r\n /** 组件测试需要的额外导入 */\r\n extraImports?: string[];\r\n /** 组件测试全局插件 */\r\n globalPlugins?: string[];\r\n /** 组件测试全局 stubs */\r\n globalStubs?: string[];\r\n /** mount 配置选项代码片段 */\r\n mountOptions?: string;\r\n /** ── 源码分析结果 ── */\r\n /** 是否有源码分析数据 */\r\n hasAnalysis?: boolean;\r\n /** 导出项列表 */\r\n exports?: TemplateExportInfo[];\r\n /** 函数类型导出 */\r\n functionExports?: TemplateExportInfo[];\r\n /** 非函数类型导出(const/class/component) */\r\n valueExports?: TemplateExportInfo[];\r\n /** Vue 组件 Props */\r\n props?: TemplatePropInfo[];\r\n /** Vue 组件 Emits */\r\n emits?: TemplateEmitInfo[];\r\n /** Vue 组件方法(Options API) */\r\n methods?: string[];\r\n /** Vue 组件 computed(Options API) */\r\n computed?: string[];\r\n /** 是否为 Vue 组件 */\r\n isVueComponent?: boolean;\r\n /** 必填 props */\r\n requiredProps?: TemplatePropInfo[];\r\n /** 有默认值的 props */\r\n optionalProps?: TemplatePropInfo[];\r\n /** 自定义变量 */\r\n [key: string]: unknown;\r\n}\r\n\r\n/** 模板类型到文件名映射 */\r\nconst TEMPLATE_MAP: Record<TestType, string> = {\r\n unit: 'unit-test.hbs',\r\n component: 'component-test.hbs',\r\n e2e: 'e2e-test.hbs',\r\n api: 'api-test.hbs',\r\n visual: 'visual-test.hbs',\r\n performance: 'performance-test.hbs',\r\n};\r\n\r\n/** 自定义模板注册表 */\r\nconst customTemplates = new Map<string, string>();\r\n\r\n// 注册 Handlebars 辅助函数\r\nHandlebars.registerHelper('eq', (a: unknown, b: unknown) => a === b);\r\nHandlebars.registerHelper('neq', (a: unknown, b: unknown) => a !== b);\r\nHandlebars.registerHelper('includes', (arr: unknown[], val: unknown) => Array.isArray(arr) && arr.includes(val));\r\nHandlebars.registerHelper('join', (arr: unknown[], sep: string) => Array.isArray(arr) ? arr.join(sep) : '');\r\nHandlebars.registerHelper('camelCase', (str: string) => toCamelCase(str));\r\nHandlebars.registerHelper('pascalCase', (str: string) => toPascalCase(str));\r\nHandlebars.registerHelper('length', (arr: unknown) => Array.isArray(arr) ? arr.length : 0);\r\nHandlebars.registerHelper('gt', (a: unknown, b: unknown) => Number(a) > Number(b));\r\nHandlebars.registerHelper('and', (...args: unknown[]) => {\r\n args.pop(); // Handlebars options\r\n return args.every(Boolean);\r\n});\r\nHandlebars.registerHelper('or', (...args: unknown[]) => {\r\n args.pop(); // Handlebars options\r\n return args.some(Boolean);\r\n});\r\nHandlebars.registerHelper('propDefaultValue', (prop: TemplatePropInfo) => {\r\n if (!prop.defaultValue) return 'undefined';\r\n const map: Record<string, string> = { String: \"''\", Number: '0', Boolean: 'false', Array: '[]', Object: '{}' };\r\n return map[prop.type] || prop.defaultValue;\r\n});\r\nHandlebars.registerHelper('propTestValue', (prop: TemplatePropInfo) => {\r\n const map: Record<string, string> = {\r\n String: \"'test-value'\",\r\n Number: '42',\r\n Boolean: 'true',\r\n Array: '[1, 2, 3]',\r\n Object: '{ key: \"value\" }',\r\n };\r\n return map[prop.type] || \"'test-value'\";\r\n});\r\n\r\n/**\r\n * 注册自定义模板\r\n * @param type 测试类型\r\n * @param templateContent 模板内容(Handlebars 格式)\r\n */\r\nexport function registerTemplate(type: string, templateContent: string): void {\r\n customTemplates.set(type, templateContent);\r\n}\r\n\r\n/**\r\n * 渲染测试用例模板\r\n */\r\nexport function renderTemplate(type: TestType, context: TemplateContext): string {\r\n const templateContent = loadTemplate(type);\r\n const template = Handlebars.compile(templateContent);\r\n\r\n // 设置默认值(context 中的值优先)\r\n const fullContext: TemplateContext = {\r\n vueVersion: 3,\r\n typescript: true,\r\n imports: [],\r\n extraImports: [],\r\n globalPlugins: [],\r\n globalStubs: [],\r\n mountOptions: '',\r\n hasAnalysis: false,\r\n exports: [],\r\n functionExports: [],\r\n valueExports: [],\r\n props: [],\r\n emits: [],\r\n methods: [],\r\n computed: [],\r\n isVueComponent: false,\r\n requiredProps: [],\r\n optionalProps: [],\r\n ...context,\r\n framework: context.framework || 'vue',\r\n camelName: context.camelName || toCamelCase(context.name),\r\n pascalName: context.pascalName || toPascalCase(context.name),\r\n };\r\n\r\n // 如果有分析数据,派生辅助字段\r\n if (fullContext.exports && fullContext.exports.length > 0) {\r\n fullContext.hasAnalysis = true;\r\n fullContext.functionExports = fullContext.exports.filter(\r\n (e) => e.kind === 'function' || e.kind === 'default',\r\n );\r\n fullContext.valueExports = fullContext.exports.filter(\r\n (e) => e.kind !== 'function' && e.kind !== 'default' && e.kind !== 'type',\r\n );\r\n }\r\n\r\n if (fullContext.props && fullContext.props.length > 0) {\r\n fullContext.isVueComponent = true;\r\n fullContext.requiredProps = fullContext.props.filter((p) => p.required);\r\n fullContext.optionalProps = fullContext.props.filter((p) => !p.required);\r\n }\r\n\r\n if (fullContext.emits && fullContext.emits.length > 0) {\r\n fullContext.isVueComponent = true;\r\n }\r\n\r\n return template(fullContext);\r\n}\r\n\r\n/**\r\n * 加载模板文件\r\n */\r\nfunction loadTemplate(type: TestType): string {\r\n // 优先使用自定义注册的模板\r\n const custom = customTemplates.get(type);\r\n if (custom) return custom;\r\n\r\n // 查找文件系统中的模板\r\n const templateDir = getTemplateDir();\r\n const templateFile = TEMPLATE_MAP[type];\r\n const templatePath = path.join(templateDir, templateFile);\r\n\r\n if (fs.existsSync(templatePath)) {\r\n return fs.readFileSync(templatePath, 'utf-8');\r\n }\r\n\r\n // 使用内置模板\r\n return getBuiltinTemplate(type);\r\n}\r\n\r\n/**\r\n * 获取模板目录\r\n */\r\nfunction getTemplateDir(): string {\r\n // 优先查找项目目录下的 templates\r\n const projectTemplates = path.join(process.cwd(), 'templates');\r\n if (fs.existsSync(projectTemplates)) {\r\n return projectTemplates;\r\n }\r\n // 使用内置模板目录\r\n return path.join(__dirname, '..', 'templates');\r\n}\r\n\r\n/**\r\n * 内置模板 - 适配 Vue 2/3 和 TypeScript\r\n */\r\nfunction getBuiltinTemplate(type: TestType): string {\r\n const templates: Record<TestType, string> = {\r\n unit: `import { describe, it, expect } from 'vitest';\r\n{{#if hasAnalysis}}\r\n{{#each valueExports}}\r\nimport { {{name}} } from '{{../target}}';\r\n{{/each}}\r\n{{#each functionExports}}\r\nimport { {{name}} } from '{{../target}}';\r\n{{/each}}\r\n{{else}}\r\nimport { {{camelName}} } from '{{target}}';\r\n{{/if}}\r\n\r\ndescribe('{{name}}', () => {\r\n{{#if hasAnalysis}}\r\n {{#each functionExports}}\r\n describe('{{name}}()', () => {\r\n {{#if isAsync}}\r\n it('should resolve successfully', async () => {\r\n const result = await {{name}}({{#each params}}{{#unless @first}}, {{/unless}}{{this}}: undefined{{/each}});\r\n expect(result).toBeDefined();\r\n });\r\n {{else}}\r\n it('should return a value', () => {\r\n const result = {{name}}({{#each params}}{{#unless @first}}, {{/unless}}undefined as any{{/each}});\r\n expect(result).toBeDefined();\r\n });\r\n {{/if}}\r\n {{#if returnType}}\r\n it('should return correct type', () => {\r\n {{#if isAsync}}\r\n const result = await {{name}}({{#each params}}{{#unless @first}}, {{/unless}}undefined as any{{/each}});\r\n {{else}}\r\n const result = {{name}}({{#each params}}{{#unless @first}}, {{/unless}}undefined as any{{/each}});\r\n {{/if}}\r\n expect(result).toBeDefined();\r\n });\r\n {{/if}}\r\n {{#if params.length}}\r\n it('should handle edge cases for parameters', () => {\r\n {{#if isAsync}}\r\n const result = await {{name}}({{#each params}}{{#unless @first}}, {{/unless}}undefined as any{{/each}});\r\n {{else}}\r\n const result = {{name}}({{#each params}}{{#unless @first}}, {{/unless}}undefined as any{{/each}});\r\n {{/if}}\r\n expect(result).toBeDefined();\r\n });\r\n {{/if}}\r\n });\r\n\r\n {{/each}}\r\n {{#each valueExports}}\r\n describe('{{name}}', () => {\r\n it('should be defined', () => {\r\n expect({{name}}).toBeDefined();\r\n });\r\n {{#if eq kind 'const'}}\r\n it('should have expected value', () => {\r\n // Adjust assertion based on actual value\r\n expect({{name}}).toBeDefined();\r\n });\r\n {{/if}}\r\n {{#if eq kind 'class'}}\r\n it('should be instantiable', () => {\r\n const instance = new {{name}}();\r\n expect(instance).toBeInstanceOf({{name}});\r\n });\r\n {{/if}}\r\n });\r\n\r\n {{/each}}\r\n{{else}}\r\n it('should work correctly', () => {\r\n // TODO: add test logic\r\n expect(true).toBe(true);\r\n });\r\n{{/if}}\r\n});\r\n`,\r\n\r\n component: `import { describe, it, expect } from 'vitest';\r\nimport { mount } from '@vue/test-utils';\r\n{{#each extraImports}}\r\n{{this}}\r\n{{/each}}\r\nimport {{pascalName}} from '{{target}}';\r\n\r\ndescribe('{{name}}', () => {\r\n {{#if isVueComponent}}\r\n const mountComponent = (propsData: Record<string, any> = {}) => {\r\n return mount({{pascalName}}, {\r\n {{#if mountOptions}}\r\n ...{{mountOptions}},\r\n {{/if}}\r\n props: propsData,\r\n });\r\n };\r\n\r\n it('renders correctly', () => {\r\n const wrapper = mountComponent({{#if requiredProps}}{\r\n {{#each requiredProps}}\r\n {{name}}: {{propTestValue this}},\r\n {{/each}}\r\n }{{/if}});\r\n expect(wrapper.exists()).toBe(true);\r\n });\r\n\r\n {{#if requiredProps}}\r\n it('renders with required props', () => {\r\n const wrapper = mountComponent({\r\n {{#each requiredProps}}\r\n {{name}}: {{propTestValue this}},\r\n {{/each}}\r\n });\r\n expect(wrapper.exists()).toBe(true);\r\n });\r\n\r\n {{/if}}\r\n {{#if optionalProps}}\r\n it('renders without optional props', () => {\r\n const wrapper = mountComponent({{#if requiredProps}}{\r\n {{#each requiredProps}}\r\n {{name}}: {{propTestValue this}},\r\n {{/each}}\r\n }{{/if}});\r\n expect(wrapper.exists()).toBe(true);\r\n });\r\n\r\n it('applies optional props correctly', () => {\r\n const wrapper = mountComponent({\r\n {{#each requiredProps}}\r\n {{name}}: {{propTestValue this}},\r\n {{/each}}\r\n {{#each optionalProps}}\r\n {{name}}: {{propTestValue this}},\r\n {{/each}}\r\n });\r\n expect(wrapper.exists()).toBe(true);\r\n });\r\n\r\n {{/if}}\r\n {{#if emits}}\r\n {{#each emits}}\r\n it('emits {{name}} event', async () => {\r\n const wrapper = mountComponent({{#if ../requiredProps}}{\r\n {{#each ../requiredProps}}\r\n {{name}}: {{propTestValue this}},\r\n {{/each}}\r\n }{{/if}});\r\n // TODO: trigger action that emits '{{name}}'\r\n // await wrapper.find('button').trigger('click');\r\n // expect(wrapper.emitted('{{name}}')).toBeTruthy();\r\n });\r\n\r\n {{/each}}\r\n {{/if}}\r\n {{#if computed}}\r\n {{#each computed}}\r\n it('computes {{this}} correctly', () => {\r\n const wrapper = mountComponent({{#if ../requiredProps}}{\r\n {{#each ../requiredProps}}\r\n {{name}}: {{propTestValue this}},\r\n {{/each}}\r\n }{{/if}});\r\n // TODO: verify computed property '{{this}}'\r\n // expect(wrapper.vm.{{this}}).toBeDefined();\r\n });\r\n\r\n {{/each}}\r\n {{/if}}\r\n {{#if methods}}\r\n {{#each methods}}\r\n it('calls {{this}} method', async () => {\r\n const wrapper = mountComponent({{#if ../requiredProps}}{\r\n {{#each ../requiredProps}}\r\n {{name}}: {{propTestValue this}},\r\n {{/each}}\r\n }{{/if}});\r\n // TODO: test method '{{this}}'\r\n // await wrapper.vm.{{this}}();\r\n // expect(someCondition).toBe(true);\r\n });\r\n\r\n {{/each}}\r\n {{/if}}\r\n {{else}}\r\n it('renders correctly', () => {\r\n const wrapper = mount({{pascalName}}{{#if mountOptions}}, {{mountOptions}}{{/if}});\r\n expect(wrapper.exists()).toBe(true);\r\n });\r\n\r\n it('renders default slot content', () => {\r\n const wrapper = mount({{pascalName}}, {\r\n {{#if mountOptions}}\r\n ...{{mountOptions}},\r\n {{/if}}\r\n slots: {\r\n default: 'Test Content',\r\n },\r\n });\r\n expect(wrapper.text()).toContain('Test Content');\r\n });\r\n\r\n it('handles user interaction', async () => {\r\n const wrapper = mount({{pascalName}}{{#if mountOptions}}, {{mountOptions}}{{/if}});\r\n // TODO: add interaction test\r\n });\r\n {{/if}}\r\n});\r\n`,\r\n\r\n e2e: `import { test, expect } from '@playwright/test';\r\n\r\ntest.describe('{{name}}', () => {\r\n test.beforeEach(async ({ page }) => {\r\n await page.goto('/');\r\n });\r\n\r\n test('should display page correctly', async ({ page }) => {\r\n // TODO: 添加E2E测试断言\r\n await expect(page).toHaveTitle(/.*/);\r\n });\r\n\r\n test('should navigate between pages', async ({ page }) => {\r\n // TODO: 添加导航测试\r\n });\r\n\r\n test('should be responsive', async ({ page }) => {\r\n await page.setViewportSize({ width: 375, height: 667 });\r\n // TODO: 添加移动端断言\r\n });\r\n});\r\n`,\r\n\r\n api: `import { describe, it, expect, beforeAll, afterAll } from 'vitest';\r\n{{#if imports}}\r\n{{#each imports}}\r\nimport {{this}};\r\n{{/each}}\r\n{{/if}}\r\n\r\ndescribe('{{name}} API', () => {\r\n const baseUrl = process.env.API_BASE_URL || 'http://localhost:3456';\r\n\r\n it('should return 200 for GET request', async () => {\r\n const response = await fetch(\\`\\${baseUrl}/api/{{camelName}}\\`);\r\n expect(response.status).toBe(200);\r\n });\r\n\r\n it('should return expected response format', async () => {\r\n const response = await fetch(\\`\\${baseUrl}/api/{{camelName}}\\`);\r\n const data = await response.json();\r\n expect(data).toBeDefined();\r\n });\r\n\r\n it('should handle error responses', async () => {\r\n const response = await fetch(\\`\\${baseUrl}/api/{{camelName}}/nonexistent\\`, {\r\n method: 'GET',\r\n });\r\n expect(response.status).toBeGreaterThanOrEqual(400);\r\n });\r\n});\r\n`,\r\n\r\n visual: `import { test, expect } from '@playwright/test';\r\n\r\ntest.describe('{{name}} visual regression', () => {\r\n test.beforeEach(async ({ page }) => {\r\n await page.goto('/');\r\n });\r\n\r\n test('should match baseline screenshot - desktop', async ({ page }) => {\r\n await expect(page).toHaveScreenshot('{{name}}-desktop.png');\r\n });\r\n\r\n test('should match baseline screenshot - mobile', async ({ page }) => {\r\n await page.setViewportSize({ width: 375, height: 667 });\r\n await expect(page).toHaveScreenshot('{{name}}-mobile.png');\r\n });\r\n});\r\n`,\r\n\r\n performance: `import { test, expect } from '@playwright/test';\r\n\r\ntest.describe('{{name}} performance', () => {\r\n test('should meet Core Web Vitals thresholds', async ({ page }) => {\r\n await page.goto('/');\r\n\r\n // 测量页面加载性能\r\n const performanceMetrics = await page.evaluate(() => {\r\n const [entry] = performance.getEntriesByType('navigation') as PerformanceNavigationTiming[];\r\n return {\r\n domContentLoaded: entry?.domContentLoadedEventEnd - entry?.domContentLoadedEventStart ?? 0,\r\n loadComplete: entry?.loadEventEnd - entry?.loadEventStart ?? 0,\r\n domInteractive: entry?.domInteractive ?? 0,\r\n };\r\n });\r\n\r\n // DOM 内容加载应在 2s 内\r\n expect(performanceMetrics.domInteractive).toBeLessThan(2000);\r\n });\r\n\r\n test('should not have memory leaks', async ({ page }) => {\r\n await page.goto('/');\r\n\r\n const metrics = await page.metrics();\r\n expect(metrics.JSHeapUsedSize).toBeLessThan(50 * 1024 * 1024); // 50MB\r\n });\r\n});\r\n`,\r\n };\r\n\r\n return templates[type];\r\n}\r\n\r\n/**\r\n * 生成测试文件到磁盘\r\n * @returns 生成的文件路径\r\n */\r\nexport function generateTestFile(\r\n type: TestType,\r\n context: TemplateContext,\r\n outputDir: string,\r\n): string {\r\n const content = renderTemplate(type, context);\r\n\r\n // 根据类型确定输出子目录\r\n const subDirMap: Record<TestType, string> = {\r\n unit: 'unit',\r\n component: 'component',\r\n e2e: 'e2e',\r\n api: 'api',\r\n visual: 'visual',\r\n performance: 'performance',\r\n };\r\n\r\n const subDir = subDirMap[type];\r\n const dir = path.join(outputDir, subDir);\r\n\r\n // 确保目录存在\r\n if (!fs.existsSync(dir)) {\r\n fs.mkdirSync(dir, { recursive: true });\r\n }\r\n\r\n // 生成文件名\r\n const fileName = `${context.name}.${type === 'e2e' ? 'spec' : 'test'}.ts`;\r\n const filePath = path.join(dir, fileName);\r\n\r\n // 检查文件是否已存在\r\n if (fs.existsSync(filePath)) {\r\n throw new Error(`测试文件已存在: ${filePath}`);\r\n }\r\n\r\n fs.writeFileSync(filePath, content, 'utf-8');\r\n return filePath;\r\n}\r\n\r\n/**\r\n * Convert string to camelCase\r\n */\r\nfunction toCamelCase(str: string): string {\r\n return str\r\n .replace(/[-_\\s]+(.)?/g, (_, c: string) => (c ? c.toUpperCase() : ''))\r\n .replace(/^[A-Z]/, (c) => c.toLowerCase());\r\n}\r\n\r\n/**\r\n * Convert string to PascalCase\r\n */\r\nfunction toPascalCase(str: string): string {\r\n const camel = toCamelCase(str);\r\n return camel.charAt(0).toUpperCase() + camel.slice(1);\r\n}\r\n","/**\r\n * 全局 AI 配置管理 - 存储在 ~/.qat/ai.json\r\n * 与项目无关,所有项目共享同一 AI 配置\r\n */\r\n\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport os from 'node:os';\r\nimport type { AIConfig } from '../types/index.js';\r\n\r\n/** 全局配置目录 */\r\nconst QAT_DIR = path.join(os.homedir(), '.qat');\r\n/** 全局 AI 配置文件路径 */\r\nconst AI_CONFIG_PATH = path.join(QAT_DIR, 'ai.json');\r\n\r\n/** 全局 AI 配置结构 */\r\nexport interface GlobalAIConfig {\r\n provider: string;\r\n apiKey: string;\r\n baseUrl: string;\r\n model: string;\r\n}\r\n\r\n/**\r\n * 加载全局 AI 配置\r\n * @returns 配置对象,未配置时返回 null\r\n */\r\nexport function loadGlobalAIConfig(): GlobalAIConfig | null {\r\n if (!fs.existsSync(AI_CONFIG_PATH)) {\r\n return null;\r\n }\r\n\r\n try {\r\n const content = fs.readFileSync(AI_CONFIG_PATH, 'utf-8');\r\n const config = JSON.parse(content) as GlobalAIConfig;\r\n\r\n // 至少要有 baseUrl 和 model 才算已配置\r\n if (!config.baseUrl || !config.model) {\r\n return null;\r\n }\r\n\r\n return config;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * 保存全局 AI 配置\r\n */\r\nexport function saveGlobalAIConfig(config: GlobalAIConfig): void {\r\n // 确保目录存在\r\n if (!fs.existsSync(QAT_DIR)) {\r\n fs.mkdirSync(QAT_DIR, { recursive: true });\r\n }\r\n\r\n fs.writeFileSync(AI_CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');\r\n}\r\n\r\n/**\r\n * 检查全局 AI 是否已配置\r\n */\r\nexport function isGlobalAIConfigured(): boolean {\r\n return loadGlobalAIConfig() !== null;\r\n}\r\n\r\n/**\r\n * 将全局 AI 配置转换为 QATConfig.ai 格式\r\n */\r\nexport function toAIConfig(globalConfig: GlobalAIConfig): AIConfig {\r\n return {\r\n provider: globalConfig.provider || 'openai',\r\n apiKey: globalConfig.apiKey || undefined,\r\n baseUrl: globalConfig.baseUrl,\r\n model: globalConfig.model,\r\n };\r\n}\r\n\r\n/**\r\n * 脱敏 API Key 用于显示\r\n */\r\nexport function maskApiKey(apiKey: string): string {\r\n if (!apiKey) return '(未设置)';\r\n if (apiKey.length <= 8) return '****';\r\n return apiKey.slice(0, 4) + '****' + apiKey.slice(-4);\r\n}\r\n\r\n/**\r\n * 获取配置文件路径(用于提示用户)\r\n */\r\nexport function getAIConfigPath(): string {\r\n return AI_CONFIG_PATH;\r\n}\r\n","/**\r\n * create 命令 - 交互式创建各类型测试用例\r\n */\r\n\r\nimport { Command } from 'commander';\r\nimport chalk from 'chalk';\r\nimport inquirer from 'inquirer';\r\nimport ora from 'ora';\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport type { CreateOptions, TestType } from '../types/index.js';\r\nimport { loadConfig } from '../services/config.js';\r\nimport { detectProject, discoverVueComponents, discoverUtilityFiles } from '../services/detector.js';\r\nimport { renderTemplate } from '../services/template.js';\r\nimport { getAIProvider, isAIAvailable, createAIProvider } from '../ai/provider.js';\r\nimport { analyzeFile } from '../services/source-analyzer.js';\r\nimport type { PropInfo, EmitInfo } from '../services/source-analyzer.js';\r\nimport { loadGlobalAIConfig, toAIConfig } from '../services/global-config.js';\r\nimport { generateWithReview, printReviewReport, type ReviewReportEntry } from '../services/test-reviewer.js';\r\n\r\nconst TEST_TYPE_LABELS: Record<TestType, string> = {\r\n unit: '单元测试',\r\n component: '组件测试',\r\n e2e: 'E2E端到端测试',\r\n api: 'API接口测试',\r\n visual: '视觉回归测试',\r\n performance: '性能测试',\r\n};\r\n\r\nconst TEST_TYPE_DESCRIPTIONS: Record<TestType, string> = {\r\n unit: '测试工具函数、composables、services 等独立模块',\r\n component: '测试 Vue 组件的渲染、交互、事件等',\r\n e2e: '测试完整的用户操作流程(页面导航、表单提交等)',\r\n api: '测试 API 接口的请求和响应',\r\n visual: '通过截图比对检测 UI 视觉变化',\r\n performance: '测试页面加载性能和 Core Web Vitals',\r\n};\r\n\r\nexport function registerCreateCommand(program: Command): void {\r\n program\r\n .command('create')\r\n .description('创建测试用例 - 交互式生成各类型测试文件')\r\n .option('-t, --type <types...>', '测试类型 (unit|component|e2e|api|visual|performance),支持多选')\r\n .option('-n, --name <name>', '测试用例名称')\r\n .option('--target <paths...>', '被测目标文件/组件路径,支持多选')\r\n .action(async (options: CreateOptions) => {\r\n try {\r\n await executeCreate(options);\r\n } catch (error) {\r\n console.error(chalk.red(`\\n ✗ ${error instanceof Error ? error.message : String(error)}\\n`));\r\n process.exit(1);\r\n }\r\n });\r\n}\r\n\r\nasync function executeCreate(options: CreateOptions): Promise<void> {\r\n // 加载配置\r\n const config = await loadConfig();\r\n\r\n // 用全局 AI 配置覆盖(AI 配置不存储在 qat.config.js 中)\r\n const globalAI = loadGlobalAIConfig();\r\n if (globalAI) {\r\n config.ai = toAIConfig(globalAI);\r\n }\r\n const projectInfo = detectProject();\r\n\r\n let { type, name, target } = options;\r\n\r\n // 1. 选择测试类型(多选)\r\n let types: TestType[];\r\n if (type) {\r\n types = Array.isArray(type) ? type : [type];\r\n } else {\r\n const answers = await inquirer.prompt([\r\n {\r\n type: 'checkbox',\r\n name: 'types',\r\n message: '选择测试类型 (空格选择/取消,回车确认):',\r\n choices: Object.entries(TEST_TYPE_LABELS).map(([value, label]) => ({\r\n name: `${label} - ${chalk.gray(TEST_TYPE_DESCRIPTIONS[value as TestType])}`,\r\n value,\r\n short: label,\r\n checked: value === 'unit' || value === 'component',\r\n })),\r\n validate: (input: string[]) => {\r\n if (input.length === 0) return '请至少选择一种测试类型';\r\n return true;\r\n },\r\n },\r\n ]);\r\n types = answers.types as TestType[];\r\n }\r\n\r\n // 校验测试类型\r\n for (const t of types) {\r\n if (!TEST_TYPE_LABELS[t]) {\r\n throw new Error(`无效的测试类型: ${t},可选值: ${Object.keys(TEST_TYPE_LABELS).join(', ')}`);\r\n }\r\n }\r\n\r\n // 2. 选择被测目标(多选)\r\n let targets: string[];\r\n if (target) {\r\n targets = Array.isArray(target) ? target : [target];\r\n } else {\r\n targets = await selectTargets(types, projectInfo, config.project.srcDir);\r\n }\r\n\r\n // 3. 输入测试名称\r\n if (!name) {\r\n const defaultName = generateDefaultName(types[0], targets[0]);\r\n const answers = await inquirer.prompt([\r\n {\r\n type: 'input',\r\n name: 'name',\r\n message: '输入测试用例名称:',\r\n default: defaultName,\r\n validate: (input: string) => {\r\n if (!input.trim()) return '名称不能为空';\r\n if (!/^[\\w-]+$/.test(input.trim())) return '名称只能包含字母、数字、下划线和连字符';\r\n return true;\r\n },\r\n },\r\n ]);\r\n name = answers.name;\r\n }\r\n\r\n // 4. AI 生成选项\r\n let useAI = false;\r\n if (isAIAvailable(config.ai) && targets.length > 0) {\r\n const { ai } = await inquirer.prompt([\r\n {\r\n type: 'confirm',\r\n name: 'ai',\r\n message: '是否使用 AI 辅助生成测试用例?',\r\n default: false,\r\n },\r\n ]);\r\n useAI = ai;\r\n }\r\n\r\n // 5. 批量生成测试文件\r\n const createdFiles: { type: TestType; filePath: string }[] = [];\r\n let skippedCount = 0;\r\n const reviewReportEntries: ReviewReportEntry[] = [];\r\n\r\n for (const testType of types) {\r\n for (const testTarget of targets) {\r\n const spinner = ora(`正在生成 ${TEST_TYPE_LABELS[testType]} - ${path.basename(testTarget)}...`).start();\r\n\r\n try {\r\n let content: string;\r\n\r\n if (useAI && testTarget) {\r\n const aiResult = await generateWithAI(testType, name!, testTarget, config);\r\n content = aiResult.code;\r\n if (aiResult.reviewEntry) {\r\n reviewReportEntries.push(aiResult.reviewEntry);\r\n }\r\n } else {\r\n // 源码分析 - 根据目标文件生成个性化测试\r\n const analysis = testTarget ? analyzeFile(testTarget) : undefined;\r\n\r\n content = renderTemplate(testType, {\r\n name: name!,\r\n target: testTarget || `./${config.project.srcDir}`,\r\n framework: projectInfo.framework,\r\n vueVersion: projectInfo.vueVersion,\r\n typescript: projectInfo.typescript,\r\n uiLibrary: projectInfo.uiLibrary,\r\n extraImports: projectInfo.componentTestSetup?.extraImports,\r\n globalPlugins: projectInfo.componentTestSetup?.globalPlugins,\r\n globalStubs: projectInfo.componentTestSetup?.globalStubs,\r\n mountOptions: projectInfo.componentTestSetup?.mountOptions,\r\n // 注入源码分析结果\r\n exports: analysis?.exports.map((e) => ({\r\n name: e.name,\r\n kind: e.kind,\r\n params: e.params,\r\n isAsync: e.isAsync,\r\n returnType: e.returnType,\r\n })),\r\n props: analysis?.vueAnalysis?.props.map((p: PropInfo) => ({\r\n name: p.name,\r\n type: p.type,\r\n required: p.required,\r\n defaultValue: p.defaultValue,\r\n })),\r\n emits: analysis?.vueAnalysis?.emits.map((e: EmitInfo) => ({\r\n name: e.name,\r\n params: e.params,\r\n })),\r\n methods: analysis?.vueAnalysis?.methods,\r\n computed: analysis?.vueAnalysis?.computed,\r\n });\r\n }\r\n\r\n // 确定输出目录\r\n const outputDir = getTestOutputDir(testType);\r\n const filePath = path.join(outputDir, `${name}.${testType === 'e2e' ? 'spec' : 'test'}.ts`);\r\n\r\n // 检查文件是否已存在\r\n if (fs.existsSync(filePath)) {\r\n spinner.warn(`测试文件已存在: ${path.relative(process.cwd(), filePath)}`);\r\n skippedCount++;\r\n continue;\r\n }\r\n\r\n // 确保目录存在\r\n if (!fs.existsSync(outputDir)) {\r\n fs.mkdirSync(outputDir, { recursive: true });\r\n }\r\n\r\n // 写入文件\r\n fs.writeFileSync(filePath, content, 'utf-8');\r\n\r\n spinner.succeed(`${TEST_TYPE_LABELS[testType]} - ${path.basename(testTarget)}`);\r\n createdFiles.push({ type: testType, filePath });\r\n } catch (error) {\r\n spinner.fail(`${TEST_TYPE_LABELS[testType]} - ${path.basename(testTarget)} 创建失败`);\r\n console.log(chalk.gray(` ${error instanceof Error ? error.message : String(error)}`));\r\n }\r\n }\r\n }\r\n\r\n // 6. 显示结果\r\n displayCreateResult(createdFiles, skippedCount, useAI);\r\n\r\n // 7. 打印审计报告\r\n if (reviewReportEntries.length > 0) {\r\n printReviewReport(reviewReportEntries);\r\n }\r\n}\r\n\r\n/**\r\n * 交互式多选被测目标\r\n */\r\nasync function selectTargets(\r\n types: TestType[],\r\n projectInfo: ReturnType<typeof detectProject>,\r\n srcDir: string,\r\n): Promise<string[]> {\r\n // 收集所有可选目标\r\n const allTargets: { name: string; value: string }[] = [];\r\n\r\n // 工具文件\r\n if (types.includes('unit')) {\r\n const utils = discoverUtilityFiles(process.cwd(), srcDir);\r\n for (const f of utils.slice(0, 30)) {\r\n allTargets.push({ name: `${chalk.gray('[unit]')} ${f}`, value: f });\r\n }\r\n }\r\n\r\n // Vue 组件\r\n if (types.includes('component')) {\r\n const components = discoverVueComponents(process.cwd(), srcDir);\r\n for (const f of components.slice(0, 30)) {\r\n allTargets.push({ name: `${chalk.gray('[comp]')} ${f}`, value: f });\r\n }\r\n }\r\n\r\n // 没有发现文件时手动输入\r\n if (allTargets.length === 0) {\r\n const { manualTarget } = await inquirer.prompt([\r\n {\r\n type: 'input',\r\n name: 'manualTarget',\r\n message: '输入被测目标路径:',\r\n default: `./${srcDir}`,\r\n },\r\n ]);\r\n return [manualTarget];\r\n }\r\n\r\n // 多选\r\n const { selectedTargets } = await inquirer.prompt([\r\n {\r\n type: 'checkbox',\r\n name: 'selectedTargets',\r\n message: '选择被测目标 (空格选择/取消,回车确认):',\r\n choices: [\r\n ...allTargets,\r\n { name: chalk.gray('手动输入路径'), value: '__manual__' },\r\n ],\r\n validate: (input: string[]) => {\r\n if (input.length === 0) return '请至少选择一个目标';\r\n return true;\r\n },\r\n },\r\n ]);\r\n\r\n // 处理手动输入\r\n if (selectedTargets.includes('__manual__')) {\r\n const { manualTarget } = await inquirer.prompt([\r\n {\r\n type: 'input',\r\n name: 'manualTarget',\r\n message: '输入目标路径:',\r\n },\r\n ]);\r\n return [\r\n ...selectedTargets.filter((t: string) => t !== '__manual__'),\r\n manualTarget,\r\n ].filter(Boolean);\r\n }\r\n\r\n return selectedTargets;\r\n}\r\n\r\n/**\r\n * 生成默认测试名称\r\n */\r\nfunction generateDefaultName(type: TestType, target?: string): string {\r\n if (!target) return `${type}-test`;\r\n\r\n const basename = path.basename(target, path.extname(target));\r\n const cleaned = basename\r\n .replace(/[^a-zA-Z0-9]/g, '-')\r\n .replace(/-+/g, '-')\r\n .replace(/^-|-$/g, '');\r\n\r\n return cleaned || `${type}-test`;\r\n}\r\n\r\n/**\r\n * AI 辅助生成测试用例(结合源码分析 + 审计员审核)\r\n */\r\nasync function generateWithAI(\r\n type: TestType,\r\n name: string,\r\n target: string,\r\n config: import('../types/index.js').QATConfig,\r\n): Promise<{ code: string; reviewEntry?: ReviewReportEntry }> {\r\n const provider = getAIProvider(config.ai);\r\n\r\n if (!provider.capabilities.generateTest) {\r\n throw new Error('当前 AI Provider 不支持测试生成');\r\n }\r\n\r\n // 读取源码\r\n let sourceCode = '';\r\n const fullPath = path.resolve(process.cwd(), target);\r\n if (fs.existsSync(fullPath)) {\r\n sourceCode = fs.readFileSync(fullPath, 'utf-8');\r\n }\r\n\r\n // 源码分析\r\n const analysis = analyzeFile(target);\r\n\r\n const analysisSummary = analysis.exports.length > 0 || analysis.vueAnalysis ? {\r\n exports: analysis.exports.map((e) => ({\r\n name: e.name,\r\n kind: e.kind,\r\n params: e.params,\r\n isAsync: e.isAsync,\r\n })),\r\n props: analysis.vueAnalysis?.props.map((p: PropInfo) => ({\r\n name: p.name,\r\n type: p.type,\r\n required: p.required,\r\n })),\r\n emits: analysis.vueAnalysis?.emits.map((e: EmitInfo) => ({\r\n name: e.name,\r\n params: e.params,\r\n })),\r\n methods: analysis.vueAnalysis?.methods,\r\n computed: analysis.vueAnalysis?.computed,\r\n } : undefined;\r\n\r\n // 使用审计流程生成\r\n const reviewResult = await generateWithReview({\r\n testType: type,\r\n targetPath: target,\r\n sourceCode,\r\n analysis: analysisSummary,\r\n aiConfig: config.ai,\r\n framework: 'vue',\r\n });\r\n\r\n // 构建文件头注释\r\n const headerComment = [\r\n `// AI Generated Test - ${name}`,\r\n `// 审计: ${reviewResult.approved ? '通过' : '未通过'} (${(reviewResult.reviewScore * 100).toFixed(0)}%) — ${reviewResult.attempts}次尝试`,\r\n reviewResult.reviewFeedback ? `// 审计意见: ${reviewResult.reviewFeedback}` : '',\r\n '',\r\n ].filter(Boolean).join('\\n');\r\n\r\n const code = `${headerComment}\\n${reviewResult.code}`;\r\n\r\n const reviewEntry: ReviewReportEntry = {\r\n target,\r\n testType: type,\r\n approved: reviewResult.approved,\r\n attempts: reviewResult.attempts,\r\n score: reviewResult.reviewScore,\r\n feedback: reviewResult.reviewFeedback,\r\n issues: reviewResult.approved ? [] : reviewResult.reviewIssues,\r\n };\r\n\r\n return { code, reviewEntry };\r\n}\r\n\r\n/**\r\n * 获取测试输出目录\r\n */\r\nfunction getTestOutputDir(type: TestType): string {\r\n const subDirMap: Record<TestType, string> = {\r\n unit: 'tests/unit',\r\n component: 'tests/component',\r\n e2e: 'tests/e2e',\r\n api: 'tests/api',\r\n visual: 'tests/visual',\r\n performance: 'tests/e2e',\r\n };\r\n\r\n return subDirMap[type];\r\n}\r\n\r\n/**\r\n * 显示创建结果\r\n */\r\nfunction displayCreateResult(\r\n createdFiles: { type: TestType; filePath: string }[],\r\n skippedCount: number,\r\n usedAI: boolean,\r\n): void {\r\n if (createdFiles.length === 0 && skippedCount === 0) {\r\n console.log(chalk.yellow('\\n 没有创建任何测试文件\\n'));\r\n return;\r\n }\r\n\r\n console.log(chalk.green(`\\n ✓ 已创建 ${createdFiles.length} 个测试文件`));\r\n if (skippedCount > 0) {\r\n console.log(chalk.yellow(` ⚠ 跳过 ${skippedCount} 个已存在的文件`));\r\n }\r\n console.log();\r\n\r\n for (const { type, filePath } of createdFiles) {\r\n const relativePath = path.relative(process.cwd(), filePath);\r\n console.log(chalk.white(` ${chalk.cyan(TEST_TYPE_LABELS[type])} ${chalk.gray(relativePath)}`));\r\n }\r\n\r\n if (usedAI) {\r\n console.log();\r\n console.log(chalk.magenta(' 生成方式: AI 辅助'));\r\n }\r\n\r\n console.log();\r\n console.log(chalk.gray(' 运行测试:'));\r\n\r\n // 按类型去重\r\n const uniqueTypes = [...new Set(createdFiles.map((f) => f.type))];\r\n if (uniqueTypes.length === 1) {\r\n console.log(chalk.cyan(` qat run -t ${uniqueTypes[0]}`));\r\n } else {\r\n console.log(chalk.cyan(` qat run -t ${uniqueTypes.join(' -t ')}`));\r\n console.log(chalk.gray(' # 或运行全部'));\r\n console.log(chalk.cyan(' qat run'));\r\n }\r\n console.log();\r\n}\r\n","/**\r\n * run 命令 - 按类型/文件/标签运行测试,聚合结果\r\n */\r\n\r\nimport { Command } from 'commander';\r\nimport chalk from 'chalk';\r\nimport inquirer from 'inquirer';\r\nimport ora from 'ora';\r\nimport type { RunOptions, TestType, TestRunResult, TestError } from '../types/index.js';\r\nimport { loadConfig } from '../services/config.js';\r\nimport { runVitest } from '../runners/vitest-runner.js';\r\nimport { runPlaywright } from '../runners/playwright-runner.js';\r\nimport { runLighthouse } from '../runners/lighthouse-runner.js';\r\nimport { getAIProvider, isAIAvailable } from '../ai/provider.js';\r\nimport { loadGlobalAIConfig, toAIConfig } from '../services/global-config.js';\r\n\r\nconst TYPE_RUNNERS: Record<TestType, string> = {\r\n unit: 'Vitest',\r\n component: 'Vitest',\r\n e2e: 'Playwright',\r\n api: 'Vitest',\r\n visual: 'Playwright',\r\n performance: 'Lighthouse',\r\n};\r\n\r\nconst TYPE_LABELS: Record<TestType, string> = {\r\n unit: '单元测试',\r\n component: '组件测试',\r\n e2e: 'E2E测试',\r\n api: 'API测试',\r\n visual: '视觉回归测试',\r\n performance: '性能测试',\r\n};\r\n\r\nexport function registerRunCommand(program: Command): void {\r\n program\r\n .command('run')\r\n .description('运行测试 - 支持按类型/文件/标签运行')\r\n .option('-t, --type <types...>', '测试类型 (unit|component|e2e|api|visual|performance),支持多选')\r\n .option('-f, --file <file>', '指定测试文件路径')\r\n .option('--tag <tag>', '按标签筛选测试用例')\r\n .option('-w, --watch', '监听模式,文件变更自动重跑')\r\n .option('-p, --parallel', '并行执行测试')\r\n .option('-b, --browser <browser>', '指定浏览器 (chromium|firefox|webkit)')\r\n .action(async (options: RunOptions) => {\r\n try {\r\n await executeRun(options);\r\n } catch (error) {\r\n console.error(chalk.red(`\\n ✗ ${error instanceof Error ? error.message : String(error)}\\n`));\r\n process.exit(1);\r\n }\r\n });\r\n}\r\n\r\nasync function executeRun(options: RunOptions): Promise<void> {\r\n const { type, file, tag, watch, parallel, browser } = options;\r\n\r\n // 加载配置\r\n const config = await loadConfig(options.config);\r\n\r\n // 用全局 AI 配置覆盖(AI 配置不存储在 qat.config.js 中)\r\n const globalAI = loadGlobalAIConfig();\r\n if (globalAI) {\r\n config.ai = toAIConfig(globalAI);\r\n }\r\n\r\n // Watch 模式 - 直接委托给 vitest 的 watch\r\n if (watch) {\r\n await runWatchMode(config, options);\r\n return;\r\n }\r\n\r\n // 确定要运行的测试类型\r\n let typesToRun = await determineTypesToRun(type, file, config);\r\n\r\n if (typesToRun.length === 0) {\r\n console.log(chalk.yellow('\\n 没有可运行的测试类型(请在 qat.config.ts 中启用)\\n'));\r\n return;\r\n }\r\n\r\n // 交互选择运行方式(仅在没有通过 CLI 指定具体类型时)\r\n if (!type && !file) {\r\n const { runMode } = await inquirer.prompt([\r\n {\r\n type: 'list',\r\n name: 'runMode',\r\n message: '选择运行方式:',\r\n choices: [\r\n { name: '立即执行', value: 'now' },\r\n { name: '仅查看命令 (不执行)', value: 'dry' },\r\n ],\r\n default: 'now',\r\n },\r\n ]);\r\n\r\n if (runMode === 'dry') {\r\n printDryRunCommands(typesToRun, options, config);\r\n return;\r\n }\r\n }\r\n\r\n // 执行测试\r\n const results: TestRunResult[] = [];\r\n\r\n if (parallel && typesToRun.length > 1) {\r\n const spinner = ora('正在并行运行所有测试...').start();\r\n const runPromises = typesToRun.map((t) => runTestType(t, config, options));\r\n const runResults = await Promise.allSettled(runPromises);\r\n\r\n for (const result of runResults) {\r\n if (result.status === 'fulfilled') {\r\n results.push(result.value);\r\n } else {\r\n results.push(createErrorResult('unknown' as TestType, result.reason));\r\n }\r\n }\r\n spinner.stop();\r\n } else {\r\n for (const testType of typesToRun) {\r\n const label = TYPE_LABELS[testType] || testType;\r\n const spinner = ora(`正在运行 ${label}...`).start();\r\n\r\n try {\r\n const result = await runTestType(testType, config, options);\r\n results.push(result);\r\n spinner[suiteStatusToSpinner(result.status)](`${label} ${result.status === 'passed' ? '通过' : '失败'}`);\r\n } catch (error) {\r\n results.push(createErrorResult(testType, error));\r\n spinner.fail(`${label} 执行错误`);\r\n }\r\n }\r\n }\r\n\r\n // 输出汇总\r\n displaySummary(results, config);\r\n}\r\n\r\n/**\r\n * 打印 dry-run 命令\r\n */\r\nfunction printDryRunCommands(\r\n types: TestType[],\r\n options: RunOptions,\r\n config: import('../types/index.js').QATConfig,\r\n): void {\r\n console.log();\r\n console.log(chalk.cyan(' 可执行的测试命令:\\n'));\r\n\r\n for (const testType of types) {\r\n const label = TYPE_LABELS[testType];\r\n const runner = TYPE_RUNNERS[testType];\r\n\r\n let cmd: string;\r\n switch (runner) {\r\n case 'Vitest':\r\n cmd = 'npx vitest run';\r\n {\r\n const dirMap: Record<string, string> = {\r\n unit: 'tests/unit',\r\n component: 'tests/component',\r\n api: 'tests/api',\r\n };\r\n const dir = dirMap[testType];\r\n if (dir) cmd += ` --include '${dir}/**/*.test.ts'`;\r\n if (config.vitest.coverage) cmd += ' --coverage';\r\n }\r\n break;\r\n case 'Playwright':\r\n cmd = 'npx playwright test';\r\n {\r\n const dirMap: Record<string, string> = {\r\n e2e: 'tests/e2e',\r\n visual: 'tests/visual',\r\n };\r\n const dir = dirMap[testType];\r\n if (dir) cmd += ` ${dir}`;\r\n if (options.browser) cmd += ` --project ${options.browser}`;\r\n }\r\n break;\r\n case 'Lighthouse':\r\n cmd = `npx lighthouse ${config.lighthouse.urls[0] || 'http://localhost:5173'} --output=json --quiet --chrome-flags=\"--headless --no-sandbox\"`;\r\n break;\r\n default:\r\n cmd = `# ${label}: 未知运行器`;\r\n }\r\n\r\n console.log(chalk.white(` ${label}:`));\r\n console.log(chalk.cyan(` ${cmd}`));\r\n console.log();\r\n }\r\n\r\n console.log(chalk.gray(' 或使用 QAT 统一运行:'));\r\n if (types.length === 1) {\r\n console.log(chalk.cyan(` qat run -t ${types[0]}`));\r\n } else {\r\n console.log(chalk.cyan(` qat run -t ${types.join(' -t ')}`));\r\n console.log(chalk.gray(' # 并行运行'));\r\n console.log(chalk.cyan(' qat run -p'));\r\n }\r\n console.log();\r\n}\r\n\r\n/**\r\n * 确定要运行的测试类型列表\r\n */\r\nasync function determineTypesToRun(\r\n type: string | string[] | undefined,\r\n file: string | undefined,\r\n config: import('../types/index.js').QATConfig,\r\n): Promise<TestType[]> {\r\n // CLI 传入了类型\r\n if (type) {\r\n const types = Array.isArray(type) ? type : [type];\r\n return types as TestType[];\r\n }\r\n\r\n if (file) {\r\n if (file.includes('/e2e/') || file.includes('\\\\e2e\\\\')) return ['e2e'];\r\n if (file.includes('/visual/') || file.includes('\\\\visual\\\\')) return ['visual'];\r\n if (file.includes('/api/') || file.includes('\\\\api\\\\')) return ['api'];\r\n if (file.includes('/component/') || file.includes('\\\\component\\\\')) return ['component'];\r\n return ['unit'];\r\n }\r\n\r\n // 交互多选\r\n const enabledTypes = getEnabledTypes(config);\r\n\r\n const { selectedTypes } = await inquirer.prompt([\r\n {\r\n type: 'checkbox',\r\n name: 'selectedTypes',\r\n message: '选择要运行的测试类型 (空格选择/取消,回车确认):',\r\n choices: enabledTypes.map((t) => ({\r\n name: `${TYPE_LABELS[t]} (${chalk.gray(TYPE_RUNNERS[t])})`,\r\n value: t,\r\n checked: true,\r\n })),\r\n validate: (input: string[]) => {\r\n if (input.length === 0) return '请至少选择一种测试类型';\r\n return true;\r\n },\r\n },\r\n ]);\r\n\r\n return selectedTypes as TestType[];\r\n}\r\n\r\n/**\r\n * 获取配置中启用的测试类型\r\n */\r\nfunction getEnabledTypes(config: import('../types/index.js').QATConfig): TestType[] {\r\n const types: TestType[] = [];\r\n if (config.vitest.enabled) {\r\n types.push('unit', 'component', 'api');\r\n }\r\n if (config.playwright.enabled) {\r\n types.push('e2e');\r\n }\r\n if (config.visual.enabled) {\r\n types.push('visual');\r\n }\r\n if (config.lighthouse.enabled) {\r\n types.push('performance');\r\n }\r\n return types.length > 0 ? types : ['unit', 'component', 'e2e', 'api', 'visual', 'performance'];\r\n}\r\n\r\n/**\r\n * 运行指定类型的测试\r\n */\r\nasync function runTestType(\r\n testType: TestType,\r\n config: import('../types/index.js').QATConfig,\r\n options: RunOptions,\r\n): Promise<TestRunResult> {\r\n const runner = TYPE_RUNNERS[testType];\r\n\r\n switch (runner) {\r\n case 'Vitest': {\r\n return runVitest({\r\n type: testType,\r\n files: options.file ? [options.file] : undefined,\r\n coverage: config.vitest.coverage,\r\n configPath: undefined,\r\n });\r\n }\r\n case 'Playwright': {\r\n return runPlaywright({\r\n type: testType,\r\n files: options.file ? [options.file] : undefined,\r\n browsers: options.browser ? [options.browser as 'chromium' | 'firefox' | 'webkit'] : config.playwright.browsers,\r\n headed: false,\r\n configPath: undefined,\r\n });\r\n }\r\n case 'Lighthouse': {\r\n return runLighthouse({\r\n urls: config.lighthouse.urls,\r\n runs: config.lighthouse.runs,\r\n thresholds: config.lighthouse.thresholds,\r\n });\r\n }\r\n default:\r\n throw new Error(`未知的运行器: ${runner}`);\r\n }\r\n}\r\n\r\n/**\r\n * Watch 模式\r\n */\r\nasync function runWatchMode(\r\n config: import('../types/index.js').QATConfig,\r\n options: RunOptions,\r\n): Promise<void> {\r\n console.log(chalk.cyan(' 监听模式已启动 (Ctrl+C 退出)\\n'));\r\n\r\n const { spawn } = await import('node:child_process');\r\n\r\n const args = ['vitest', '--watch'];\r\n if (options.file) {\r\n args.push(options.file);\r\n }\r\n if (options.type) {\r\n const types = Array.isArray(options.type) ? options.type : [options.type];\r\n const dirMap: Record<string, string> = {\r\n unit: 'tests/unit',\r\n component: 'tests/component',\r\n api: 'tests/api',\r\n };\r\n const dirs = types.map((t) => dirMap[t]).filter(Boolean);\r\n if (dirs.length > 0) {\r\n args.push('--include', dirs.map((d) => `${d}/**/*.test.ts`).join(','));\r\n }\r\n }\r\n\r\n const child = spawn('npx', args, {\r\n cwd: process.cwd(),\r\n stdio: 'inherit',\r\n shell: true,\r\n });\r\n\r\n child.on('error', (err) => {\r\n console.error(chalk.red(`\\n Vitest 启动失败: ${err.message}\\n`));\r\n });\r\n\r\n child.on('exit', (code) => {\r\n if (code !== null && code !== 0) {\r\n process.exit(code);\r\n }\r\n });\r\n\r\n process.on('SIGINT', () => {\r\n child.kill('SIGINT');\r\n process.exit(0);\r\n });\r\n}\r\n\r\n/**\r\n * 创建错误结果\r\n */\r\nfunction createErrorResult(type: TestType, error: unknown): TestRunResult {\r\n return {\r\n type,\r\n status: 'failed',\r\n startTime: Date.now(),\r\n endTime: Date.now(),\r\n duration: 0,\r\n suites: [{\r\n name: 'Runner Error',\r\n file: 'unknown',\r\n type,\r\n status: 'failed',\r\n duration: 0,\r\n tests: [{\r\n name: `${type} runner error`,\r\n file: 'unknown',\r\n status: 'failed',\r\n duration: 0,\r\n error: {\r\n message: error instanceof Error ? error.message : String(error),\r\n },\r\n retries: 0,\r\n }],\r\n }],\r\n };\r\n}\r\n\r\n/**\r\n * 映射测试状态到 spinner 方法\r\n */\r\nfunction suiteStatusToSpinner(status: string): 'succeed' | 'fail' | 'warn' {\r\n switch (status) {\r\n case 'passed': return 'succeed';\r\n case 'failed': return 'fail';\r\n default: return 'warn';\r\n }\r\n}\r\n\r\n/**\r\n * 显示测试汇总\r\n */\r\nasync function displaySummary(results: TestRunResult[], config: import('../types/index.js').QATConfig): Promise<void> {\r\n let totalSuites = 0;\r\n let totalPassed = 0;\r\n let totalFailed = 0;\r\n let totalSkipped = 0;\r\n let totalDuration = 0;\r\n\r\n for (const result of results) {\r\n totalDuration += result.duration;\r\n for (const suite of result.suites) {\r\n totalSuites++;\r\n for (const test of suite.tests) {\r\n if (test.status === 'passed') totalPassed++;\r\n else if (test.status === 'failed') totalFailed++;\r\n else if (test.status === 'skipped') totalSkipped++;\r\n }\r\n }\r\n }\r\n\r\n const total = totalPassed + totalFailed + totalSkipped;\r\n\r\n console.log();\r\n console.log(chalk.cyan(' ─────────────────────────────'));\r\n\r\n if (totalFailed > 0) {\r\n console.log(chalk.red(' ✗ 测试失败'));\r\n } else if (total === 0) {\r\n console.log(chalk.yellow(' ⚠ 没有发现测试用例'));\r\n } else {\r\n console.log(chalk.green(' ✓ 全部通过'));\r\n }\r\n\r\n console.log();\r\n console.log(` ${chalk.white('测试用例:')} ${total} total`);\r\n if (totalPassed > 0) console.log(` ${chalk.green(' 通过:')} ${totalPassed}`);\r\n if (totalFailed > 0) console.log(` ${chalk.red(' 失败:')} ${totalFailed}`);\r\n if (totalSkipped > 0) console.log(` ${chalk.yellow(' 跳过:')} ${totalSkipped}`);\r\n console.log(` ${chalk.gray(' 套件:')} ${totalSuites}`);\r\n console.log(` ${chalk.gray(' 耗时:')} ${formatDuration(totalDuration)}`);\r\n\r\n // 按类型展示\r\n if (results.length > 1) {\r\n console.log();\r\n for (const result of results) {\r\n const label = TYPE_LABELS[result.type] || result.type;\r\n const icon = result.status === 'passed' ? chalk.green('✓') : result.status === 'failed' ? chalk.red('✗') : chalk.yellow('⚠');\r\n const testCount = result.suites.reduce((sum, s) => sum + s.tests.length, 0);\r\n console.log(` ${icon} ${label} (${testCount} tests, ${formatDuration(result.duration)})`);\r\n }\r\n }\r\n\r\n // 性能测试额外信息\r\n for (const result of results) {\r\n if (result.type === 'performance' && result.performance) {\r\n const p = result.performance;\r\n console.log();\r\n console.log(chalk.cyan(' 性能指标:'));\r\n console.log(` Performance: ${scoreColor(p.performance)} ${p.performance}/100`);\r\n console.log(` Accessibility: ${scoreColor(p.accessibility)} ${p.accessibility}/100`);\r\n console.log(` Best Practices: ${scoreColor(p.bestPractices)} ${p.bestPractices}/100`);\r\n console.log(` SEO: ${scoreColor(p.seo)} ${p.seo}/100`);\r\n if (p.lcp !== undefined) console.log(` LCP: ${p.lcp.toFixed(0)}ms`);\r\n if (p.fid !== undefined) console.log(` FID: ${p.fid.toFixed(0)}ms`);\r\n if (p.cls !== undefined) console.log(` CLS: ${p.cls.toFixed(3)}`);\r\n }\r\n }\r\n\r\n // 失败测试详情\r\n const failedTests = results.flatMap((r) =>\r\n r.suites.flatMap((s) =>\r\n s.tests.filter((t) => t.status === 'failed').map((t) => ({ suite: s, test: t }))\r\n )\r\n );\r\n\r\n if (failedTests.length > 0) {\r\n console.log();\r\n console.log(chalk.red(' 失败详情:'));\r\n for (const { suite, test } of failedTests) {\r\n console.log(chalk.red(` ✗ ${suite.name} > ${test.name}`));\r\n if (test.error?.message) {\r\n console.log(chalk.gray(` ${test.error.message.split('\\n')[0]}`));\r\n }\r\n }\r\n }\r\n\r\n console.log(chalk.cyan(' ─────────────────────────────'));\r\n console.log();\r\n\r\n if (totalFailed > 0) {\r\n process.exitCode = 1;\r\n }\r\n\r\n // AI 分析失败测试\r\n if (totalFailed > 0 && isAIAvailable(config.ai)) {\r\n await aiAnalyzeFailures(results, config.ai);\r\n }\r\n}\r\n\r\n/**\r\n * AI 分析失败测试\r\n */\r\nasync function aiAnalyzeFailures(\r\n results: TestRunResult[],\r\n aiConfig: import('../types/index.js').AIConfig,\r\n): Promise<void> {\r\n const failedTests = results.flatMap((r) =>\r\n r.suites.flatMap((s) =>\r\n s.tests\r\n .filter((t) => t.status === 'failed' && t.error)\r\n .map((t) => ({ suite: s, test: t })),\r\n ),\r\n );\r\n\r\n if (failedTests.length === 0) return;\r\n\r\n console.log(chalk.magenta(' 🤖 AI 分析失败原因...'));\r\n console.log();\r\n\r\n const provider = getAIProvider(aiConfig);\r\n\r\n if (!provider.capabilities.suggestFix) return;\r\n\r\n for (const { suite, test } of failedTests.slice(0, 5)) {\r\n try {\r\n const suggestions = await provider.suggestFix(test.error!);\r\n\r\n console.log(chalk.white(` ${suite.name} > ${test.name}`));\r\n\r\n if (test.error?.message) {\r\n console.log(chalk.gray(` 错误: ${test.error.message.split('\\n')[0]}`));\r\n }\r\n\r\n for (const suggestion of suggestions.slice(0, 3)) {\r\n console.log(chalk.cyan(` → ${suggestion}`));\r\n }\r\n console.log();\r\n } catch (error) {\r\n console.log(chalk.gray(` AI 分析失败: ${error instanceof Error ? error.message : String(error)}`));\r\n }\r\n }\r\n}\r\n\r\nfunction formatDuration(ms: number): string {\r\n if (ms < 1000) return `${ms}ms`;\r\n if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;\r\n const min = Math.floor(ms / 60000);\r\n const sec = Math.floor((ms % 60000) / 1000);\r\n return `${min}m ${sec}s`;\r\n}\r\n\r\nfunction scoreColor(score: number): string {\r\n if (score >= 90) return chalk.green('●');\r\n if (score >= 50) return chalk.yellow('●');\r\n return chalk.red('●');\r\n}\r\n","/**\r\n * Vitest 运行器 - 通过子进程调用Vitest,解析输出收集结果\r\n */\r\n\r\nimport { execFile } from 'node:child_process';\r\nimport path from 'node:path';\r\nimport type { TestRunResult, TestSuiteResult, TestCaseResult, TestStatus, TestType, CoverageResult } from '../types/index.js';\r\n\r\nexport interface VitestRunnerOptions {\r\n type: TestType;\r\n files?: string[];\r\n watch?: boolean;\r\n coverage?: boolean;\r\n configPath?: string;\r\n /** 额外传递给 vitest 的参数 */\r\n extraArgs?: string[];\r\n}\r\n\r\n/**\r\n * 执行Vitest测试\r\n */\r\nexport async function runVitest(options: VitestRunnerOptions): Promise<TestRunResult> {\r\n const startTime = Date.now();\r\n\r\n const args = buildVitestArgs(options);\r\n\r\n try {\r\n const result = await execVitest(args);\r\n const endTime = Date.now();\r\n\r\n return {\r\n type: options.type,\r\n status: result.success ? 'passed' : 'failed',\r\n startTime,\r\n endTime,\r\n duration: endTime - startTime,\r\n suites: result.suites,\r\n coverage: result.coverage,\r\n };\r\n } catch (error) {\r\n const endTime = Date.now();\r\n return {\r\n type: options.type,\r\n status: 'failed',\r\n startTime,\r\n endTime,\r\n duration: endTime - startTime,\r\n suites: [{\r\n name: 'Vitest Runner',\r\n file: options.files?.[0] || 'unknown',\r\n type: options.type,\r\n status: 'failed',\r\n duration: endTime - startTime,\r\n tests: [{\r\n name: 'Runner Error',\r\n file: options.files?.[0] || 'unknown',\r\n status: 'failed',\r\n duration: 0,\r\n error: {\r\n message: error instanceof Error ? error.message : String(error),\r\n },\r\n retries: 0,\r\n }],\r\n }],\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * 构建vitest命令参数\r\n */\r\nfunction buildVitestArgs(options: VitestRunnerOptions): string[] {\r\n const args = ['vitest', 'run', '--reporter=json'];\r\n\r\n // 指定测试文件\r\n if (options.files && options.files.length > 0) {\r\n args.push(...options.files);\r\n } else {\r\n // 根据测试类型确定包含路径\r\n const includeMap: Record<string, string[]> = {\r\n unit: ['tests/unit'],\r\n component: ['tests/component'],\r\n api: ['tests/api'],\r\n };\r\n const includes = includeMap[options.type];\r\n if (includes) {\r\n args.push('--include', includes.map((d) => `${d}/**/*.test.ts`).join(','));\r\n }\r\n }\r\n\r\n // 覆盖率\r\n if (options.coverage) {\r\n args.push('--coverage');\r\n }\r\n\r\n // 配置文件\r\n if (options.configPath) {\r\n args.push('--config', options.configPath);\r\n }\r\n\r\n // 额外参数\r\n if (options.extraArgs) {\r\n args.push(...options.extraArgs);\r\n }\r\n\r\n return args;\r\n}\r\n\r\n/**\r\n * 执行vitest命令并解析JSON输出\r\n */\r\nasync function execVitest(args: string[]): Promise<{\r\n success: boolean;\r\n suites: TestSuiteResult[];\r\n coverage?: CoverageResult;\r\n}> {\r\n return new Promise((resolve, reject) => {\r\n const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx';\r\n\r\n const child = execFile(npx, args, {\r\n cwd: process.cwd(),\r\n env: { ...process.env, FORCE_COLOR: '0' },\r\n maxBuffer: 50 * 1024 * 1024, // 50MB\r\n shell: true,\r\n }, (error, stdout, stderr) => {\r\n // vitest 非零退出码表示测试失败,不是命令执行错误\r\n const output = stdout || stderr || '';\r\n\r\n try {\r\n const parsed = parseVitestJSONOutput(output);\r\n resolve(parsed);\r\n } catch {\r\n // JSON 解析失败,尝试从文本输出提取结果\r\n if (output) {\r\n resolve(parseVitestTextOutput(output, !!error));\r\n } else if (error && error.message.includes('ENOENT')) {\r\n reject(new Error('未找到 vitest,请确保已安装: npm install -D vitest'));\r\n } else {\r\n resolve({ success: !error, suites: [] });\r\n }\r\n }\r\n });\r\n\r\n child.on('error', (err) => {\r\n reject(new Error(`Vitest 执行失败: ${err.message}`));\r\n });\r\n });\r\n}\r\n\r\n/**\r\n * 解析 vitest JSON 输出\r\n */\r\nfunction parseVitestJSONOutput(output: string): {\r\n success: boolean;\r\n suites: TestSuiteResult[];\r\n coverage?: CoverageResult;\r\n} {\r\n // vitest --reporter=json 输出 JSON 结果\r\n // 尝试从输出中提取 JSON\r\n const jsonMatch = output.match(/\\{[\\s\\S]*\"testResults\"[\\s\\S]*\\}/);\r\n\r\n if (!jsonMatch) {\r\n return parseVitestTextOutput(output, false);\r\n }\r\n\r\n try {\r\n const data = JSON.parse(jsonMatch[0]);\r\n const suites: TestSuiteResult[] = [];\r\n\r\n // vitest JSON reporter 格式\r\n if (data.testResults && Array.isArray(data.testResults)) {\r\n for (const fileResult of data.testResults) {\r\n const suite: TestSuiteResult = {\r\n name: path.basename(fileResult.name || fileResult.assertionResults?.[0]?.ancestorTitles?.[0] || 'unknown'),\r\n file: fileResult.name || 'unknown',\r\n type: 'unit' as TestType,\r\n status: mapVitestStatus(fileResult.status),\r\n duration: fileResult.duration || 0,\r\n tests: (fileResult.assertionResults || []).map((assertion: Record<string, unknown>) => ({\r\n name: assertion.title || assertion.fullName || 'unknown',\r\n file: fileResult.name || 'unknown',\r\n status: mapVitestStatus(assertion.status as string),\r\n duration: (assertion.duration as number) || 0,\r\n error: (assertion.failureMessages as string[] | undefined)?.length\r\n ? { message: ((assertion.failureMessages as string[])[0]) }\r\n : undefined,\r\n retries: 0,\r\n }) as TestCaseResult),\r\n };\r\n suites.push(suite);\r\n }\r\n }\r\n\r\n // 解析覆盖率\r\n let coverage: CoverageResult | undefined;\r\n if (data.coverageMap) {\r\n coverage = extractCoverage(data.coverageMap);\r\n }\r\n\r\n const success = data.success !== false && suites.every((s) => s.status !== 'failed');\r\n\r\n return { success, suites, coverage };\r\n } catch {\r\n return parseVitestTextOutput(output, false);\r\n }\r\n}\r\n\r\n/**\r\n * 解析 vitest 文本输出(JSON 解析失败时的回退方案)\r\n */\r\nfunction parseVitestTextOutput(output: string, hasError: boolean): {\r\n success: boolean;\r\n suites: TestSuiteResult[];\r\n} {\r\n const suites: TestSuiteResult[] = [];\r\n let totalPassed = 0;\r\n let totalFailed = 0;\r\n\r\n // 匹配 vitest 文本输出格式\r\n // ✓ tests/unit/example.test.ts (2 tests)\r\n const suiteRegex = /[✓✗×]\\s+(.+\\.test\\.ts|.+\\.spec\\.ts)\\s*\\((\\d+)\\s+test/i;\r\n const lines = output.split('\\n');\r\n\r\n for (const line of lines) {\r\n const match = line.match(suiteRegex);\r\n if (match) {\r\n const file = match[1];\r\n const testCount = parseInt(match[2], 10);\r\n const isPassed = line.includes('✓');\r\n\r\n if (isPassed) totalPassed += testCount;\r\n else totalFailed += testCount;\r\n\r\n suites.push({\r\n name: path.basename(file),\r\n file,\r\n type: 'unit',\r\n status: isPassed ? 'passed' : 'failed',\r\n duration: 0,\r\n tests: Array.from({ length: testCount }, (_, i) => ({\r\n name: `test ${i + 1}`,\r\n file,\r\n status: isPassed ? ('passed' as TestStatus) : ('failed' as TestStatus),\r\n duration: 0,\r\n retries: 0,\r\n })),\r\n });\r\n }\r\n }\r\n\r\n return {\r\n success: !hasError || totalFailed === 0,\r\n suites,\r\n };\r\n}\r\n\r\n/**\r\n * 映射 vitest 状态到 QAT 状态\r\n */\r\nfunction mapVitestStatus(status: string): TestStatus {\r\n switch (status) {\r\n case 'passed':\r\n case 'pass':\r\n return 'passed';\r\n case 'failed':\r\n case 'fail':\r\n return 'failed';\r\n case 'skipped':\r\n case 'skip':\r\n case 'pending':\r\n return 'skipped';\r\n default:\r\n return 'pending';\r\n }\r\n}\r\n\r\n/**\r\n * 从 vitest coverage map 提取汇总数据\r\n */\r\nfunction extractCoverage(coverageMap: Record<string, unknown>): CoverageResult {\r\n let totalStatements = 0;\r\n let coveredStatements = 0;\r\n let totalFunctions = 0;\r\n let coveredFunctions = 0;\r\n let totalBranches = 0;\r\n let coveredBranches = 0;\r\n let totalLines = 0;\r\n let coveredLines = 0;\r\n\r\n for (const fileCov of Object.values(coverageMap) as Record<string, unknown>[]) {\r\n const s = fileCov.s as Record<string, number> || {};\r\n const f = fileCov.f as Record<string, number> || {};\r\n const b = fileCov.b as Record<string, Record<string, number>> || {};\r\n\r\n for (const count of Object.values(s)) {\r\n totalLines++;\r\n if (count > 0) coveredLines++;\r\n }\r\n\r\n for (const count of Object.values(f)) {\r\n totalFunctions++;\r\n if (count > 0) coveredFunctions++;\r\n }\r\n\r\n for (const branch of Object.values(b)) {\r\n for (const count of Object.values(branch)) {\r\n totalBranches++;\r\n if (count > 0) coveredBranches++;\r\n }\r\n }\r\n }\r\n\r\n totalStatements = totalLines;\r\n coveredStatements = coveredLines;\r\n\r\n return {\r\n lines: totalLines > 0 ? coveredLines / totalLines : 0,\r\n statements: totalStatements > 0 ? coveredStatements / totalStatements : 0,\r\n functions: totalFunctions > 0 ? coveredFunctions / totalFunctions : 0,\r\n branches: totalBranches > 0 ? coveredBranches / totalBranches : 0,\r\n };\r\n}\r\n","/**\r\n * Playwright 运行器 - 通过子进程调用Playwright,解析输出收集结果\r\n */\r\n\r\nimport { execFile } from 'node:child_process';\r\nimport path from 'node:path';\r\nimport type { TestRunResult, TestSuiteResult, TestCaseResult, TestStatus, TestType } from '../types/index.js';\r\n\r\nexport interface PlaywrightRunnerOptions {\r\n type: TestType;\r\n files?: string[];\r\n browsers?: ('chromium' | 'firefox' | 'webkit')[];\r\n headed?: boolean;\r\n configPath?: string;\r\n /** 额外传递给 playwright 的参数 */\r\n extraArgs?: string[];\r\n}\r\n\r\n/**\r\n * 执行Playwright测试\r\n */\r\nexport async function runPlaywright(options: PlaywrightRunnerOptions): Promise<TestRunResult> {\r\n const startTime = Date.now();\r\n\r\n const args = buildPlaywrightArgs(options);\r\n\r\n try {\r\n const result = await execPlaywright(args);\r\n const endTime = Date.now();\r\n\r\n return {\r\n type: options.type,\r\n status: result.success ? 'passed' : 'failed',\r\n startTime,\r\n endTime,\r\n duration: endTime - startTime,\r\n suites: result.suites,\r\n };\r\n } catch (error) {\r\n const endTime = Date.now();\r\n return {\r\n type: options.type,\r\n status: 'failed',\r\n startTime,\r\n endTime,\r\n duration: endTime - startTime,\r\n suites: [{\r\n name: 'Playwright Runner',\r\n file: options.files?.[0] || 'unknown',\r\n type: options.type,\r\n status: 'failed',\r\n duration: endTime - startTime,\r\n tests: [{\r\n name: 'Runner Error',\r\n file: options.files?.[0] || 'unknown',\r\n status: 'failed',\r\n duration: 0,\r\n error: {\r\n message: error instanceof Error ? error.message : String(error),\r\n },\r\n retries: 0,\r\n }],\r\n }],\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * 构建playwright命令参数\r\n */\r\nfunction buildPlaywrightArgs(options: PlaywrightRunnerOptions): string[] {\r\n const args = ['playwright', 'test', '--reporter=json'];\r\n\r\n // 指定测试文件或目录\r\n if (options.files && options.files.length > 0) {\r\n args.push(...options.files);\r\n } else {\r\n // 根据测试类型确定测试目录\r\n const dirMap: Record<string, string> = {\r\n e2e: 'tests/e2e',\r\n visual: 'tests/visual',\r\n performance: 'tests/e2e',\r\n };\r\n const testDir = dirMap[options.type];\r\n if (testDir) {\r\n args.push(testDir);\r\n }\r\n }\r\n\r\n // 指定浏览器\r\n if (options.browsers && options.browsers.length > 0) {\r\n for (const browser of options.browsers) {\r\n args.push('--project', browser);\r\n }\r\n }\r\n\r\n // 有头模式\r\n if (options.headed) {\r\n args.push('--headed');\r\n }\r\n\r\n // 配置文件\r\n if (options.configPath) {\r\n args.push('--config', options.configPath);\r\n }\r\n\r\n // 额外参数\r\n if (options.extraArgs) {\r\n args.push(...options.extraArgs);\r\n }\r\n\r\n return args;\r\n}\r\n\r\n/**\r\n * 执行playwright命令并解析JSON输出\r\n */\r\nasync function execPlaywright(args: string[]): Promise<{\r\n success: boolean;\r\n suites: TestSuiteResult[];\r\n}> {\r\n return new Promise((resolve, reject) => {\r\n const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx';\r\n\r\n const child = execFile(npx, args, {\r\n cwd: process.cwd(),\r\n env: { ...process.env, FORCE_COLOR: '0', PLAYWRIGHT_JSON_OUTPUT_DIR: '' },\r\n maxBuffer: 50 * 1024 * 1024,\r\n shell: true,\r\n }, (error, stdout, stderr) => {\r\n const output = stdout || stderr || '';\r\n\r\n try {\r\n const parsed = parsePlaywrightJSONOutput(output);\r\n resolve(parsed);\r\n } catch {\r\n if (output) {\r\n resolve(parsePlaywrightTextOutput(output, !!error));\r\n } else if (error && error.message.includes('ENOENT')) {\r\n reject(new Error('未找到 playwright,请确保已安装: npm install -D @playwright/test'));\r\n } else {\r\n resolve({ success: !error, suites: [] });\r\n }\r\n }\r\n });\r\n\r\n child.on('error', (err) => {\r\n reject(new Error(`Playwright 执行失败: ${err.message}`));\r\n });\r\n });\r\n}\r\n\r\n/**\r\n * 解析 Playwright JSON 输出\r\n */\r\nfunction parsePlaywrightJSONOutput(output: string): {\r\n success: boolean;\r\n suites: TestSuiteResult[];\r\n} {\r\n // Playwright JSON reporter 输出格式\r\n // 尝试提取 JSON\r\n const jsonMatch = output.match(/\\{[\\s\\S]*\"suites\"[\\s\\S]*\\}/);\r\n\r\n if (!jsonMatch) {\r\n return parsePlaywrightTextOutput(output, false);\r\n }\r\n\r\n try {\r\n const data = JSON.parse(jsonMatch[0]);\r\n const suites: TestSuiteResult[] = [];\r\n\r\n // Playwright JSON reporter 格式\r\n if (data.suites && Array.isArray(data.suites)) {\r\n for (const suiteData of data.suites) {\r\n extractPlaywrightSuites(suiteData, suites);\r\n }\r\n }\r\n\r\n const success = data.stats?.status === 'passed' || suites.every((s) => s.status !== 'failed');\r\n\r\n return { success, suites };\r\n } catch {\r\n return parsePlaywrightTextOutput(output, false);\r\n }\r\n}\r\n\r\n/**\r\n * 递归提取 Playwright 套件\r\n */\r\nfunction extractPlaywrightSuites(\r\n suiteData: Record<string, unknown>,\r\n result: TestSuiteResult[],\r\n parentPath = '',\r\n): void {\r\n const suiteName = (suiteData.title as string) || 'unknown';\r\n const suitePath = parentPath ? `${parentPath} > ${suiteName}` : suiteName;\r\n\r\n // 如果有 specs,这是一个叶子套件\r\n if (suiteData.specs && Array.isArray(suiteData.specs)) {\r\n const tests: TestCaseResult[] = (suiteData.specs as Record<string, unknown>[]).map((spec) => {\r\n const specTitle = (spec.title as string) || 'unknown';\r\n const specFile = (spec.file as string) || 'unknown';\r\n // 从 tests 数组获取状态\r\n const specTests = spec.tests as Record<string, unknown>[] | undefined;\r\n let status: TestStatus = 'pending';\r\n let duration = 0;\r\n let error: { message: string; stack?: string } | undefined;\r\n\r\n if (specTests && specTests.length > 0) {\r\n const lastRun = specTests[specTests.length - 1] as Record<string, unknown>;\r\n const results = lastRun.results as Record<string, unknown>[] | undefined;\r\n if (results && results.length > 0) {\r\n const lastResult = results[results.length - 1] as Record<string, unknown>;\r\n status = mapPlaywrightStatus(lastResult.status as string);\r\n duration = (lastResult.duration as number) || 0;\r\n if (lastResult.error) {\r\n const err = lastResult.error as Record<string, unknown>;\r\n error = {\r\n message: (err.message as string) || '',\r\n stack: err.stack as string | undefined,\r\n };\r\n }\r\n }\r\n }\r\n\r\n return {\r\n name: specTitle,\r\n file: specFile,\r\n status,\r\n duration,\r\n error,\r\n retries: 0,\r\n };\r\n });\r\n\r\n const suiteStatus = tests.some((t) => t.status === 'failed')\r\n ? 'failed'\r\n : tests.every((t) => t.status === 'skipped')\r\n ? 'skipped'\r\n : 'passed';\r\n\r\n result.push({\r\n name: suiteName,\r\n file: ((suiteData.specs as Record<string, unknown>[])[0]?.file as string) || 'unknown',\r\n type: 'e2e',\r\n status: suiteStatus,\r\n duration: tests.reduce((sum, t) => sum + t.duration, 0),\r\n tests,\r\n });\r\n }\r\n\r\n // 递归处理子套件\r\n if (suiteData.suites && Array.isArray(suiteData.suites)) {\r\n for (const child of suiteData.suites as Record<string, unknown>[]) {\r\n extractPlaywrightSuites(child, result, suitePath);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * 解析 Playwright 文本输出\r\n */\r\nfunction parsePlaywrightTextOutput(output: string, hasError: boolean): {\r\n success: boolean;\r\n suites: TestSuiteResult[];\r\n} {\r\n const suites: TestSuiteResult[] = [];\r\n let totalPassed = 0;\r\n let totalFailed = 0;\r\n let totalSkipped = 0;\r\n\r\n // 匹配 Playwright 文本输出\r\n // ✓ 1 test.ts:3:1 › login page › should display login form (2s)\r\n // ✘ 2 test.ts:10:1 › login page › should submit form (1s)\r\n // - 3 test.ts:20:1 › login page › should show error (skipped)\r\n const testRegex = /^\\s*([✓✘×\\-])\\s+\\d+\\s+(.+\\.test\\.ts|.+\\.spec\\.ts)\\s*:\\d+:\\d+\\s*›\\s*(.+?)\\s*\\((\\d+)ms?\\)/;\r\n\r\n for (const line of output.split('\\n')) {\r\n const match = line.match(testRegex);\r\n if (match) {\r\n const symbol = match[1];\r\n const file = match[2];\r\n const testName = match[3];\r\n const duration = parseInt(match[4], 10);\r\n\r\n let status: TestStatus;\r\n if (symbol === '✓') {\r\n status = 'passed';\r\n totalPassed++;\r\n } else if (symbol === '✘' || symbol === '×') {\r\n status = 'failed';\r\n totalFailed++;\r\n } else {\r\n status = 'skipped';\r\n totalSkipped++;\r\n }\r\n\r\n // 按文件分组\r\n let existingSuite = suites.find((s) => s.file === file);\r\n if (!existingSuite) {\r\n existingSuite = {\r\n name: path.basename(file),\r\n file,\r\n type: 'e2e',\r\n status: 'passed',\r\n duration: 0,\r\n tests: [],\r\n };\r\n suites.push(existingSuite);\r\n }\r\n\r\n existingSuite.tests.push({\r\n name: testName,\r\n file,\r\n status,\r\n duration,\r\n retries: 0,\r\n });\r\n\r\n if (status === 'failed') {\r\n existingSuite.status = 'failed';\r\n }\r\n }\r\n }\r\n\r\n // 更新套件状态和持续时间\r\n for (const suite of suites) {\r\n if (suite.tests.some((t) => t.status === 'failed')) {\r\n suite.status = 'failed';\r\n }\r\n suite.duration = suite.tests.reduce((sum, t) => sum + t.duration, 0);\r\n }\r\n\r\n // 匹配最终汇总\r\n // 3 passed, 1 failed, 1 skipped\r\n const summaryRegex = /(\\d+)\\s+passed.*?(\\d+)\\s+failed.*?(\\d+)\\s+skipped/i;\r\n const summaryMatch = output.match(summaryRegex);\r\n if (summaryMatch && suites.length === 0) {\r\n // 从汇总推断\r\n totalPassed = parseInt(summaryMatch[1], 10);\r\n totalFailed = parseInt(summaryMatch[2], 10);\r\n totalSkipped = parseInt(summaryMatch[3], 10);\r\n }\r\n\r\n return {\r\n success: !hasError || totalFailed === 0,\r\n suites,\r\n };\r\n}\r\n\r\n/**\r\n * 映射 Playwright 状态\r\n */\r\nfunction mapPlaywrightStatus(status: string): TestStatus {\r\n switch (status) {\r\n case 'passed':\r\n case 'pass':\r\n return 'passed';\r\n case 'failed':\r\n case 'fail':\r\n case 'timedOut':\r\n case 'interrupted':\r\n return 'failed';\r\n case 'skipped':\r\n case 'skip':\r\n return 'skipped';\r\n default:\r\n return 'pending';\r\n }\r\n}\r\n","/**\r\n * Lighthouse 运行器 - 通过子进程调用Lighthouse,提取性能指标\r\n */\r\n\r\nimport { execFile } from 'node:child_process';\r\nimport type { TestRunResult, TestSuiteResult, TestCaseResult, TestStatus, PerformanceMetrics } from '../types/index.js';\r\n\r\nexport interface LighthouseRunnerOptions {\r\n urls: string[];\r\n runs?: number;\r\n thresholds?: Partial<Record<'performance' | 'accessibility' | 'best-practices' | 'seo', number>>;\r\n /** 额外传递给 lighthouse 的参数 */\r\n extraArgs?: string[];\r\n}\r\n\r\n/**\r\n * 执行Lighthouse性能测试\r\n */\r\nexport async function runLighthouse(options: LighthouseRunnerOptions): Promise<TestRunResult> {\r\n const startTime = Date.now();\r\n const runs = options.runs || 3;\r\n const urls = options.urls;\r\n\r\n if (urls.length === 0) {\r\n return {\r\n type: 'performance',\r\n status: 'skipped',\r\n startTime,\r\n endTime: Date.now(),\r\n duration: 0,\r\n suites: [],\r\n };\r\n }\r\n\r\n const allResults: LighthouseRunResult[] = [];\r\n\r\n for (const url of urls) {\r\n try {\r\n const urlResults: LighthouseRunResult[] = [];\r\n\r\n for (let i = 0; i < runs; i++) {\r\n const result = await runLighthouseOnce(url, options.extraArgs);\r\n urlResults.push(result);\r\n }\r\n\r\n // 取中位数\r\n const median = getMedianResult(urlResults);\r\n allResults.push(median);\r\n } catch (error) {\r\n allResults.push({\r\n url,\r\n error: error instanceof Error ? error.message : String(error),\r\n scores: { performance: 0, accessibility: 0, bestPractices: 0, seo: 0 },\r\n metrics: {},\r\n });\r\n }\r\n }\r\n\r\n const endTime = Date.now();\r\n\r\n // 构建测试结果\r\n const suites: TestSuiteResult[] = allResults.map((result) => {\r\n const tests: TestCaseResult[] = [\r\n makeTestResult('Performance Score', result.scores.performance, options.thresholds?.performance),\r\n makeTestResult('Accessibility Score', result.scores.accessibility, options.thresholds?.accessibility),\r\n makeTestResult('Best Practices Score', result.scores.bestPractices, options.thresholds?.['best-practices']),\r\n makeTestResult('SEO Score', result.scores.seo, options.thresholds?.seo),\r\n ];\r\n\r\n // 添加核心 Web 指标测试\r\n if (result.metrics.lcp !== undefined) {\r\n tests.push(makeTestResult('Largest Contentful Paint', result.metrics.lcp, 2500, true));\r\n }\r\n if (result.metrics.fid !== undefined) {\r\n tests.push(makeTestResult('First Input Delay', result.metrics.fid, 100, true));\r\n }\r\n if (result.metrics.cls !== undefined) {\r\n tests.push(makeTestResult('Cumulative Layout Shift', result.metrics.cls, 0.1, true));\r\n }\r\n\r\n const suiteStatus: TestStatus = result.error\r\n ? 'failed'\r\n : tests.some((t) => t.status === 'failed')\r\n ? 'failed'\r\n : 'passed';\r\n\r\n return {\r\n name: `Performance: ${result.url}`,\r\n file: result.url,\r\n type: 'performance',\r\n status: suiteStatus,\r\n duration: endTime - startTime,\r\n tests,\r\n };\r\n });\r\n\r\n // 计算平均指标\r\n const avgMetrics = calculateAverageMetrics(allResults);\r\n\r\n const overallStatus = suites.some((s) => s.status === 'failed') ? 'failed' : 'passed';\r\n\r\n return {\r\n type: 'performance',\r\n status: overallStatus,\r\n startTime,\r\n endTime,\r\n duration: endTime - startTime,\r\n suites,\r\n performance: avgMetrics,\r\n };\r\n}\r\n\r\ninterface LighthouseRunResult {\r\n url: string;\r\n error?: string;\r\n scores: {\r\n performance: number;\r\n accessibility: number;\r\n bestPractices: number;\r\n seo: number;\r\n };\r\n metrics: {\r\n lcp?: number;\r\n fid?: number;\r\n cls?: number;\r\n };\r\n}\r\n\r\n/**\r\n * 单次 Lighthouse 运行\r\n */\r\nasync function runLighthouseOnce(url: string, extraArgs?: string[]): Promise<LighthouseRunResult> {\r\n const args = [\r\n 'lighthouse', url,\r\n '--output=json',\r\n '--quiet',\r\n '--chrome-flags=--headless --no-sandbox',\r\n '--only-categories=performance,accessibility,best-practices,seo',\r\n ];\r\n\r\n if (extraArgs) {\r\n args.push(...extraArgs);\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx';\r\n\r\n execFile(npx, args, {\r\n cwd: process.cwd(),\r\n maxBuffer: 50 * 1024 * 1024,\r\n shell: true,\r\n }, (error, stdout) => {\r\n if (error && !stdout) {\r\n reject(new Error(`Lighthouse 执行失败: ${error.message}`));\r\n return;\r\n }\r\n\r\n try {\r\n const data = JSON.parse(stdout);\r\n const categories = data.categories || {};\r\n const audits = data.audits || {};\r\n\r\n resolve({\r\n url,\r\n scores: {\r\n performance: Math.round((categories.performance?.score || 0) * 100),\r\n accessibility: Math.round((categories.accessibility?.score || 0) * 100),\r\n bestPractices: Math.round((categories['best-practices']?.score || 0) * 100),\r\n seo: Math.round((categories.seo?.score || 0) * 100),\r\n },\r\n metrics: {\r\n lcp: audits['largest-contentful-paint']?.numericValue,\r\n fid: audits['max-potential-fid']?.numericValue,\r\n cls: audits['cumulative-layout-shift']?.numericValue,\r\n },\r\n });\r\n } catch {\r\n reject(new Error('Lighthouse 输出解析失败'));\r\n }\r\n });\r\n });\r\n}\r\n\r\n/**\r\n * 获取中位数结果\r\n */\r\nfunction getMedianResult(results: LighthouseRunResult[]): LighthouseRunResult {\r\n if (results.length === 1) return results[0];\r\n\r\n // 按 performance score 排序取中位数\r\n const sorted = [...results].sort(\r\n (a, b) => a.scores.performance - b.scores.performance,\r\n );\r\n const mid = Math.floor(sorted.length / 2);\r\n return sorted[mid];\r\n}\r\n\r\n/**\r\n * 创建测试结果\r\n */\r\nfunction makeTestResult(\r\n name: string,\r\n value: number,\r\n threshold?: number,\r\n isLowerBetter = false,\r\n): TestCaseResult {\r\n let status: TestStatus = 'passed';\r\n let error: { message: string } | undefined;\r\n\r\n if (threshold !== undefined) {\r\n const passed = isLowerBetter ? value <= threshold : value >= threshold;\r\n if (!passed) {\r\n status = 'failed';\r\n error = {\r\n message: `${name}: ${isLowerBetter ? `${value}ms` : value} (${isLowerBetter ? '>' : '<'} threshold ${isLowerBetter ? `${threshold}ms` : threshold})`,\r\n };\r\n }\r\n }\r\n\r\n return {\r\n name,\r\n file: 'lighthouse',\r\n status,\r\n duration: 0,\r\n error,\r\n retries: 0,\r\n };\r\n}\r\n\r\n/**\r\n * 计算平均分数\r\n */\r\nfunction calculateAverageScores(results: LighthouseRunResult[]): PerformanceMetrics {\r\n const valid = results.filter((r) => !r.error);\r\n if (valid.length === 0) {\r\n return { performance: 0, accessibility: 0, bestPractices: 0, seo: 0 };\r\n }\r\n\r\n return {\r\n performance: Math.round(valid.reduce((s, r) => s + r.scores.performance, 0) / valid.length),\r\n accessibility: Math.round(valid.reduce((s, r) => s + r.scores.accessibility, 0) / valid.length),\r\n bestPractices: Math.round(valid.reduce((s, r) => s + r.scores.bestPractices, 0) / valid.length),\r\n seo: Math.round(valid.reduce((s, r) => s + r.scores.seo, 0) / valid.length),\r\n };\r\n}\r\n\r\n/**\r\n * 计算平均指标\r\n */\r\nfunction calculateAverageMetrics(results: LighthouseRunResult[]): PerformanceMetrics {\r\n const valid = results.filter((r) => !r.error);\r\n const scores = calculateAverageScores(results);\r\n\r\n const lcpValues = valid.map((r) => r.metrics.lcp).filter((v): v is number => v !== undefined);\r\n const fidValues = valid.map((r) => r.metrics.fid).filter((v): v is number => v !== undefined);\r\n const clsValues = valid.map((r) => r.metrics.cls).filter((v): v is number => v !== undefined);\r\n\r\n return {\r\n ...scores,\r\n lcp: lcpValues.length > 0 ? lcpValues.reduce((s, v) => s + v, 0) / lcpValues.length : undefined,\r\n fid: fidValues.length > 0 ? fidValues.reduce((s, v) => s + v, 0) / fidValues.length : undefined,\r\n cls: clsValues.length > 0 ? clsValues.reduce((s, v) => s + v, 0) / clsValues.length : undefined,\r\n };\r\n}\r\n","/**\r\n * mock 命令 - 启停Mock服务器,管理API路由\r\n */\r\n\r\nimport { Command } from 'commander';\r\nimport chalk from 'chalk';\r\nimport ora from 'ora';\r\nimport type { MockOptions } from '../types/index.js';\r\nimport { loadConfig } from '../services/config.js';\r\nimport {\r\n startMockServer,\r\n stopMockServer,\r\n getMockServerState,\r\n loadMockRoutes,\r\n} from '../services/mock-server.js';\r\n\r\nexport function registerMockCommand(program: Command): void {\r\n program\r\n .command('mock')\r\n .description('Mock服务管理 - 启动/停止Mock API服务器')\r\n .argument('<action>', '操作类型 (start|stop|status)')\r\n .option('-p, --port <port>', '指定端口号', '3456')\r\n .action(async (action: string, options: { port?: string; config?: string }) => {\r\n try {\r\n await executeMock({\r\n action: action as MockOptions['action'],\r\n port: options.port ? parseInt(options.port, 10) : undefined,\r\n config: options.config,\r\n });\r\n } catch (error) {\r\n console.error(chalk.red(`\\n ✗ ${error instanceof Error ? error.message : String(error)}\\n`));\r\n process.exit(1);\r\n }\r\n });\r\n}\r\n\r\nasync function executeMock(\r\n options: { action: MockOptions['action']; port?: number; config?: string },\r\n): Promise<void> {\r\n const config = await loadConfig(options.config);\r\n const port = options.port || config.mock.port;\r\n\r\n switch (options.action) {\r\n case 'start': {\r\n await startMock(port, config.mock.routesDir);\r\n break;\r\n }\r\n case 'stop': {\r\n await stopMock();\r\n break;\r\n }\r\n case 'status': {\r\n showStatus();\r\n break;\r\n }\r\n default: {\r\n console.error(chalk.red(`\\n 未知操作: ${options.action}`));\r\n console.log(chalk.gray(' 可用操作: start, stop, status\\n'));\r\n process.exit(1);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * 启动 Mock 服务器\r\n */\r\nasync function startMock(port: number, routesDir: string): Promise<void> {\r\n const state = getMockServerState();\r\n\r\n if (state.running) {\r\n console.log(chalk.yellow(`\\n Mock服务器已在运行 (端口: ${state.port})\\n`));\r\n return;\r\n }\r\n\r\n const spinner = ora(`正在启动Mock服务器 (端口: ${port})...`).start();\r\n\r\n try {\r\n // 加载路由\r\n const routes = await loadMockRoutes(routesDir);\r\n if (routes.length > 0) {\r\n spinner.text = `正在启动Mock服务器 (加载了 ${routes.length} 个路由)...`;\r\n }\r\n\r\n await startMockServer(port, routes);\r\n\r\n spinner.succeed(`Mock服务器已启动`);\r\n\r\n console.log();\r\n console.log(chalk.white(' 地址:'), chalk.cyan(`http://localhost:${port}`));\r\n console.log(chalk.white(' 路由:'), routes.length > 0 ? `${routes.length} 个` : chalk.gray('使用默认路由'));\r\n console.log(chalk.white(' 健康检查:'), chalk.cyan(`http://localhost:${port}/api/health`));\r\n\r\n if (routes.length > 0) {\r\n console.log();\r\n console.log(chalk.white(' 已注册路由:'));\r\n for (const route of routes.slice(0, 10)) {\r\n const method = chalk.gray(route.method.padEnd(6));\r\n console.log(` ${method} ${route.path}`);\r\n }\r\n if (routes.length > 10) {\r\n console.log(chalk.gray(` ... 还有 ${routes.length - 10} 个路由`));\r\n }\r\n }\r\n\r\n console.log();\r\n console.log(chalk.gray(' 按 Ctrl+C 停止服务器'));\r\n\r\n // 保持进程运行\r\n process.on('SIGINT', async () => {\r\n const stopSpinner = ora('正在停止Mock服务器...').start();\r\n await stopMockServer();\r\n stopSpinner.succeed('Mock服务器已停止');\r\n process.exit(0);\r\n });\r\n\r\n // 防止进程退出\r\n await new Promise(() => {});\r\n } catch (error) {\r\n spinner.fail('Mock服务器启动失败');\r\n throw error;\r\n }\r\n}\r\n\r\n/**\r\n * 停止 Mock 服务器\r\n */\r\nasync function stopMock(): Promise<void> {\r\n const state = getMockServerState();\r\n\r\n if (!state.running) {\r\n console.log(chalk.yellow('\\n Mock服务器未在运行\\n'));\r\n return;\r\n }\r\n\r\n const spinner = ora('正在停止Mock服务器...').start();\r\n\r\n try {\r\n await stopMockServer();\r\n spinner.succeed('Mock服务器已停止');\r\n console.log();\r\n } catch (error) {\r\n spinner.fail('Mock服务器停止失败');\r\n throw error;\r\n }\r\n}\r\n\r\n/**\r\n * 显示 Mock 服务器状态\r\n */\r\nfunction showStatus(): void {\r\n const state = getMockServerState();\r\n\r\n console.log();\r\n if (state.running) {\r\n console.log(chalk.green(' ● Mock服务器运行中'));\r\n console.log(chalk.white(' 端口:'), state.port);\r\n console.log(chalk.white(' 路由:'), state.routes.length);\r\n } else {\r\n console.log(chalk.gray(' ○ Mock服务器未运行'));\r\n console.log(chalk.gray(' 使用 qat mock start 启动'));\r\n }\r\n console.log();\r\n}\r\n","/**\r\n * report 命令 - 聚合测试结果,生成HTML报告,支持历史记录\r\n */\r\n\r\nimport { Command } from 'commander';\r\nimport chalk from 'chalk';\r\nimport ora from 'ora';\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport type { ReportOptions, TestRunResult } from '../types/index.js';\r\nimport { loadConfig } from '../services/config.js';\r\nimport { aggregateResults, generateHTMLReport, writeReportToDisk, type ReportData } from '../services/reporter.js';\r\n\r\n/** 测试结果存储目录 */\r\nconst RESULTS_DIR = '.qat-results';\r\n\r\nexport function registerReportCommand(program: Command): void {\r\n program\r\n .command('report')\r\n .description('生成测试报告 - 聚合所有测试结果并输出HTML')\r\n .option('-o, --output <dir>', '报告输出目录')\r\n .option('--open', '生成后自动打开报告', false)\r\n .action(async (options: ReportOptions) => {\r\n try {\r\n await executeReport(options);\r\n } catch (error) {\r\n console.error(chalk.red(`\\n ✗ ${error instanceof Error ? error.message : String(error)}\\n`));\r\n process.exit(1);\r\n }\r\n });\r\n}\r\n\r\nasync function executeReport(options: ReportOptions): Promise<void> {\r\n const config = await loadConfig(options.config);\r\n const outputDir = options.output || config.report.outputDir;\r\n\r\n const spinner = ora('正在收集测试结果...').start();\r\n\r\n // 1. 收集测试结果\r\n const results = collectResults();\r\n\r\n if (results.length === 0) {\r\n spinner.info('没有找到测试结果');\r\n console.log(chalk.gray('\\n 提示: 先运行 qat run 生成测试结果\\n'));\r\n return;\r\n }\r\n\r\n spinner.text = '正在生成HTML报告...';\r\n\r\n // 2. 聚合结果\r\n const reportData = aggregateResults(results);\r\n\r\n // 3. 写入报告\r\n const reportPath = writeReportToDisk(reportData, outputDir);\r\n\r\n // 4. 保存本次结果到历史记录\r\n saveResultToHistory(reportData);\r\n\r\n spinner.succeed('测试报告已生成');\r\n\r\n // 5. 显示结果\r\n displayReportResult(reportPath, reportData);\r\n\r\n // 6. 自动打开浏览器\r\n if (options.open || config.report.open) {\r\n await openReport(reportPath);\r\n }\r\n}\r\n\r\n/**\r\n * 收集测试结果\r\n */\r\nfunction collectResults(): TestRunResult[] {\r\n const results: TestRunResult[] = [];\r\n const resultsPath = path.join(process.cwd(), RESULTS_DIR);\r\n\r\n if (!fs.existsSync(resultsPath)) {\r\n return results;\r\n }\r\n\r\n const files = fs.readdirSync(resultsPath)\r\n .filter((f) => f.endsWith('.json'))\r\n .sort()\r\n .reverse(); // 最新的在前\r\n\r\n // 取最新一次运行的结果\r\n if (files.length > 0) {\r\n const latestFile = path.join(resultsPath, files[0]);\r\n try {\r\n const data = JSON.parse(fs.readFileSync(latestFile, 'utf-8'));\r\n if (Array.isArray(data)) {\r\n results.push(...data);\r\n } else if (data.results) {\r\n results.push(...data.results);\r\n }\r\n } catch {\r\n // 解析失败跳过\r\n }\r\n }\r\n\r\n return results;\r\n}\r\n\r\n/**\r\n * 保存结果到历史记录\r\n */\r\nfunction saveResultToHistory(reportData: ReportData): void {\r\n const resultsPath = path.join(process.cwd(), RESULTS_DIR);\r\n\r\n if (!fs.existsSync(resultsPath)) {\r\n fs.mkdirSync(resultsPath, { recursive: true });\r\n }\r\n\r\n const timestamp = new Date(reportData.timestamp).toISOString().replace(/[:.]/g, '-');\r\n const fileName = `result-${timestamp}.json`;\r\n const filePath = path.join(resultsPath, fileName);\r\n\r\n fs.writeFileSync(filePath, JSON.stringify(reportData, null, 2), 'utf-8');\r\n\r\n // 清理旧结果(保留最近 20 次)\r\n const files = fs.readdirSync(resultsPath)\r\n .filter((f) => f.startsWith('result-') && f.endsWith('.json'))\r\n .sort();\r\n\r\n while (files.length > 20) {\r\n const oldest = files.shift()!;\r\n fs.unlinkSync(path.join(resultsPath, oldest));\r\n }\r\n}\r\n\r\n/**\r\n * 显示报告生成结果\r\n */\r\nfunction displayReportResult(reportPath: string, data: ReportData): void {\r\n const relativePath = path.relative(process.cwd(), reportPath);\r\n\r\n console.log();\r\n console.log(chalk.green(' ✓ 测试报告已生成'));\r\n console.log();\r\n console.log(chalk.white(' 报告路径:'), chalk.cyan(relativePath));\r\n console.log(chalk.white(' 测试用例:'), `${data.summary.total} total`);\r\n console.log(chalk.white(' 通过:'), chalk.green(String(data.summary.passed)));\r\n if (data.summary.failed > 0) {\r\n console.log(chalk.white(' 失败:'), chalk.red(String(data.summary.failed)));\r\n }\r\n if (data.summary.skipped > 0) {\r\n console.log(chalk.white(' 跳过:'), chalk.yellow(String(data.summary.skipped)));\r\n }\r\n\r\n // 按类型统计\r\n if (Object.keys(data.byType).length > 0) {\r\n console.log();\r\n console.log(chalk.white(' 按类型:'));\r\n for (const [type, stats] of Object.entries(data.byType)) {\r\n const rate = stats.total > 0 ? ((stats.passed / stats.total) * 100).toFixed(0) : '0';\r\n const icon = stats.failed > 0 ? chalk.red('✗') : chalk.green('✓');\r\n console.log(` ${icon} ${type}: ${stats.passed}/${stats.total} (${rate}%)`);\r\n }\r\n }\r\n\r\n console.log();\r\n}\r\n\r\n/**\r\n * 自动打开报告\r\n */\r\nasync function openReport(reportPath: string): Promise<void> {\r\n const { exec } = await import('node:child_process');\r\n const platform = process.platform;\r\n\r\n let command: string;\r\n if (platform === 'win32') {\r\n command = `start \"\" \"${reportPath}\"`;\r\n } else if (platform === 'darwin') {\r\n command = `open \"${reportPath}\"`;\r\n } else {\r\n command = `xdg-open \"${reportPath}\"`;\r\n }\r\n\r\n exec(command, { shell: true }, (error) => {\r\n if (error) {\r\n console.log(chalk.gray(' 提示: 手动打开报告查看'));\r\n }\r\n });\r\n}\r\n","/**\r\n * 报告聚合服务 - 收集各运行器结果,生成完整HTML报告\r\n */\r\n\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport type { TestRunResult, TestSuiteResult, TestCaseResult } from '../types/index.js';\r\n\r\n/** 报告数据 */\r\nexport interface ReportData {\r\n timestamp: number;\r\n duration: number;\r\n results: TestRunResult[];\r\n summary: {\r\n total: number;\r\n passed: number;\r\n failed: number;\r\n skipped: number;\r\n pending: number;\r\n };\r\n /** 按测试类型分组统计 */\r\n byType: Record<string, { total: number; passed: number; failed: number; skipped: number }>;\r\n}\r\n\r\n/**\r\n * 聚合多个测试运行结果\r\n */\r\nexport function aggregateResults(results: TestRunResult[]): ReportData {\r\n const summary = {\r\n total: 0,\r\n passed: 0,\r\n failed: 0,\r\n skipped: 0,\r\n pending: 0,\r\n };\r\n\r\n const byType: Record<string, { total: number; passed: number; failed: number; skipped: number }> = {};\r\n\r\n for (const result of results) {\r\n const typeKey = result.type;\r\n if (!byType[typeKey]) {\r\n byType[typeKey] = { total: 0, passed: 0, failed: 0, skipped: 0 };\r\n }\r\n\r\n for (const suite of result.suites) {\r\n for (const test of suite.tests) {\r\n summary.total++;\r\n byType[typeKey].total++;\r\n\r\n if (test.status === 'passed') {\r\n summary.passed++;\r\n byType[typeKey].passed++;\r\n } else if (test.status === 'failed') {\r\n summary.failed++;\r\n byType[typeKey].failed++;\r\n } else if (test.status === 'skipped') {\r\n summary.skipped++;\r\n byType[typeKey].skipped++;\r\n } else {\r\n summary.pending++;\r\n }\r\n }\r\n }\r\n }\r\n\r\n const totalDuration = results.reduce((sum, r) => sum + r.duration, 0);\r\n\r\n return {\r\n timestamp: Date.now(),\r\n duration: totalDuration,\r\n results,\r\n summary,\r\n byType,\r\n };\r\n}\r\n\r\n/**\r\n * 格式化耗时\r\n */\r\nfunction formatDuration(ms: number): string {\r\n if (ms < 1000) return `${ms}ms`;\r\n if (ms < 60000) return `${(ms / 1000).toFixed(2)}s`;\r\n const minutes = Math.floor(ms / 60000);\r\n const seconds = ((ms % 60000) / 1000).toFixed(0);\r\n return `${minutes}m ${seconds}s`;\r\n}\r\n\r\n/**\r\n * 格式化时间戳\r\n */\r\nfunction formatTimestamp(ts: number): string {\r\n return new Date(ts).toLocaleString('zh-CN', {\r\n year: 'numeric',\r\n month: '2-digit',\r\n day: '2-digit',\r\n hour: '2-digit',\r\n minute: '2-digit',\r\n second: '2-digit',\r\n });\r\n}\r\n\r\n/**\r\n * 生成测试套件 HTML\r\n */\r\nfunction renderSuiteHTML(suite: TestSuiteResult): string {\r\n const statusClass = suite.status === 'passed' ? 'passed' : suite.status === 'failed' ? 'failed' : 'skipped';\r\n const testsHTML = suite.tests.map((test: TestCaseResult) => {\r\n const testStatusClass = test.status;\r\n const errorHTML = test.error\r\n ? `<div class=\"error-message\"><strong>${escapeHTML(test.error.message)}</strong>${\r\n test.error.stack ? `\\n${escapeHTML(test.error.stack)}` : ''\r\n }${\r\n test.error.expected && test.error.actual\r\n ? `\\n\\nExpected: ${escapeHTML(test.error.expected)}\\nActual: ${escapeHTML(test.error.actual)}`\r\n : ''\r\n }</div>`\r\n : '';\r\n return `<div class=\"test-item\">\r\n <span class=\"status-dot ${testStatusClass}\"></span>\r\n <span class=\"test-name\">${escapeHTML(test.name)}</span>\r\n <span class=\"duration\">${formatDuration(test.duration)}</span>\r\n ${test.retries > 0 ? `<span class=\"retries\">重试 ${test.retries} 次</span>` : ''}\r\n </div>\r\n ${errorHTML}`;\r\n }).join('');\r\n\r\n return `<div class=\"suite\">\r\n <div class=\"suite-header\" onclick=\"this.parentElement.classList.toggle('collapsed')\">\r\n <div>\r\n <span class=\"status-dot ${statusClass}\"></span>\r\n <strong>${escapeHTML(suite.name)}</strong>\r\n <span class=\"suite-file\">${escapeHTML(suite.file)}</span>\r\n </div>\r\n <div>\r\n <span class=\"duration\">${formatDuration(suite.duration)}</span>\r\n <span class=\"toggle-icon\">▼</span>\r\n </div>\r\n </div>\r\n <div class=\"suite-body\">${testsHTML}</div>\r\n </div>`;\r\n}\r\n\r\n/**\r\n * 转义 HTML 特殊字符\r\n */\r\nfunction escapeHTML(str: string): string {\r\n return str\r\n .replace(/&/g, '&amp;')\r\n .replace(/</g, '&lt;')\r\n .replace(/>/g, '&gt;')\r\n .replace(/\"/g, '&quot;')\r\n .replace(/'/g, '&#039;');\r\n}\r\n\r\n/**\r\n * 生成完整 HTML 报告\r\n */\r\nexport function generateHTMLReport(data: ReportData): string {\r\n const passRate = data.summary.total > 0\r\n ? ((data.summary.passed / data.summary.total) * 100).toFixed(1)\r\n : '0';\r\n\r\n const suitesHTML = data.results\r\n .flatMap((r) => r.suites)\r\n .map(renderSuiteHTML)\r\n .join('\\n');\r\n\r\n const byTypeHTML = Object.entries(data.byType)\r\n .map(([type, stats]) => {\r\n const rate = stats.total > 0 ? ((stats.passed / stats.total) * 100).toFixed(0) : '0';\r\n return `<div class=\"type-card\">\r\n <div class=\"type-name\">${type}</div>\r\n <div class=\"type-stats\">\r\n <span class=\"passed\">${stats.passed} 通过</span>\r\n <span class=\"failed\">${stats.failed} 失败</span>\r\n <span class=\"skipped\">${stats.skipped} 跳过</span>\r\n </div>\r\n <div class=\"type-rate\">${rate}%</div>\r\n </div>`;\r\n })\r\n .join('\\n');\r\n\r\n return `<!DOCTYPE html>\r\n<html lang=\"zh-CN\">\r\n<head>\r\n <meta charset=\"UTF-8\">\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n <title>QAT 测试报告 - ${formatTimestamp(data.timestamp)}</title>\r\n <style>\r\n :root {\r\n --color-passed: #22c55e;\r\n --color-failed: #ef4444;\r\n --color-skipped: #f59e0b;\r\n --color-info: #3b82f6;\r\n --bg-primary: #ffffff;\r\n --bg-secondary: #f8fafc;\r\n --text-primary: #1e293b;\r\n --text-secondary: #64748b;\r\n --border-color: #e2e8f0;\r\n }\r\n * { margin: 0; padding: 0; box-sizing: border-box; }\r\n body {\r\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\r\n background: var(--bg-secondary);\r\n color: var(--text-primary);\r\n line-height: 1.6;\r\n }\r\n .container { max-width: 1200px; margin: 0 auto; padding: 20px; }\r\n header {\r\n background: white;\r\n border-bottom: 1px solid var(--border-color);\r\n padding: 20px;\r\n margin-bottom: 20px;\r\n border-radius: 8px;\r\n }\r\n header h1 { font-size: 24px; font-weight: 600; }\r\n header .meta { color: var(--text-secondary); font-size: 14px; margin-top: 4px; }\r\n .summary { display: flex; gap: 16px; margin: 20px 0; flex-wrap: wrap; }\r\n .card {\r\n padding: 16px 24px; border-radius: 8px; color: white;\r\n font-weight: 600; min-width: 120px; text-align: center;\r\n }\r\n .card.passed { background: var(--color-passed); }\r\n .card.failed { background: var(--color-failed); }\r\n .card.skipped { background: var(--color-skipped); }\r\n .card.total { background: var(--color-info); }\r\n .card .card-value { font-size: 28px; }\r\n .card .card-label { font-size: 13px; opacity: 0.9; }\r\n .pass-rate {\r\n font-size: 48px; font-weight: 700; text-align: center; margin: 20px 0;\r\n color: ${parseFloat(passRate) >= 80 ? 'var(--color-passed)' : parseFloat(passRate) >= 50 ? 'var(--color-skipped)' : 'var(--color-failed)'};\r\n }\r\n .pass-rate-label { text-align: center; color: var(--text-secondary); margin-bottom: 20px; }\r\n .by-type { display: flex; gap: 12px; flex-wrap: wrap; margin: 20px 0; }\r\n .type-card {\r\n background: white; border: 1px solid var(--border-color); border-radius: 8px;\r\n padding: 12px 16px; min-width: 180px;\r\n }\r\n .type-name { font-weight: 600; text-transform: capitalize; margin-bottom: 4px; }\r\n .type-stats { font-size: 13px; }\r\n .type-stats span { margin-right: 8px; }\r\n .type-stats .passed { color: var(--color-passed); }\r\n .type-stats .failed { color: var(--color-failed); }\r\n .type-stats .skipped { color: var(--color-skipped); }\r\n .type-rate { font-size: 20px; font-weight: 700; margin-top: 4px; }\r\n .suite {\r\n background: white; border: 1px solid var(--border-color);\r\n border-radius: 8px; margin-bottom: 12px; overflow: hidden;\r\n }\r\n .suite-header {\r\n padding: 12px 16px; display: flex; justify-content: space-between;\r\n align-items: center; cursor: pointer; user-select: none;\r\n }\r\n .suite-header:hover { background: var(--bg-secondary); }\r\n .suite-header div { display: flex; align-items: center; gap: 8px; }\r\n .suite.collapsed .suite-body { display: none; }\r\n .suite-file { color: var(--text-secondary); font-size: 12px; }\r\n .toggle-icon { font-size: 12px; color: var(--text-secondary); transition: transform 0.2s; }\r\n .suite.collapsed .toggle-icon { transform: rotate(-90deg); }\r\n .suite-body { border-top: 1px solid var(--border-color); padding: 8px 16px; }\r\n .test-item {\r\n padding: 8px 0; display: flex; align-items: center; gap: 8px;\r\n }\r\n .test-item + .test-item { border-top: 1px solid var(--border-color); }\r\n .status-dot {\r\n width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;\r\n }\r\n .status-dot.passed { background: var(--color-passed); }\r\n .status-dot.failed { background: var(--color-failed); }\r\n .status-dot.skipped { background: var(--color-skipped); }\r\n .status-dot.pending { background: var(--text-secondary); }\r\n .test-name { flex: 1; }\r\n .duration { color: var(--text-secondary); font-size: 13px; }\r\n .retries { color: var(--color-skipped); font-size: 12px; }\r\n .error-message {\r\n background: #fef2f2; color: #991b1b; padding: 12px;\r\n border-radius: 4px; font-family: 'Fira Code', monospace;\r\n font-size: 13px; margin: 8px 0 8px 16px; white-space: pre-wrap;\r\n word-break: break-all;\r\n }\r\n footer {\r\n text-align: center; padding: 20px; color: var(--text-secondary);\r\n font-size: 13px; margin-top: 40px;\r\n }\r\n </style>\r\n</head>\r\n<body>\r\n <div class=\"container\">\r\n <header>\r\n <h1>QAT 测试报告</h1>\r\n <div class=\"meta\">生成时间: ${formatTimestamp(data.timestamp)} | 总耗时: ${formatDuration(data.duration)}</div>\r\n </header>\r\n\r\n <div class=\"pass-rate\">${passRate}%</div>\r\n <div class=\"pass-rate-label\">测试通过率</div>\r\n\r\n <div class=\"summary\">\r\n <div class=\"card total\">\r\n <div class=\"card-value\">${data.summary.total}</div>\r\n <div class=\"card-label\">总计</div>\r\n </div>\r\n <div class=\"card passed\">\r\n <div class=\"card-value\">${data.summary.passed}</div>\r\n <div class=\"card-label\">通过</div>\r\n </div>\r\n <div class=\"card failed\">\r\n <div class=\"card-value\">${data.summary.failed}</div>\r\n <div class=\"card-label\">失败</div>\r\n </div>\r\n <div class=\"card skipped\">\r\n <div class=\"card-value\">${data.summary.skipped}</div>\r\n <div class=\"card-label\">跳过</div>\r\n </div>\r\n </div>\r\n\r\n ${Object.keys(data.byType).length > 0 ? `<h2 style=\"margin-top:30px;margin-bottom:10px;\">按类型统计</h2><div class=\"by-type\">${byTypeHTML}</div>` : ''}\r\n\r\n <h2 style=\"margin-top:30px;margin-bottom:10px;\">测试详情</h2>\r\n ${suitesHTML || '<p style=\"color:var(--text-secondary)\">暂无测试结果</p>'}\r\n\r\n <footer>由 QAT 自动化测试工具生成</footer>\r\n </div>\r\n</body>\r\n</html>`;\r\n}\r\n\r\n/**\r\n * 将报告写入磁盘\r\n * @returns 报告文件路径\r\n */\r\nexport function writeReportToDisk(data: ReportData, outputDir: string): string {\r\n const html = generateHTMLReport(data);\r\n const dir = path.resolve(outputDir);\r\n\r\n if (!fs.existsSync(dir)) {\r\n fs.mkdirSync(dir, { recursive: true });\r\n }\r\n\r\n const indexPath = path.join(dir, 'index.html');\r\n fs.writeFileSync(indexPath, html, 'utf-8');\r\n\r\n return indexPath;\r\n}\r\n","/**\r\n * visual 命令 - 截图比对、基线管理、差异分析\r\n */\r\n\r\nimport { Command } from 'commander';\r\nimport chalk from 'chalk';\r\nimport ora from 'ora';\r\nimport type { VisualOptions } from '../types/index.js';\r\nimport { loadConfig } from '../services/config.js';\r\nimport {\r\n compareDirectories,\r\n updateAllBaselines,\r\n cleanBaselines,\r\n cleanDiffs,\r\n listBaselines,\r\n listDiffs,\r\n} from '../services/visual.js';\r\nimport { runPlaywright } from '../runners/playwright-runner.js';\r\n\r\nexport function registerVisualCommand(program: Command): void {\r\n program\r\n .command('visual')\r\n .description('视觉回归测试 - 截图比对与基线管理')\r\n .argument('<action>', '操作类型 (test|approve|clean)')\r\n .option('--threshold <number>', '像素差异阈值 (0-1)', '0.1')\r\n .action(async (action: string, options: { threshold?: string; config?: string }) => {\r\n try {\r\n await executeVisual({\r\n action: action as VisualOptions['action'],\r\n threshold: options.threshold ? parseFloat(options.threshold) : undefined,\r\n config: options.config,\r\n });\r\n } catch (error) {\r\n console.error(chalk.red(`\\n ✗ ${error instanceof Error ? error.message : String(error)}\\n`));\r\n process.exit(1);\r\n }\r\n });\r\n}\r\n\r\nasync function executeVisual(\r\n options: { action: VisualOptions['action']; threshold?: number; config?: string },\r\n): Promise<void> {\r\n const config = await loadConfig(options.config);\r\n const threshold = options.threshold ?? config.visual.threshold;\r\n const baselineDir = config.visual.baselineDir;\r\n const diffDir = config.visual.diffDir;\r\n\r\n switch (options.action) {\r\n case 'test': {\r\n await runVisualTest(threshold, baselineDir, diffDir, config);\r\n break;\r\n }\r\n case 'approve': {\r\n await approveBaselines(baselineDir, diffDir);\r\n break;\r\n }\r\n case 'clean': {\r\n await cleanAll(baselineDir, diffDir);\r\n break;\r\n }\r\n default: {\r\n console.error(chalk.red(`\\n 未知操作: ${options.action}`));\r\n console.log(chalk.gray(' 可用操作: test, approve, clean\\n'));\r\n process.exit(1);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * 执行视觉回归测试\r\n */\r\nasync function runVisualTest(\r\n threshold: number,\r\n baselineDir: string,\r\n diffDir: string,\r\n config: import('../types/index.js').QATConfig,\r\n): Promise<void> {\r\n // 1. 先运行 Playwright 视觉测试生成截图\r\n const spinner = ora('正在运行视觉测试截图...').start();\r\n\r\n try {\r\n const result = await runPlaywright({\r\n type: 'visual',\r\n browsers: config.playwright.browsers,\r\n });\r\n\r\n if (result.status === 'failed') {\r\n spinner.warn('部分视觉测试截图失败');\r\n } else {\r\n spinner.succeed('视觉测试截图完成');\r\n }\r\n } catch (error) {\r\n // Playwright 未安装等情况,继续尝试比对已有的截图\r\n spinner.warn('Playwright 运行跳过,尝试比对已有截图');\r\n }\r\n\r\n // 2. 比对截图\r\n const compareSpinner = ora('正在比对截图...').start();\r\n\r\n // 当前截图目录(Playwright 输出)\r\n const currentDir = findCurrentScreenshotsDir(baselineDir);\r\n\r\n if (!currentDir) {\r\n compareSpinner.info('没有找到截图文件');\r\n console.log(chalk.gray('\\n 提示: 先运行 qat run -t visual 生成截图\\n'));\r\n return;\r\n }\r\n\r\n const results = compareDirectories(baselineDir, currentDir, diffDir, threshold);\r\n\r\n compareSpinner.succeed('截图比对完成');\r\n\r\n // 3. 显示结果\r\n displayVisualResults(results, threshold);\r\n\r\n // 设置退出码\r\n const hasFailures = results.some((r) => !r.passed);\r\n if (hasFailures) {\r\n process.exitCode = 1;\r\n }\r\n}\r\n\r\n/**\r\n * 查找当前截图目录\r\n */\r\nfunction findCurrentScreenshotsDir(baselineDir: string): string | null {\r\n // Playwright 默认截图输出位置\r\n const possibleDirs = [\r\n path.join(process.cwd(), 'test-results'),\r\n path.join(process.cwd(), 'tests', 'visual', 'current'),\r\n path.join(process.cwd(), baselineDir, '..', 'current'),\r\n ];\r\n\r\n for (const dir of possibleDirs) {\r\n if (fs.existsSync(dir)) {\r\n const pngs = findPngFiles(dir);\r\n if (pngs.length > 0) return dir;\r\n }\r\n }\r\n\r\n return null;\r\n}\r\n\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\n\r\n/**\r\n * 递归查找 PNG 文件\r\n */\r\nfunction findPngFiles(dir: string): string[] {\r\n const files: string[] = [];\r\n\r\n function walk(d: string): void {\r\n if (!fs.existsSync(d)) return;\r\n const entries = fs.readdirSync(d, { withFileTypes: true });\r\n for (const entry of entries) {\r\n const fullPath = path.join(d, entry.name);\r\n if (entry.isDirectory() && entry.name !== 'node_modules') {\r\n walk(fullPath);\r\n } else if (entry.name.endsWith('.png')) {\r\n files.push(fullPath);\r\n }\r\n }\r\n }\r\n\r\n walk(dir);\r\n return files;\r\n}\r\n\r\n/**\r\n * 显示视觉回归测试结果\r\n */\r\nfunction displayVisualResults(\r\n results: import('../services/visual.js').DiffResult[],\r\n threshold: number,\r\n): void {\r\n const passed = results.filter((r) => r.passed);\r\n const failed = results.filter((r) => !r.passed);\r\n\r\n console.log();\r\n console.log(chalk.cyan(' ─────────────────────────────'));\r\n\r\n if (failed.length === 0) {\r\n console.log(chalk.green(` ✓ 全部通过 (${results.length} 个截图比对)`));\r\n } else {\r\n console.log(chalk.red(` ✗ ${failed.length} 个截图存在差异`));\r\n }\r\n\r\n console.log();\r\n console.log(` ${chalk.white('阈值:')} ${(threshold * 100).toFixed(0)}%`);\r\n console.log(` ${chalk.green('通过:')} ${passed.length}`);\r\n console.log(` ${chalk.red('失败:')} ${failed.length}`);\r\n\r\n if (passed.length > 0) {\r\n console.log();\r\n console.log(chalk.green(' 通过的截图:'));\r\n for (const result of passed) {\r\n const name = path.basename(result.baselinePath);\r\n if (result.totalPixels > 0) {\r\n const diffPct = (result.diffRatio * 100).toFixed(2);\r\n console.log(chalk.green(` ✓ ${name} (差异: ${diffPct}%)`));\r\n } else {\r\n console.log(chalk.green(` ✓ ${name} (新建基线)`));\r\n }\r\n }\r\n }\r\n\r\n if (failed.length > 0) {\r\n console.log();\r\n console.log(chalk.red(' 失败的截图:'));\r\n for (const result of failed) {\r\n const name = path.basename(result.baselinePath);\r\n if (result.diffPixels === -1) {\r\n console.log(chalk.red(` ✗ ${name} (尺寸不匹配)`));\r\n } else {\r\n const diffPct = (result.diffRatio * 100).toFixed(2);\r\n console.log(chalk.red(` ✗ ${name} (差异: ${diffPct}%)`));\r\n }\r\n if (result.diffPath) {\r\n console.log(chalk.gray(` 差异图: ${path.relative(process.cwd(), result.diffPath)}`));\r\n }\r\n }\r\n\r\n console.log();\r\n console.log(chalk.yellow(' 提示: 运行 qat visual approve 更新基线'));\r\n }\r\n\r\n console.log(chalk.cyan(' ─────────────────────────────'));\r\n console.log();\r\n}\r\n\r\n/**\r\n * 批量批准基线更新\r\n */\r\nasync function approveBaselines(baselineDir: string, diffDir: string): Promise<void> {\r\n const spinner = ora('正在更新基线快照...').start();\r\n\r\n const currentDir = findCurrentScreenshotsDir(baselineDir);\r\n\r\n if (!currentDir) {\r\n spinner.info('没有找到当前截图');\r\n return;\r\n }\r\n\r\n const updated = updateAllBaselines(currentDir, baselineDir);\r\n\r\n if (updated.length === 0) {\r\n spinner.info('没有需要更新的基线');\r\n return;\r\n }\r\n\r\n // 同时清理差异图片\r\n cleanDiffs(diffDir);\r\n\r\n spinner.succeed(`已更新 ${updated.length} 个基线快照`);\r\n\r\n console.log();\r\n for (const file of updated) {\r\n console.log(chalk.green(` ✓ ${file}`));\r\n }\r\n console.log();\r\n}\r\n\r\n/**\r\n * 清理所有基线和差异图片\r\n */\r\nasync function cleanAll(baselineDir: string, diffDir: string): Promise<void> {\r\n const spinner = ora('正在清理基线快照...').start();\r\n\r\n const baselines = cleanBaselines(baselineDir);\r\n const diffs = cleanDiffs(diffDir);\r\n\r\n spinner.succeed('清理完成');\r\n\r\n console.log();\r\n console.log(chalk.white(' 已删除:'));\r\n console.log(` 基线: ${baselines} 个`);\r\n console.log(` 差异: ${diffs} 个`);\r\n console.log();\r\n}\r\n","/**\r\n * 视觉回归服务 - 截图比对、基线管理、差异图片生成\r\n */\r\n\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport pixelmatch from 'pixelmatch';\r\nimport { PNG } from 'pngjs';\r\n\r\n/** 比对结果 */\r\nexport interface DiffResult {\r\n /** 是否通过(差异在阈值内) */\r\n passed: boolean;\r\n /** 差异像素数 */\r\n diffPixels: number;\r\n /** 总像素数 */\r\n totalPixels: number;\r\n /** 差异比例 (0-1) */\r\n diffRatio: number;\r\n /** 基线图片路径 */\r\n baselinePath: string;\r\n /** 当前图片路径 */\r\n currentPath: string;\r\n /** 差异图片路径(仅在有差异时生成) */\r\n diffPath?: string;\r\n}\r\n\r\n/**\r\n * 比对两张图片\r\n */\r\nexport function compareImages(\r\n baselinePath: string,\r\n currentPath: string,\r\n diffOutputPath: string,\r\n threshold: number = 0.1,\r\n): DiffResult {\r\n // 读取基线图片\r\n if (!fs.existsSync(baselinePath)) {\r\n throw new Error(`基线图片不存在: ${baselinePath}`);\r\n }\r\n\r\n if (!fs.existsSync(currentPath)) {\r\n throw new Error(`当前图片不存在: ${currentPath}`);\r\n }\r\n\r\n const baseline = PNG.sync.read(fs.readFileSync(baselinePath));\r\n const current = PNG.sync.read(fs.readFileSync(currentPath));\r\n\r\n // 尺寸必须一致\r\n if (baseline.width !== current.width || baseline.height !== current.height) {\r\n return {\r\n passed: false,\r\n diffPixels: -1,\r\n totalPixels: baseline.width * baseline.height,\r\n diffRatio: 1,\r\n baselinePath,\r\n currentPath,\r\n diffPath: undefined,\r\n };\r\n }\r\n\r\n const { width, height } = baseline;\r\n const totalPixels = width * height;\r\n\r\n // 创建差异图片\r\n const diff = new PNG({ width, height });\r\n const diffPixels = pixelmatch(\r\n baseline.data,\r\n current.data,\r\n diff.data,\r\n width,\r\n height,\r\n { threshold: 0.1 }, // pixelmatch 自身阈值(颜色差异灵敏度)\r\n );\r\n\r\n const diffRatio = diffPixels / totalPixels;\r\n const passed = diffRatio <= threshold;\r\n\r\n // 生成差异图片(仅在有差异时)\r\n let diffPath: string | undefined;\r\n if (diffPixels > 0) {\r\n // 确保输出目录存在\r\n const diffDir = path.dirname(diffOutputPath);\r\n if (!fs.existsSync(diffDir)) {\r\n fs.mkdirSync(diffDir, { recursive: true });\r\n }\r\n fs.writeFileSync(diffOutputPath, PNG.sync.write(diff));\r\n diffPath = diffOutputPath;\r\n }\r\n\r\n return {\r\n passed,\r\n diffPixels,\r\n totalPixels,\r\n diffRatio,\r\n baselinePath,\r\n currentPath,\r\n diffPath,\r\n };\r\n}\r\n\r\n/**\r\n * 创建基线快照(如果不存在则从当前截图复制)\r\n */\r\nexport function createBaseline(\r\n currentPath: string,\r\n baselinePath: string,\r\n): string {\r\n if (!fs.existsSync(currentPath)) {\r\n throw new Error(`当前截图不存在: ${currentPath}`);\r\n }\r\n\r\n const baselineDir = path.dirname(baselinePath);\r\n if (!fs.existsSync(baselineDir)) {\r\n fs.mkdirSync(baselineDir, { recursive: true });\r\n }\r\n\r\n fs.copyFileSync(currentPath, baselinePath);\r\n return baselinePath;\r\n}\r\n\r\n/**\r\n * 更新基线(将当前截图替换为基线)\r\n */\r\nexport function updateBaseline(\r\n currentPath: string,\r\n baselinePath: string,\r\n): string {\r\n return createBaseline(currentPath, baselinePath);\r\n}\r\n\r\n/**\r\n * 批量更新基线(将 diff 目录下的所有当前截图更新为基线)\r\n */\r\nexport function updateAllBaselines(\r\n currentDir: string,\r\n baselineDir: string,\r\n): string[] {\r\n const updated: string[] = [];\r\n\r\n if (!fs.existsSync(currentDir)) {\r\n return updated;\r\n }\r\n\r\n const files = fs.readdirSync(currentDir).filter((f) => f.endsWith('.png'));\r\n\r\n for (const file of files) {\r\n const currentPath = path.join(currentDir, file);\r\n const baselinePath = path.join(baselineDir, file);\r\n\r\n const baselineDirAbs = path.dirname(baselinePath);\r\n if (!fs.existsSync(baselineDirAbs)) {\r\n fs.mkdirSync(baselineDirAbs, { recursive: true });\r\n }\r\n\r\n fs.copyFileSync(currentPath, baselinePath);\r\n updated.push(file);\r\n }\r\n\r\n return updated;\r\n}\r\n\r\n/**\r\n * 清理所有基线快照\r\n */\r\nexport function cleanBaselines(baselineDir: string): number {\r\n if (!fs.existsSync(baselineDir)) {\r\n return 0;\r\n }\r\n\r\n const files = fs.readdirSync(baselineDir).filter((f) => f.endsWith('.png'));\r\n let count = 0;\r\n\r\n for (const file of files) {\r\n fs.unlinkSync(path.join(baselineDir, file));\r\n count++;\r\n }\r\n\r\n return count;\r\n}\r\n\r\n/**\r\n * 清理差异图片\r\n */\r\nexport function cleanDiffs(diffDir: string): number {\r\n if (!fs.existsSync(diffDir)) {\r\n return 0;\r\n }\r\n\r\n const files = fs.readdirSync(diffDir).filter((f) => f.endsWith('.png'));\r\n let count = 0;\r\n\r\n for (const file of files) {\r\n fs.unlinkSync(path.join(diffDir, file));\r\n count++;\r\n }\r\n\r\n return count;\r\n}\r\n\r\n/**\r\n * 获取基线快照列表\r\n */\r\nexport function listBaselines(baselineDir: string): string[] {\r\n if (!fs.existsSync(baselineDir)) {\r\n return [];\r\n }\r\n\r\n return fs\r\n .readdirSync(baselineDir)\r\n .filter((f) => f.endsWith('.png'))\r\n .sort();\r\n}\r\n\r\n/**\r\n * 获取差异图片列表\r\n */\r\nexport function listDiffs(diffDir: string): string[] {\r\n if (!fs.existsSync(diffDir)) {\r\n return [];\r\n }\r\n\r\n return fs\r\n .readdirSync(diffDir)\r\n .filter((f) => f.endsWith('.png'))\r\n .sort();\r\n}\r\n\r\n/**\r\n * 批量比对目录中的所有截图\r\n */\r\nexport function compareDirectories(\r\n baselineDir: string,\r\n currentDir: string,\r\n diffDir: string,\r\n threshold: number = 0.1,\r\n): DiffResult[] {\r\n const results: DiffResult[] = [];\r\n\r\n if (!fs.existsSync(currentDir)) {\r\n return results;\r\n }\r\n\r\n const currentFiles = fs.readdirSync(currentDir).filter((f) => f.endsWith('.png'));\r\n\r\n for (const file of currentFiles) {\r\n const currentPath = path.join(currentDir, file);\r\n const baselinePath = path.join(baselineDir, file);\r\n const diffPath = path.join(diffDir, file);\r\n\r\n if (!fs.existsSync(baselinePath)) {\r\n // 无基线,自动创建\r\n createBaseline(currentPath, baselinePath);\r\n results.push({\r\n passed: true,\r\n diffPixels: 0,\r\n totalPixels: 0,\r\n diffRatio: 0,\r\n baselinePath,\r\n currentPath,\r\n diffPath: undefined,\r\n });\r\n continue;\r\n }\r\n\r\n try {\r\n const result = compareImages(baselinePath, currentPath, diffPath, threshold);\r\n results.push(result);\r\n } catch (error) {\r\n results.push({\r\n passed: false,\r\n diffPixels: -1,\r\n totalPixels: 0,\r\n diffRatio: 1,\r\n baselinePath,\r\n currentPath,\r\n diffPath: undefined,\r\n });\r\n }\r\n }\r\n\r\n return results;\r\n}\r\n","/**\r\n * setup 命令 - 一键安装测试所需的外部依赖\r\n */\r\n\r\nimport { Command } from 'commander';\r\nimport chalk from 'chalk';\r\nimport inquirer from 'inquirer';\r\nimport ora from 'ora';\r\nimport { execFile } from 'node:child_process';\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport type { SetupOptions, QATConfig } from '../types/index.js';\r\nimport { loadConfig } from '../services/config.js';\r\nimport { detectProject } from '../services/detector.js';\r\n\r\n/** 依赖分组定义 */\r\ninterface DependencyGroup {\r\n /** 分组名称 */\r\n name: string;\r\n /** 对应的配置项启用路径 */\r\n configKey: string;\r\n /** 需要安装的包列表 */\r\n packages: string[];\r\n /** 额外的安装后步骤 */\r\n postInstall?: string[];\r\n}\r\n\r\n/** 所有可选依赖分组 */\r\nconst DEPENDENCY_GROUPS: DependencyGroup[] = [\r\n {\r\n name: 'Vitest (单元/组件/API测试)',\r\n configKey: 'vitest',\r\n packages: ['vitest', '@vue/test-utils', 'happy-dom', '@vitest/coverage-v8'],\r\n },\r\n {\r\n name: 'Playwright (E2E/视觉回归测试)',\r\n configKey: 'playwright',\r\n packages: ['@playwright/test'],\r\n postInstall: ['npx playwright install --with-deps'],\r\n },\r\n {\r\n name: 'Lighthouse (性能测试)',\r\n configKey: 'lighthouse',\r\n packages: ['lighthouse'],\r\n },\r\n];\r\n\r\nexport function registerSetupCommand(program: Command): void {\r\n program\r\n .command('setup')\r\n .description('一键安装测试所需的依赖包')\r\n .option('-f, --force', '强制重新安装已有依赖')\r\n .option('--dry-run', '仅显示需要安装的依赖,不实际执行')\r\n .action(async (options: SetupOptions) => {\r\n try {\r\n await executeSetup(options);\r\n } catch (error) {\r\n console.error(chalk.red(`\\n ✗ ${error instanceof Error ? error.message : String(error)}\\n`));\r\n process.exit(1);\r\n }\r\n });\r\n}\r\n\r\nasync function executeSetup(options: SetupOptions): Promise<void> {\r\n console.log(chalk.cyan('\\n QAT 依赖安装器\\n'));\r\n\r\n // 1. 检测项目\r\n const projectInfo = detectProject();\r\n\r\n if (!fs.existsSync(path.join(process.cwd(), 'package.json'))) {\r\n throw new Error('未找到 package.json,请在项目根目录执行此命令');\r\n }\r\n\r\n // 2. 显示框架信息\r\n if (projectInfo.frameworkConfidence > 0) {\r\n console.log(chalk.white(` 检测到框架: ${chalk.cyan(projectInfo.frameworkDisplayName)}`));\r\n if (projectInfo.uiLibrary !== 'none') {\r\n console.log(chalk.white(` UI 组件库: ${chalk.cyan(projectInfo.uiLibrary)}`));\r\n }\r\n if (projectInfo.monorepo !== 'none') {\r\n console.log(chalk.white(` Monorepo: ${chalk.cyan(projectInfo.monorepo)}`));\r\n if (projectInfo.appDirs.length > 0) {\r\n console.log(chalk.white(` 子项目: ${chalk.gray(projectInfo.appDirs.join(', '))}`));\r\n }\r\n }\r\n console.log();\r\n }\r\n\r\n // 3. Monorepo 提示\r\n let installDir = process.cwd();\r\n if (projectInfo.monorepo !== 'none' && projectInfo.appDirs.length > 0) {\r\n const { chooseDir } = await inquirer.prompt([\r\n {\r\n type: 'list',\r\n name: 'chooseDir',\r\n message: '检测到 Monorepo 结构,请选择依赖安装位置:',\r\n choices: [\r\n { name: `项目根目录 (${projectInfo.packageManager} workspace 自动提升)`, value: 'root' },\r\n ...projectInfo.appDirs.map((d) => ({\r\n name: `${d}/`,\r\n value: d,\r\n })),\r\n ],\r\n default: 'root',\r\n },\r\n ]);\r\n if (chooseDir !== 'root') {\r\n installDir = path.join(process.cwd(), chooseDir);\r\n if (!fs.existsSync(path.join(installDir, 'package.json'))) {\r\n throw new Error(`${chooseDir} 下没有 package.json`);\r\n }\r\n }\r\n }\r\n\r\n // 4. 读取配置(如果存在)\r\n let config: Partial<QATConfig> | null = null;\r\n try {\r\n config = await loadConfig(options.config);\r\n } catch {\r\n // 配置文件不存在,根据项目检测结果推断\r\n }\r\n\r\n // 5. 根据框架调整依赖分组\r\n const groupsToInstall = determineGroups(config, projectInfo, options.force);\r\n\r\n if (groupsToInstall.length === 0) {\r\n console.log(chalk.green(' ✓ 所有依赖已安装,无需额外操作\\n'));\r\n return;\r\n }\r\n\r\n // 6. 交互选择\r\n const selectedGroups = await selectGroups(groupsToInstall, options.dryRun);\r\n\r\n if (selectedGroups.length === 0) {\r\n console.log(chalk.gray('\\n 已取消安装\\n'));\r\n return;\r\n }\r\n\r\n // 7. 确认安装\r\n const allPackages = selectedGroups.flatMap((g) => g.packages);\r\n const { confirmed } = await inquirer.prompt([\r\n {\r\n type: 'confirm',\r\n name: 'confirmed',\r\n message: `确认安装以下 ${allPackages.length} 个依赖?\\n${allPackages.map((p) => ` - ${p}`).join('\\n')}`,\r\n default: true,\r\n },\r\n ]);\r\n\r\n if (!confirmed) {\r\n console.log(chalk.gray('\\n 已取消安装\\n'));\r\n return;\r\n }\r\n\r\n // 8. Dry run 模式\r\n if (options.dryRun) {\r\n console.log(chalk.yellow('\\n [Dry Run] 以下命令将被执行:\\n'));\r\n const pm = getPackageManager(projectInfo.packageManager);\r\n const installCmd = pm === 'npm' ? 'npm install -D' : pm === 'yarn' ? 'yarn add -D' : pm === 'pnpm' ? 'pnpm add -D' : 'bun add -D';\r\n for (const group of selectedGroups) {\r\n const dirHint = installDir !== process.cwd() ? ` (在 ${path.relative(process.cwd(), installDir) || installDir})` : '';\r\n console.log(chalk.white(` ${installCmd} ${group.packages.join(' ')}${dirHint}`));\r\n if (group.postInstall) {\r\n for (const cmd of group.postInstall) {\r\n console.log(chalk.white(` ${cmd}`));\r\n }\r\n }\r\n }\r\n console.log();\r\n return;\r\n }\r\n\r\n // 9. 执行安装\r\n for (const group of selectedGroups) {\r\n await installGroup(group, projectInfo.packageManager, installDir);\r\n }\r\n\r\n // 10. 显示结果\r\n displaySetupResult(selectedGroups);\r\n}\r\n\r\n/**\r\n * 确定需要安装的依赖分组\r\n */\r\nfunction determineGroups(\r\n config: Partial<QATConfig> | null,\r\n projectInfo: ReturnType<typeof detectProject>,\r\n force: boolean,\r\n): DependencyGroup[] {\r\n const groups: DependencyGroup[] = [];\r\n\r\n for (const group of DEPENDENCY_GROUPS) {\r\n // 判断该功能是否启用\r\n const isConfigEnabled = isGroupEnabled(group.configKey, config);\r\n // 判断是否已安装\r\n const isInstalled = group.packages.every((pkg) =>\r\n projectInfo.dependencies.includes(pkg) || projectInfo.dependencies.includes(pkg.replace(/^@/, '')),\r\n );\r\n\r\n if (isConfigEnabled && (!isInstalled || force)) {\r\n groups.push(group);\r\n }\r\n }\r\n\r\n // 如果没有配置文件,所有分组都作为候选\r\n if (!config) {\r\n return DEPENDENCY_GROUPS;\r\n }\r\n\r\n return groups;\r\n}\r\n\r\n/**\r\n * 判断配置中某功能是否启用\r\n */\r\nfunction isGroupEnabled(configKey: string, config: Partial<QATConfig> | null): boolean {\r\n if (!config) return true; // 无配置时默认启用\r\n const section = config[configKey as keyof QATConfig];\r\n if (section && typeof section === 'object' && 'enabled' in section) {\r\n return (section as { enabled: boolean }).enabled;\r\n }\r\n return true;\r\n}\r\n\r\n/**\r\n * 交互选择要安装的依赖分组\r\n */\r\nasync function selectGroups(\r\n groups: DependencyGroup[],\r\n dryRun?: boolean,\r\n): Promise<DependencyGroup[]> {\r\n const { selected } = await inquirer.prompt([\r\n {\r\n type: 'checkbox',\r\n name: 'selected',\r\n message: '选择要安装的测试工具:',\r\n choices: groups.map((g) => ({\r\n name: g.name,\r\n value: g,\r\n checked: true,\r\n })),\r\n },\r\n ]);\r\n\r\n return selected;\r\n}\r\n\r\n/**\r\n * 安装一个依赖分组\r\n */\r\nasync function installGroup(\r\n group: DependencyGroup,\r\n packageManager: string,\r\n installDir: string,\r\n): Promise<void> {\r\n const pm = getPackageManager(packageManager);\r\n const spinner = ora(`正在安装 ${group.name}...`).start();\r\n\r\n try {\r\n // 安装 npm 包\r\n const installArgs = buildInstallArgs(pm, group.packages);\r\n await execCommand(pm, installArgs, installDir);\r\n\r\n // 执行安装后步骤(如 playwright install)\r\n if (group.postInstall) {\r\n for (const cmd of group.postInstall) {\r\n spinner.text = `正在执行 ${cmd}...`;\r\n const [command, ...args] = cmd.split(' ');\r\n await execCommand(command, args, installDir);\r\n }\r\n }\r\n\r\n spinner.succeed(`${group.name} 安装完成`);\r\n } catch (error) {\r\n spinner.fail(`${group.name} 安装失败`);\r\n throw new Error(\r\n `安装 ${group.name} 失败: ${error instanceof Error ? error.message : String(error)}`,\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * 构建安装命令参数\r\n */\r\nfunction buildInstallArgs(pm: string, packages: string[]): string[] {\r\n switch (pm) {\r\n case 'npm':\r\n return ['install', '-D', ...packages];\r\n case 'yarn':\r\n return ['add', '-D', ...packages];\r\n case 'pnpm':\r\n return ['add', '-D', ...packages];\r\n case 'bun':\r\n return ['add', '-D', ...packages];\r\n default:\r\n return ['install', '-D', ...packages];\r\n }\r\n}\r\n\r\n/**\r\n * 获取包管理器可执行文件名\r\n */\r\nfunction getPackageManager(pm: string): string {\r\n // Windows 上需要 .cmd 后缀\r\n if (process.platform === 'win32') {\r\n if (pm === 'npm') return 'npm.cmd';\r\n if (pm === 'yarn') return 'yarn.cmd';\r\n if (pm === 'pnpm') return 'pnpm.cmd';\r\n if (pm === 'bun') return 'bun.cmd';\r\n }\r\n return pm;\r\n}\r\n\r\n/**\r\n * 执行命令\r\n */\r\nfunction execCommand(command: string, args: string[], cwd?: string): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n execFile(command, args, {\r\n cwd: cwd || process.cwd(),\r\n env: { ...process.env },\r\n maxBuffer: 50 * 1024 * 1024,\r\n shell: true,\r\n }, (error, stdout, stderr) => {\r\n if (error) {\r\n reject(new Error(error.message + (stderr ? `\\n${stderr}` : '')));\r\n return;\r\n }\r\n resolve();\r\n });\r\n });\r\n}\r\n\r\n/**\r\n * 显示安装结果\r\n */\r\nfunction displaySetupResult(groups: DependencyGroup[]): void {\r\n console.log(chalk.green('\\n ✓ 依赖安装完成!\\n'));\r\n console.log(chalk.white(' 已安装:'));\r\n\r\n for (const group of groups) {\r\n console.log(chalk.cyan(`\\n ${group.name}`));\r\n for (const pkg of group.packages) {\r\n console.log(chalk.gray(` ✓ ${pkg}`));\r\n }\r\n if (group.postInstall) {\r\n for (const cmd of group.postInstall) {\r\n console.log(chalk.gray(` ✓ ${cmd}`));\r\n }\r\n }\r\n }\r\n\r\n console.log();\r\n console.log(chalk.cyan(' 下一步:'));\r\n console.log(chalk.gray(' 1. 运行 qat create 创建测试用例'));\r\n console.log(chalk.gray(' 2. 运行 qat run 执行测试'));\r\n console.log();\r\n}\r\n","/**\r\n * status 命令 - 显示 QAT 当前状态,包括 AI 模型信息\r\n */\r\n\r\nimport { Command } from 'commander';\r\nimport chalk from 'chalk';\r\nimport ora from 'ora';\r\nimport type { GlobalOptions } from '../types/index.js';\r\nimport {\r\n loadGlobalAIConfig,\r\n isGlobalAIConfigured,\r\n maskApiKey,\r\n getAIConfigPath,\r\n toAIConfig,\r\n} from '../services/global-config.js';\r\nimport { testAIConnection } from '../ai/provider.js';\r\nimport { loadConfig } from '../services/config.js';\r\nimport { detectProject } from '../services/detector.js';\r\n\r\nexport function registerStatusCommand(program: Command): void {\r\n program\r\n .command('status')\r\n .description('查看 QAT 状态 - AI 模型信息、项目配置')\r\n .action(async (options: GlobalOptions) => {\r\n try {\r\n await executeStatus(options);\r\n } catch (error) {\r\n console.error(chalk.red(`\\n ✗ ${error instanceof Error ? error.message : String(error)}\\n`));\r\n process.exit(1);\r\n }\r\n });\r\n}\r\n\r\nasync function executeStatus(_options: GlobalOptions): Promise<void> {\r\n // ─── AI 模型状态 ────────────────────────────────────\r\n console.log(chalk.cyan('\\n AI 模型状态'));\r\n console.log(chalk.gray(' ─────────────────────────────'));\r\n\r\n const globalAI = loadGlobalAIConfig();\r\n\r\n if (!globalAI) {\r\n console.log(chalk.yellow(' ✗ 未配置 AI 模型'));\r\n console.log(chalk.gray(' 运行 qat change 配置 AI 模型'));\r\n } else {\r\n console.log(` ${chalk.white('模型:')} ${chalk.green(globalAI.model)}`);\r\n console.log(` ${chalk.white('API URL:')} ${chalk.gray(globalAI.baseUrl)}`);\r\n console.log(` ${chalk.white('API Key:')} ${chalk.gray(maskApiKey(globalAI.apiKey))}`);\r\n console.log(` ${chalk.white('Provider:')} ${chalk.gray(globalAI.provider)}`);\r\n console.log(` ${chalk.white('配置文件:')} ${chalk.gray(getAIConfigPath())}`);\r\n\r\n // 连通性测试\r\n const testSpinner = ora(' 正在测试连通性...').start();\r\n try {\r\n const aiConfig = toAIConfig(globalAI);\r\n const result = await testAIConnection(aiConfig);\r\n if (result.ok) {\r\n testSpinner.succeed(` 连通正常 ${chalk.gray(`(${result.latencyMs}ms)`)}`);\r\n } else {\r\n testSpinner.fail(` 连通异常: ${result.message}`);\r\n }\r\n } catch (error) {\r\n testSpinner.fail(` 连通测试失败: ${error instanceof Error ? error.message : String(error)}`);\r\n }\r\n }\r\n\r\n // ─── 项目配置状态 ───────────────────────────────────\r\n console.log();\r\n console.log(chalk.cyan(' 项目配置'));\r\n console.log(chalk.gray(' ─────────────────────────────'));\r\n\r\n try {\r\n const config = await loadConfig();\r\n const projectInfo = detectProject();\r\n\r\n console.log(` ${chalk.white('框架:')} ${projectInfo.frameworkDisplayName}`);\r\n console.log(` ${chalk.white('源码目录:')} ${config.project.srcDir}`);\r\n console.log(` ${chalk.white('Vitest:')} ${config.vitest.enabled ? chalk.green('✓') : chalk.red('✗')} (${config.vitest.environment})`);\r\n console.log(` ${chalk.white('Playwright:')} ${config.playwright.enabled ? chalk.green('✓') : chalk.red('✗')} (${config.playwright.browsers.join(', ')})`);\r\n console.log(` ${chalk.white('Mock:')} ${config.mock.enabled ? chalk.green('✓') : chalk.red('✗')} (port ${config.mock.port})`);\r\n console.log(` ${chalk.white('Visual:')} ${config.visual.enabled ? chalk.green('✓') : chalk.red('✗')}`);\r\n console.log(` ${chalk.white('Lighthouse:')} ${config.lighthouse.enabled ? chalk.green('✓') : chalk.red('✗')}`);\r\n } catch {\r\n console.log(chalk.yellow(' ✗ 未找到项目配置 (运行 qat init 初始化)'));\r\n }\r\n\r\n console.log();\r\n}\r\n","/**\r\n * change 命令 - 更改 AI 模型配置\r\n * 修改全局 ~/.qat/ai.json,影响所有项目\r\n */\r\n\r\nimport { Command } from 'commander';\r\nimport chalk from 'chalk';\r\nimport inquirer from 'inquirer';\r\nimport ora from 'ora';\r\nimport type { GlobalOptions } from '../types/index.js';\r\nimport {\r\n loadGlobalAIConfig,\r\n saveGlobalAIConfig,\r\n isGlobalAIConfigured,\r\n maskApiKey,\r\n getAIConfigPath,\r\n toAIConfig,\r\n} from '../services/global-config.js';\r\nimport { testAIConnection } from '../ai/provider.js';\r\n\r\nexport function registerChangeCommand(program: Command): void {\r\n program\r\n .command('change')\r\n .description('更改 AI 模型配置 - 修改全局 AI 模型、API Key 等')\r\n .action(async (options: GlobalOptions) => {\r\n try {\r\n await executeChange(options);\r\n } catch (error) {\r\n console.error(chalk.red(`\\n ✗ ${error instanceof Error ? error.message : String(error)}\\n`));\r\n process.exit(1);\r\n }\r\n });\r\n}\r\n\r\nasync function executeChange(_options: GlobalOptions): Promise<void> {\r\n const current = loadGlobalAIConfig();\r\n\r\n if (current) {\r\n console.log(chalk.cyan('\\n 当前 AI 配置:'));\r\n console.log(` ${chalk.white('模型:')} ${chalk.green(current.model)}`);\r\n console.log(` ${chalk.white('API URL:')} ${chalk.gray(current.baseUrl)}`);\r\n console.log(` ${chalk.white('API Key:')} ${chalk.gray(maskApiKey(current.apiKey))}`);\r\n console.log();\r\n } else {\r\n console.log(chalk.yellow('\\n 当前未配置 AI 模型,请配置:\\n'));\r\n }\r\n\r\n // 引导用户配置\r\n const answers = await inquirer.prompt([\r\n {\r\n type: 'input',\r\n name: 'apiKey',\r\n message: 'API Key (Ollama 本地可留空):',\r\n default: current?.apiKey || '',\r\n },\r\n {\r\n type: 'input',\r\n name: 'baseUrl',\r\n message: 'API Base URL:',\r\n default: current?.baseUrl || 'https://api.deepseek.com/v1',\r\n validate: (input: string) => {\r\n if (!input.trim()) return 'Base URL 不能为空';\r\n if (!input.trim().startsWith('http')) return 'URL 必须以 http(s):// 开头';\r\n return true;\r\n },\r\n },\r\n {\r\n type: 'input',\r\n name: 'model',\r\n message: '模型名称:',\r\n default: current?.model || 'deepseek-chat',\r\n validate: (input: string) => {\r\n if (!input.trim()) return '模型名称不能为空';\r\n return true;\r\n },\r\n },\r\n ]);\r\n\r\n const newConfig = {\r\n provider: 'openai',\r\n apiKey: answers.apiKey?.trim() || '',\r\n baseUrl: answers.baseUrl?.trim() || 'https://api.deepseek.com/v1',\r\n model: answers.model?.trim() || 'deepseek-chat',\r\n };\r\n\r\n // 保存\r\n saveGlobalAIConfig(newConfig);\r\n console.log(chalk.green(`\\n ✓ AI 配置已保存`));\r\n console.log(chalk.gray(` ${getAIConfigPath()}`));\r\n console.log(` ${chalk.white('模型:')} ${chalk.green(newConfig.model)} @ ${chalk.gray(newConfig.baseUrl)}`);\r\n\r\n // 连通性测试\r\n const testSpinner = ora(' 正在测试连通性...').start();\r\n try {\r\n const aiConfig = toAIConfig(newConfig);\r\n const result = await testAIConnection(aiConfig);\r\n if (result.ok) {\r\n testSpinner.succeed(` AI 连通正常 ${chalk.gray(`(${newConfig.model}, ${result.latencyMs}ms)`)}`);\r\n } else {\r\n testSpinner.fail(` AI 连通异常: ${result.message}`);\r\n }\r\n } catch (error) {\r\n testSpinner.fail(` 连通测试失败: ${error instanceof Error ? error.message : String(error)}`);\r\n }\r\n\r\n console.log();\r\n}\r\n"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IA+EN,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAmIX,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAuBL,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":["chalk","chalk","ora","fs","path","fs","path","path","fs","fs","path","fs","path","path","fs","chalk","chalk","fs","path","path","fs","fs","path","path","fs","fs","path","path","program","chalk","ora","fs","chalk","inquirer","ora","fs","path","program","chalk","inquirer","ora","path","fs","chalk","inquirer","ora","path","execFile","path","execFile","program","chalk","inquirer","ora","chalk","ora","program","chalk","ora","chalk","ora","fs","path","fs","path","formatDuration","program","chalk","ora","path","fs","chalk","ora","fs","path","fs","path","fs","path","fs","path","program","chalk","ora","chalk","inquirer","ora","execFile","fs","path","program","chalk","fs","path","inquirer","ora","execFile","chalk","ora","program","chalk","ora","chalk","inquirer","ora","program","chalk","inquirer","ora","chalk"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/commands/init.ts","../src/services/detector.ts","../src/services/config.ts","../src/services/mock-server.ts","../src/ai/noop-provider.ts","../src/ai/openai-provider.ts","../src/ai/provider.ts","../src/services/test-reviewer.ts","../src/services/source-analyzer.ts","../src/services/template.ts","../src/services/global-config.ts","../src/commands/create.ts","../src/commands/run.ts","../src/runners/vitest-runner.ts","../src/runners/playwright-runner.ts","../src/runners/lighthouse-runner.ts","../src/commands/mock.ts","../src/commands/report.ts","../src/services/reporter.ts","../src/commands/visual.ts","../src/services/visual.ts","../src/commands/setup.ts","../src/commands/status.ts","../src/commands/change.ts"],"sourcesContent":["#!/usr/bin/env node\r\n\r\n/**\r\n * QAT CLI 入口\r\n * 面向Vue项目的自动化测试命令行工具\r\n */\r\n\r\nimport { Command } from 'commander';\r\nimport chalk from 'chalk';\r\nimport { registerInitCommand } from './commands/init.js';\r\nimport { registerCreateCommand } from './commands/create.js';\r\nimport { registerRunCommand } from './commands/run.js';\r\nimport { registerMockCommand } from './commands/mock.js';\r\nimport { registerReportCommand } from './commands/report.js';\r\nimport { registerVisualCommand } from './commands/visual.js';\r\nimport { registerSetupCommand } from './commands/setup.js';\r\nimport { registerStatusCommand } from './commands/status.js';\r\nimport { registerChangeCommand } from './commands/change.js';\r\n\r\nconst VERSION = '0.2.8';\r\n\r\n/** 打印 QAT Logo */\r\nfunction printLogo(): void {\r\n const logo = `\r\n ${chalk.bold.cyan(' ___ _ _ _ _ _____ _ _ ')}\r\n ${chalk.bold.cyan(' / _ \\\\ _ _ (_) ___ | | __ / \\\\ _ _ | |_ ___ |_ _| ___ ___ | |_ (_) _ __ __ _ ')}\r\n ${chalk.bold.cyan(' | | | | | | | | | | / __| | |/ / / _ \\\\ | | | | | __| / _ \\\\ | | / _ \\\\ / __| | __| | | | \\'_ \\\\ / _` |')}\r\n ${chalk.bold.cyan(' | |_| | | |_| | | | | (__ | < / ___ \\\\ | |_| | | |_ | (_) | | | | __/ \\\\__ \\\\ | |_ | | | | | | | (_| |')}\r\n ${chalk.bold.cyan(' \\\\__\\\\_\\\\ \\\\__,_| |_| \\\\___| |_|\\\\_\\\\ /_/ \\\\_\\\\ \\\\__,_| \\\\__| \\\\___/ |_| \\\\___| |___/ \\\\__| |_| |_| |_| \\\\__, |')}\r\n ${chalk.bold.cyan(' |___/ ')}\r\n ${chalk.gray(' CLI自动化测试工具 v')}${chalk.green(VERSION)}\r\n`;\r\n console.log(logo);\r\n}\r\n\r\nconst program = new Command();\r\n\r\nprogram\r\n .name('qat')\r\n .description('CLI自动化测试工具 - 面向Vue项目,集成Vitest、Playwright,覆盖测试全流程')\r\n .version(VERSION)\r\n .option('-c, --config <path>', '指定配置文件路径')\r\n .option('-v, --verbose', '显示详细输出')\r\n .hook('preAction', async (thisCommand) => {\r\n // 打印 Logo\r\n printLogo();\r\n\r\n const opts = thisCommand.opts();\r\n if (opts.verbose) {\r\n process.env.QAT_VERBOSE = 'true';\r\n }\r\n if (opts.config) {\r\n process.env.QAT_CONFIG_PATH = opts.config;\r\n }\r\n\r\n // 加载用户自定义框架注册文件\r\n const { loadExternalFrameworks } = await import('./services/framework-registry.js');\r\n const loadResult = await loadExternalFrameworks(process.cwd());\r\n\r\n if (loadResult.loaded > 0 && opts.verbose) {\r\n console.log(chalk.gray(` [ext] 已加载 ${loadResult.loaded} 个外部扩展文件: ${loadResult.files.join(', ')}`));\r\n }\r\n\r\n if (loadResult.errors.length > 0) {\r\n for (const err of loadResult.errors) {\r\n console.log(chalk.yellow(` [ext] 警告: ${err.file} - ${err.error}`));\r\n }\r\n }\r\n });\r\n\r\n// 注册所有命令\r\nregisterInitCommand(program);\r\nregisterCreateCommand(program);\r\nregisterRunCommand(program);\r\nregisterMockCommand(program);\r\nregisterReportCommand(program);\r\nregisterVisualCommand(program);\r\nregisterSetupCommand(program);\r\nregisterStatusCommand(program);\r\nregisterChangeCommand(program);\r\n\r\n// 未知命令提示\r\nprogram.on('command:*', (operands) => {\r\n console.error(chalk.red(`\\n 未知命令: ${operands[0]}`));\r\n console.log(chalk.gray(` 使用 qat --help 查看可用命令\\n`));\r\n process.exit(1);\r\n});\r\n\r\nprogram.parse();\r\n","/**\r\n * init 命令 - 检测项目结构,生成配置、目录和测试用例\r\n * AI 配置从全局 ~/.qat/ai.json 读取\r\n * 按文件类型智能选择测试类型:.vue → 组件测试, utils/composables → 单元测试, api → API测试\r\n */\r\n\r\nimport { Command } from 'commander';\r\nimport chalk from 'chalk';\r\nimport inquirer from 'inquirer';\r\nimport ora from 'ora';\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport type { InitOptions, QATConfig, TestType } from '../types/index.js';\r\nimport { detectProject, discoverVueComponents, discoverUtilityFiles } from '../services/detector.js';\r\nimport { writeConfigFile, DEFAULT_CONFIG } from '../services/config.js';\r\nimport { initMockRoutesDir } from '../services/mock-server.js';\r\nimport { testAIConnection, getAIProvider, isAIAvailable } from '../ai/provider.js';\r\nimport { generateWithReview, printReviewReport, type ReviewResult, type ReviewReportEntry } from '../services/test-reviewer.js';\r\nimport { scanAPICalls, generateMockRoutesFromAPICalls, analyzeFile } from '../services/source-analyzer.js';\r\nimport type { PropInfo, EmitInfo } from '../services/source-analyzer.js';\r\nimport { renderTemplate } from '../services/template.js';\r\nimport {\r\n loadGlobalAIConfig,\r\n saveGlobalAIConfig,\r\n isGlobalAIConfigured,\r\n toAIConfig,\r\n maskApiKey,\r\n getAIConfigPath,\r\n} from '../services/global-config.js';\r\n\r\n/** 测试类型到输出子目录的映射 */\r\nconst TEST_TYPE_DIR: Record<TestType, string> = {\r\n unit: 'tests/unit',\r\n component: 'tests/component',\r\n e2e: 'tests/e2e',\r\n api: 'tests/api',\r\n visual: 'tests/visual',\r\n performance: 'tests/e2e',\r\n};\r\n\r\n/**\r\n * 根据文件路径智能判断应该使用哪种测试类型\r\n * - .vue 文件 → component 组件测试\r\n * - composables/hooks → unit 单元测试\r\n * - utils/helpers/services → unit 单元测试\r\n * - api/ 页面级 vue → api 测试\r\n */\r\nfunction inferTestType(filePath: string): TestType {\r\n const normalized = filePath.replace(/\\\\/g, '/').toLowerCase();\r\n\r\n // API 文件\r\n if (/\\/api\\//.test(normalized) || /api\\.(ts|js)$/.test(normalized)) {\r\n return 'api';\r\n }\r\n\r\n // Vue 组件\r\n if (normalized.endsWith('.vue')) {\r\n return 'component';\r\n }\r\n\r\n // composables / hooks\r\n if (/\\/composables?\\//.test(normalized) || /\\/hooks?\\//.test(normalized) || /^use[A-Z]/.test(path.basename(filePath))) {\r\n return 'unit';\r\n }\r\n\r\n // utils / helpers / services / lib\r\n if (/\\/(utils|helpers|services|lib)\\//.test(normalized)) {\r\n return 'unit';\r\n }\r\n\r\n // 默认 unit\r\n return 'unit';\r\n}\r\n\r\nexport function registerInitCommand(program: Command): void {\r\n program\r\n .command('init')\r\n .description('初始化测试项目 - 检测项目、生成配置、自动创建测试用例')\r\n .option('-f, --force', '强制覆盖已有配置文件')\r\n .action(async (options: InitOptions) => {\r\n try {\r\n await executeInit(options);\r\n } catch (error) {\r\n console.error(chalk.red(`\\n ✗ ${error instanceof Error ? error.message : String(error)}\\n`));\r\n process.exit(1);\r\n }\r\n });\r\n}\r\n\r\nasync function executeInit(options: InitOptions): Promise<void> {\r\n // 1. 检测项目\r\n const spinner = ora('正在检测项目结构...').start();\r\n const projectInfo = detectProject();\r\n spinner.stop();\r\n\r\n // 2. 显示检测结果\r\n displayProjectInfo(projectInfo);\r\n\r\n // 3. 非Vue项目警告\r\n if (!projectInfo.isVue) {\r\n const { proceed } = await inquirer.prompt([\r\n {\r\n type: 'confirm',\r\n name: 'proceed',\r\n message: '未检测到 Vue 项目,是否继续初始化?',\r\n default: false,\r\n },\r\n ]);\r\n if (!proceed) {\r\n console.log(chalk.gray('\\n 已取消初始化\\n'));\r\n return;\r\n }\r\n }\r\n\r\n // 4. 检查全局 AI 配置,未配置则引导配置\r\n let globalAI = loadGlobalAIConfig();\r\n if (!globalAI) {\r\n console.log(chalk.cyan(' AI 模型配置 (首次使用需配置,之后可通过 qat change 修改)\\n'));\r\n globalAI = await promptAIConfig();\r\n saveGlobalAIConfig(globalAI);\r\n console.log(chalk.gray(` 配置已保存至 ${getAIConfigPath()}\\n`));\r\n } else {\r\n // 同一行显示当前大模型\r\n console.log(chalk.green(` ✓ 当前 AI 模型: ${chalk.white(globalAI.model)} @ ${chalk.gray(globalAI.baseUrl)} (${maskApiKey(globalAI.apiKey)})\\n`));\r\n }\r\n\r\n const aiConfig = toAIConfig(globalAI);\r\n\r\n // 5. AI 连通性测试\r\n if (aiConfig.apiKey || aiConfig.baseUrl) {\r\n const testSpinner = ora(`正在测试 AI 连通性 (${globalAI.model})...`).start();\r\n try {\r\n const result = await testAIConnection(aiConfig);\r\n if (result.ok) {\r\n testSpinner.succeed(`AI 连通正常 ${chalk.gray(`${globalAI.model} (${result.latencyMs}ms)`)}`);\r\n } else {\r\n testSpinner.fail(`AI 连通异常: ${result.message}`);\r\n console.log(chalk.yellow(' 可运行 qat change 修改 AI 配置。'));\r\n }\r\n } catch (error) {\r\n testSpinner.fail(`AI 连通测试失败: ${error instanceof Error ? error.message : String(error)}`);\r\n }\r\n }\r\n\r\n // 6. 组装项目配置(AI 不写入 qat.config.js)\r\n const config = buildProjectConfig(projectInfo);\r\n\r\n // 7. 生成配置文件\r\n let configPath: string;\r\n const existingConfigPath = path.join(process.cwd(), 'qat.config.js');\r\n const existingTsPath = path.join(process.cwd(), 'qat.config.ts');\r\n const configExists = fs.existsSync(existingConfigPath) || fs.existsSync(existingTsPath);\r\n\r\n if (configExists && !options.force) {\r\n const { overwrite } = await inquirer.prompt([\r\n {\r\n type: 'confirm',\r\n name: 'overwrite',\r\n message: '配置文件 qat.config.js 已存在,是否覆盖?',\r\n default: true,\r\n },\r\n ]);\r\n if (!overwrite) {\r\n console.log(chalk.gray(' 保留现有配置文件,继续后续步骤...'));\r\n configPath = existingConfigPath;\r\n } else {\r\n const fileSpinner = ora('正在覆盖配置文件...').start();\r\n try {\r\n configPath = await writeConfigFile(process.cwd(), config, true);\r\n fileSpinner.succeed('配置文件已覆盖');\r\n } catch (error) {\r\n fileSpinner.fail('配置文件覆盖失败');\r\n throw error;\r\n }\r\n }\r\n } else {\r\n const fileSpinner = ora('正在生成配置文件...').start();\r\n try {\r\n configPath = await writeConfigFile(process.cwd(), config, options.force);\r\n fileSpinner.succeed('配置文件已生成');\r\n } catch (error) {\r\n fileSpinner.fail('配置文件生成失败');\r\n throw error;\r\n }\r\n }\r\n\r\n // 8. 创建测试目录结构\r\n const dirSpinner = ora('正在创建测试目录...').start();\r\n const createdDirs = createTestDirectories(config);\r\n dirSpinner.succeed('测试目录已创建');\r\n\r\n // 9. 初始化Mock路由\r\n if (config.mock?.enabled !== false) {\r\n const mockDir = config.mock?.routesDir || DEFAULT_CONFIG.mock.routesDir;\r\n initMockRoutesDir(mockDir);\r\n\r\n const srcDir = config.project?.srcDir || 'src';\r\n const apiCalls = scanAPICalls(srcDir);\r\n\r\n if (apiCalls.length > 0) {\r\n const mockRoutes = generateMockRoutesFromAPICalls(apiCalls);\r\n const mockFilePath = path.join(process.cwd(), mockDir, 'auto-generated.json');\r\n\r\n if (!fs.existsSync(mockFilePath)) {\r\n fs.writeFileSync(mockFilePath, JSON.stringify(mockRoutes, null, 2), 'utf-8');\r\n console.log(chalk.green(` 自动发现 ${apiCalls.length} 个 API 接口,已生成 Mock 路由`));\r\n }\r\n } else {\r\n console.log(chalk.gray(' 未发现 API 调用,已生成示例 Mock 路由'));\r\n }\r\n }\r\n\r\n // 10. 扫描源码 + 用户选择文件 + 生成测试用例\r\n const useAI = isAIAvailable(aiConfig);\r\n const generatedFiles = await autoGenerateTests(config, projectInfo, aiConfig, useAI);\r\n\r\n // 11. 输出结果\r\n displayResult(configPath, createdDirs, generatedFiles, projectInfo);\r\n}\r\n\r\n// ─── AI 配置向导 ────────────────────────────────────────────\r\n\r\nasync function promptAIConfig() {\r\n const answers = await inquirer.prompt([\r\n {\r\n type: 'input',\r\n name: 'apiKey',\r\n message: 'API Key (Ollama 本地可留空):',\r\n default: '',\r\n },\r\n {\r\n type: 'input',\r\n name: 'baseUrl',\r\n message: 'API Base URL:',\r\n default: 'https://api.deepseek.com/v1',\r\n validate: (input: string) => {\r\n if (!input.trim()) return 'Base URL 不能为空';\r\n if (!input.trim().startsWith('http')) return 'URL 必须以 http(s):// 开头';\r\n return true;\r\n },\r\n },\r\n {\r\n type: 'input',\r\n name: 'model',\r\n message: '模型名称:',\r\n default: 'deepseek-chat',\r\n validate: (input: string) => {\r\n if (!input.trim()) return '模型名称不能为空';\r\n return true;\r\n },\r\n },\r\n ]);\r\n\r\n return {\r\n provider: 'openai',\r\n apiKey: answers.apiKey?.trim() || '',\r\n baseUrl: answers.baseUrl?.trim() || 'https://api.deepseek.com/v1',\r\n model: answers.model?.trim() || 'deepseek-chat',\r\n };\r\n}\r\n\r\n// ─── 组装项目配置(不含 AI) ──────────────────────────────\r\n\r\nfunction buildProjectConfig(projectInfo: ReturnType<typeof detectProject>): Partial<QATConfig> {\r\n return {\r\n project: {\r\n framework: projectInfo.framework,\r\n uiLibrary: projectInfo.uiLibrary !== 'none' ? projectInfo.uiLibrary : undefined,\r\n monorepo: projectInfo.monorepo !== 'none' ? projectInfo.monorepo : undefined,\r\n vite: projectInfo.isVite,\r\n srcDir: projectInfo.srcDir,\r\n appDir: projectInfo.appDirs.length > 0 ? projectInfo.appDirs[0] : undefined,\r\n },\r\n vitest: {\r\n enabled: true,\r\n coverage: true,\r\n globals: true,\r\n environment: 'happy-dom',\r\n },\r\n playwright: {\r\n enabled: true,\r\n browsers: ['chromium'],\r\n baseURL: 'http://localhost:5173',\r\n screenshot: 'only-on-failure',\r\n },\r\n visual: {\r\n enabled: true,\r\n threshold: 0.1,\r\n baselineDir: 'tests/visual/baseline',\r\n diffDir: 'tests/visual/diff',\r\n },\r\n lighthouse: {\r\n enabled: true,\r\n urls: ['http://localhost:5173'],\r\n runs: 3,\r\n thresholds: {\r\n performance: 80,\r\n accessibility: 90,\r\n },\r\n },\r\n mock: {\r\n enabled: true,\r\n port: 3456,\r\n routesDir: 'tests/mock/routes',\r\n },\r\n };\r\n}\r\n\r\n// ─── 自动扫描 + 用户选择 + AI 生成测试用例 ────────────────\r\n\r\nasync function autoGenerateTests(\r\n config: Partial<QATConfig>,\r\n projectInfo: ReturnType<typeof detectProject>,\r\n aiConfig: import('../types/index.js').AIConfig,\r\n useAI: boolean,\r\n): Promise<string[]> {\r\n const srcDir = config.project?.srcDir || 'src';\r\n\r\n // 扫描源码文件\r\n const components = discoverVueComponents(process.cwd(), srcDir);\r\n const utilities = discoverUtilityFiles(process.cwd(), srcDir);\r\n\r\n if (components.length === 0 && utilities.length === 0) {\r\n console.log(chalk.yellow('\\n 未发现可测试的源码文件,跳过测试用例生成。'));\r\n return [];\r\n }\r\n\r\n // 合并所有文件并智能分类\r\n const allTargets: { filePath: string; testType: TestType }[] = [];\r\n\r\n for (const compPath of components.slice(0, 30)) {\r\n allTargets.push({ filePath: compPath, testType: inferTestType(compPath) });\r\n }\r\n\r\n for (const utilPath of utilities.slice(0, 30)) {\r\n allTargets.push({ filePath: utilPath, testType: inferTestType(utilPath) });\r\n }\r\n\r\n // 让用户多选要生成测试的文件\r\n const selectedTargets = await selectTargetFiles(allTargets);\r\n if (selectedTargets.length === 0) {\r\n console.log(chalk.gray('\\n 未选择任何文件,跳过测试用例生成。'));\r\n return [];\r\n }\r\n\r\n const total = selectedTargets.length;\r\n const genSpinner = ora(`正在生成测试用例 [0/${total}] ...`).start();\r\n\r\n const generatedFiles: string[] = [];\r\n const typeCount: Record<string, number> = {};\r\n const reviewReport: ReviewReportEntry[] = [];\r\n let current = 0;\r\n let failed = 0;\r\n\r\n for (const { filePath, testType } of selectedTargets) {\r\n current++;\r\n const fileLabel = path.basename(filePath);\r\n genSpinner.text = `正在生成测试用例 [${current}/${total}] ${chalk.cyan(fileLabel)} ...`;\r\n\r\n try {\r\n const result = await generateTestForTarget(\r\n testType,\r\n filePath,\r\n config,\r\n projectInfo,\r\n aiConfig,\r\n useAI,\r\n );\r\n if (result) {\r\n generatedFiles.push(result.filePath);\r\n typeCount[testType] = (typeCount[testType] || 0) + 1;\r\n if (result.reviewEntry) {\r\n reviewReport.push(result.reviewEntry);\r\n }\r\n }\r\n } catch {\r\n failed++;\r\n }\r\n }\r\n\r\n if (generatedFiles.length > 0) {\r\n const summary = Object.entries(typeCount)\r\n .map(([type, count]) => `${count} ${type}`)\r\n .join(', ');\r\n const approvedCount = reviewReport.filter((r) => r.approved).length;\r\n let msg = `已生成 ${generatedFiles.length}/${total} 个测试用例 (${summary}) — AI 审计 ${approvedCount}/${reviewReport.length} 通过`;\r\n if (failed > 0) msg += chalk.yellow(` ${failed} 个失败`);\r\n genSpinner.succeed(msg);\r\n } else {\r\n genSpinner.warn(`未生成测试用例 (${failed} 个失败)`);\r\n }\r\n\r\n // 打印审计报告\r\n if (reviewReport.length > 0) {\r\n printReviewReport(reviewReport);\r\n }\r\n\r\n return generatedFiles;\r\n}\r\n\r\n/**\r\n * 让用户多选要生成测试的文件\r\n */\r\nasync function selectTargetFiles(\r\n allTargets: { filePath: string; testType: TestType }[],\r\n): Promise<{ filePath: string; testType: TestType }[]> {\r\n const testTypeLabels: Record<TestType, string> = {\r\n unit: chalk.blue('[unit]'),\r\n component: chalk.magenta('[comp]'),\r\n e2e: chalk.green('[e2e]'),\r\n api: chalk.yellow('[api]'),\r\n visual: chalk.cyan('[visual]'),\r\n performance: chalk.gray('[perf]'),\r\n };\r\n\r\n const choices = allTargets.map(({ filePath, testType }) => ({\r\n name: `${testTypeLabels[testType]} ${filePath}`,\r\n value: filePath,\r\n short: filePath,\r\n }));\r\n\r\n // 添加手动输入选项\r\n choices.push({\r\n name: chalk.gray('✎ 手动输入文件/目录路径'),\r\n value: '__manual__',\r\n short: '手动输入',\r\n });\r\n\r\n const { selected } = await inquirer.prompt([\r\n {\r\n type: 'checkbox',\r\n name: 'selected',\r\n message: '选择要生成测试用例的文件 (空格选择/取消,回车确认):',\r\n choices,\r\n pageSize: 15,\r\n },\r\n ]);\r\n\r\n // 处理手动输入\r\n const manualPaths: string[] = [];\r\n if ((selected as string[]).includes('__manual__')) {\r\n const { manualInput } = await inquirer.prompt([\r\n {\r\n type: 'input',\r\n name: 'manualInput',\r\n message: '输入文件或目录路径(多个用逗号分隔):',\r\n default: '',\r\n filter: (input: string) =>\r\n input\r\n .split(',')\r\n .map((s: string) => s.trim())\r\n .filter(Boolean),\r\n },\r\n ]);\r\n\r\n // 验证路径是否存在,展开目录\r\n for (const p of manualInput as string[]) {\r\n const resolved = path.resolve(process.cwd(), p);\r\n if (fs.existsSync(resolved)) {\r\n const stat = fs.statSync(resolved);\r\n if (stat.isDirectory()) {\r\n // 展开目录下的 .vue / .ts / .js 文件\r\n const dirFiles = walkDirForTestableFiles(resolved);\r\n manualPaths.push(...dirFiles);\r\n } else if (stat.isFile()) {\r\n manualPaths.push(p.replace(/\\\\/g, '/'));\r\n }\r\n } else {\r\n console.log(chalk.yellow(` 路径不存在,已跳过: ${p}`));\r\n }\r\n }\r\n }\r\n\r\n // 根据用户选择过滤(排除手动输入标记)\r\n const selectedPaths = (selected as string[]).filter((s) => s !== '__manual__');\r\n const selectedSet = new Set(selectedPaths);\r\n\r\n const result = allTargets.filter((t) => selectedSet.has(t.filePath));\r\n\r\n // 添加手动输入的文件(推断测试类型)\r\n const existingPaths = new Set(result.map((t) => t.filePath));\r\n for (const mp of manualPaths) {\r\n if (!existingPaths.has(mp)) {\r\n result.push({ filePath: mp, testType: inferTestType(mp) });\r\n }\r\n }\r\n\r\n return result;\r\n}\r\n\r\n/**\r\n * 递归扫描目录中的可测试文件(.vue / .ts / .js)\r\n */\r\nfunction walkDirForTestableFiles(dir: string): string[] {\r\n const files: string[] = [];\r\n try {\r\n const entries = fs.readdirSync(dir, { withFileTypes: true });\r\n for (const entry of entries) {\r\n if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name.startsWith('.')) continue;\r\n const fullPath = path.join(dir, entry.name);\r\n if (entry.isDirectory()) {\r\n files.push(...walkDirForTestableFiles(fullPath));\r\n } else if (entry.isFile() && /\\.(vue|ts|js)$/.test(entry.name)) {\r\n files.push(path.relative(process.cwd(), fullPath).replace(/\\\\/g, '/'));\r\n }\r\n }\r\n } catch {\r\n // 权限不足等情况跳过\r\n }\r\n return files;\r\n}\r\n\r\n/**\r\n * 为单个目标文件生成测试(含 AI 审计)\r\n */\r\nasync function generateTestForTarget(\r\n testType: TestType,\r\n targetPath: string,\r\n config: Partial<QATConfig>,\r\n projectInfo: ReturnType<typeof detectProject>,\r\n aiConfig: import('../types/index.js').AIConfig,\r\n useAI: boolean,\r\n): Promise<{ filePath: string; reviewEntry?: ReviewReportEntry } | null> {\r\n const basename = path.basename(targetPath, path.extname(targetPath));\r\n const name = basename.replace(/[^a-zA-Z0-9]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') || `${testType}-test`;\r\n const outputDir = TEST_TYPE_DIR[testType];\r\n const fileName = `${name}.${testType === 'e2e' ? 'spec' : 'test'}.ts`;\r\n const filePath = path.join(process.cwd(), outputDir, fileName);\r\n\r\n // 不覆盖已有文件\r\n if (fs.existsSync(filePath)) return null;\r\n\r\n let content: string;\r\n let reviewEntry: ReviewReportEntry | undefined;\r\n\r\n if (useAI) {\r\n // AI 生成 + 审计员审核\r\n const result = await generateWithAIAndReview(testType, name, targetPath, aiConfig, projectInfo);\r\n content = result.code;\r\n reviewEntry = result.reviewEntry;\r\n } else {\r\n // 源码分析 + 模板渲染\r\n const analysis = analyzeFile(targetPath);\r\n content = renderTemplate(testType, {\r\n name,\r\n target: targetPath,\r\n framework: projectInfo.framework,\r\n vueVersion: projectInfo.vueVersion,\r\n typescript: projectInfo.typescript,\r\n uiLibrary: projectInfo.uiLibrary,\r\n extraImports: projectInfo.componentTestSetup?.extraImports,\r\n globalPlugins: projectInfo.componentTestSetup?.globalPlugins,\r\n globalStubs: projectInfo.componentTestSetup?.globalStubs,\r\n mountOptions: projectInfo.componentTestSetup?.mountOptions,\r\n exports: analysis.exports.map((e) => ({\r\n name: e.name,\r\n kind: e.kind,\r\n params: e.params,\r\n isAsync: e.isAsync,\r\n returnType: e.returnType,\r\n })),\r\n props: analysis.vueAnalysis?.props.map((p: PropInfo) => ({\r\n name: p.name,\r\n type: p.type,\r\n required: p.required,\r\n defaultValue: p.defaultValue,\r\n })),\r\n emits: analysis.vueAnalysis?.emits.map((e: EmitInfo) => ({\r\n name: e.name,\r\n params: e.params,\r\n })),\r\n methods: analysis.vueAnalysis?.methods,\r\n computed: analysis.vueAnalysis?.computed,\r\n });\r\n }\r\n\r\n // 确保目录存在\r\n if (!fs.existsSync(path.join(process.cwd(), outputDir))) {\r\n fs.mkdirSync(path.join(process.cwd(), outputDir), { recursive: true });\r\n }\r\n\r\n fs.writeFileSync(filePath, content, 'utf-8');\r\n return {\r\n filePath: path.relative(process.cwd(), filePath).replace(/\\\\/g, '/'),\r\n reviewEntry,\r\n };\r\n}\r\n\r\n/**\r\n * AI 辅助生成测试用例 + 审计员审核\r\n */\r\nasync function generateWithAIAndReview(\r\n type: TestType,\r\n name: string,\r\n target: string,\r\n aiConfig: import('../types/index.js').AIConfig,\r\n projectInfo: ReturnType<typeof detectProject>,\r\n): Promise<{ code: string; reviewEntry: ReviewReportEntry }> {\r\n // 读取源码\r\n const fullPath = path.resolve(process.cwd(), target);\r\n let sourceCode = '';\r\n if (fs.existsSync(fullPath)) {\r\n sourceCode = fs.readFileSync(fullPath, 'utf-8');\r\n }\r\n\r\n // 源码分析 - 给 AI 更精准的上下文\r\n const analysis = analyzeFile(target);\r\n\r\n const analysisSummary = analysis.exports.length > 0 || analysis.vueAnalysis ? {\r\n exports: analysis.exports.map((e) => ({\r\n name: e.name,\r\n kind: e.kind,\r\n params: e.params,\r\n isAsync: e.isAsync,\r\n })),\r\n props: analysis.vueAnalysis?.props.map((p: PropInfo) => ({\r\n name: p.name,\r\n type: p.type,\r\n required: p.required,\r\n })),\r\n emits: analysis.vueAnalysis?.emits.map((e: EmitInfo) => ({\r\n name: e.name,\r\n params: e.params,\r\n })),\r\n methods: analysis.vueAnalysis?.methods,\r\n computed: analysis.vueAnalysis?.computed,\r\n } : undefined;\r\n\r\n // 使用审计流程生成\r\n const reviewResult = await generateWithReview({\r\n testType: type,\r\n targetPath: target,\r\n sourceCode,\r\n analysis: analysisSummary,\r\n aiConfig,\r\n framework: 'vue',\r\n });\r\n\r\n // 构建文件头注释\r\n const headerComment = [\r\n `// AI Generated Test - ${name}`,\r\n `// 审计: ${reviewResult.approved ? '通过' : '未通过'} (${(reviewResult.reviewScore * 100).toFixed(0)}%) — ${reviewResult.attempts}次尝试`,\r\n reviewResult.reviewFeedback ? `// 审计意见: ${reviewResult.reviewFeedback}` : '',\r\n '',\r\n ].filter(Boolean).join('\\n');\r\n\r\n const code = `${headerComment}\\n${reviewResult.code}`;\r\n\r\n // 构建审计报告条目\r\n const reviewEntry: ReviewReportEntry = {\r\n target,\r\n testType: type,\r\n approved: reviewResult.approved,\r\n attempts: reviewResult.attempts,\r\n score: reviewResult.reviewScore,\r\n feedback: reviewResult.reviewFeedback,\r\n issues: reviewResult.approved ? [] : reviewResult.reviewIssues,\r\n };\r\n\r\n return { code, reviewEntry };\r\n}\r\n\r\n// ─── 显示项目检测信息 ──────────────────────────────────────\r\n\r\nfunction displayProjectInfo(info: ReturnType<typeof detectProject>): void {\r\n console.log(chalk.cyan('\\n 项目检测结果:'));\r\n console.log(chalk.gray(' ─────────────────────────────'));\r\n\r\n const items: [string, string | boolean | number | undefined][] = [\r\n ['项目名称', info.name],\r\n ['框架', info.frameworkConfidence > 0.5\r\n ? `${info.frameworkDisplayName} (置信度 ${Math.round(info.frameworkConfidence * 100)}%)`\r\n : info.frameworkDisplayName],\r\n ['Vue 项目', info.isVue ? `是 (v${info.vueVersion})` : '否'],\r\n ['UI 组件库', info.uiLibrary !== 'none' ? info.uiLibrary : '未检测到'],\r\n ['Vite 构建', info.isVite ? '是' : '否'],\r\n ['TypeScript', info.typescript ? '是' : '否'],\r\n ['包管理器', info.packageManager],\r\n ['Monorepo', info.monorepo !== 'none' ? info.monorepo : '否'],\r\n ['源码目录', info.srcDir],\r\n ];\r\n\r\n if (info.appDirs.length > 0) {\r\n items.push(['子项目', info.appDirs.join(', ')]);\r\n }\r\n\r\n if (info.testFrameworks.length > 0) {\r\n items.push(['已有测试框架', info.testFrameworks.join(', ')]);\r\n }\r\n\r\n if (info.componentDirs.length > 0) {\r\n items.push(['组件目录', info.componentDirs.join(', ')]);\r\n }\r\n\r\n for (const [label, value] of items) {\r\n const displayValue = value === true ? chalk.green('✓') : value === false ? chalk.red('✗') : String(value);\r\n console.log(` ${chalk.white(label.padEnd(12))} ${displayValue}`);\r\n }\r\n\r\n console.log();\r\n}\r\n\r\n// ─── 创建测试目录结构 ──────────────────────────────────────\r\n\r\nfunction createTestDirectories(config: Partial<QATConfig>): string[] {\r\n const dirs: string[] = [];\r\n\r\n const dirMap: Record<string, boolean> = {\r\n 'tests': true,\r\n 'tests/unit': config.vitest?.enabled !== false,\r\n 'tests/component': config.vitest?.enabled !== false,\r\n 'tests/e2e': config.playwright?.enabled !== false,\r\n 'tests/api': config.mock?.enabled !== false,\r\n 'tests/visual': config.visual?.enabled !== false,\r\n 'tests/visual/baseline': config.visual?.enabled !== false,\r\n 'tests/visual/diff': config.visual?.enabled !== false,\r\n 'tests/mock': config.mock?.enabled !== false,\r\n 'tests/mock/routes': config.mock?.enabled !== false,\r\n };\r\n\r\n for (const [dir, shouldCreate] of Object.entries(dirMap)) {\r\n if (shouldCreate) {\r\n const fullPath = path.join(process.cwd(), dir);\r\n if (!fs.existsSync(fullPath)) {\r\n fs.mkdirSync(fullPath, { recursive: true });\r\n dirs.push(dir);\r\n }\r\n }\r\n }\r\n\r\n return dirs;\r\n}\r\n\r\n// ─── 显示最终结果 ──────────────────────────────────────────\r\n\r\nfunction displayResult(\r\n configPath: string,\r\n createdDirs: string[],\r\n generatedFiles: string[],\r\n projectInfo: ReturnType<typeof detectProject>,\r\n): void {\r\n const relativeConfig = path.relative(process.cwd(), configPath);\r\n\r\n console.log(chalk.green('\\n ✓ 项目初始化完成!\\n'));\r\n\r\n // 配置文件\r\n console.log(chalk.white(' 已生成配置:'));\r\n console.log(chalk.gray(` ${relativeConfig}`));\r\n console.log();\r\n\r\n // 生成的测试用例(按类型分组)\r\n if (generatedFiles.length > 0) {\r\n console.log(chalk.white(' 已生成测试用例:'));\r\n for (const file of generatedFiles) {\r\n const typeLabel = file.includes('/unit/') ? chalk.blue('[unit]')\r\n : file.includes('/component/') ? chalk.magenta('[comp]')\r\n : file.includes('/api/') ? chalk.yellow('[api]')\r\n : chalk.gray('[other]');\r\n console.log(` ${typeLabel} ${chalk.gray(file)}`);\r\n }\r\n console.log();\r\n }\r\n\r\n // 下一步建议\r\n console.log(chalk.cyan(' 下一步:'));\r\n if (generatedFiles.length > 0) {\r\n console.log(chalk.gray(' 1. qat run 执行测试'));\r\n console.log(chalk.gray(' 2. qat create 添加更多测试用例'));\r\n console.log(chalk.gray(' 3. qat status 查看 AI 模型状态'));\r\n } else {\r\n console.log(chalk.gray(' 1. qat change 配置 AI 模型'));\r\n console.log(chalk.gray(' 2. qat create 创建测试用例'));\r\n }\r\n\r\n if (projectInfo.testFrameworks.length === 0) {\r\n console.log();\r\n console.log(chalk.yellow(' ⚠ 未检测到测试框架依赖,建议运行:'));\r\n console.log(chalk.gray(` qat setup`));\r\n }\r\n\r\n console.log();\r\n}\r\n\r\n/**\r\n * 构建目录树字符串\r\n */\r\nfunction buildDirectoryTree(dirs: string[]): string {\r\n if (dirs.length === 0) return ' (无新目录)';\r\n\r\n const tree: Record<string, string[]> = {};\r\n for (const dir of dirs) {\r\n const parts = dir.split('/');\r\n if (parts.length >= 2 && parts[0] === 'tests') {\r\n const parent = parts[0];\r\n const child = parts.slice(1).join('/');\r\n if (!tree[parent]) tree[parent] = [];\r\n tree[parent].push(child);\r\n } else {\r\n if (!tree[dir]) tree[dir] = [];\r\n }\r\n }\r\n\r\n let result = '';\r\n const topDirs = Object.keys(tree).sort();\r\n topDirs.forEach((dir, i) => {\r\n const isLast = i === topDirs.length - 1;\r\n const prefix = isLast ? '└── ' : '├── ';\r\n result += ` ${prefix}${dir}/\\n`;\r\n\r\n const children = tree[dir].sort();\r\n children.forEach((child, j) => {\r\n const isLastChild = j === children.length - 1;\r\n const childPrefix = isLast ? ' ' : '│ ';\r\n const connector = isLastChild ? '└── ' : '├── ';\r\n result += ` ${childPrefix}${connector}${child}/\\n`;\r\n });\r\n });\r\n\r\n return result;\r\n}\r\n","/**\r\n * 项目检测服务 - 识别框架版本、依赖、目录结构、组件发现\r\n * 集成框架注册表,支持 Vue / Vben / Nuxt 等框架自动检测\r\n */\r\n\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport type { FrameworkType, UILibrary, MonorepoType } from '../types/index.js';\r\nimport { detectFramework, discoverAppDirs, detectUILibrary, detectMonorepo } from './framework-registry.js';\r\nimport type { ComponentTestSetup } from './framework-registry.js';\r\n\r\nexport interface ProjectInfo {\r\n /** 是否为 Vue 项目 */\r\n isVue: boolean;\r\n /** 是否使用 Vite 构建 */\r\n isVite: boolean;\r\n /** Vue 主版本号 */\r\n vueVersion?: 2 | 3;\r\n /** 是否使用 TypeScript */\r\n typescript: boolean;\r\n /** 源码目录 */\r\n srcDir: string;\r\n /** 包管理器 */\r\n packageManager: 'npm' | 'yarn' | 'pnpm' | 'bun';\r\n /** 所有依赖名列表 */\r\n dependencies: string[];\r\n /** 组件目录列表 */\r\n componentDirs: string[];\r\n /** 页面/视图目录列表 */\r\n pageDirs: string[];\r\n /** 是否已有测试目录 */\r\n hasTests: boolean;\r\n /** 已有的测试框架 */\r\n testFrameworks: string[];\r\n /** 项目名称 */\r\n name: string;\r\n /** 检测到的框架类型 */\r\n framework: FrameworkType;\r\n /** 框架显示名称 */\r\n frameworkDisplayName: string;\r\n /** UI 组件库 */\r\n uiLibrary: UILibrary;\r\n /** Monorepo 类型 */\r\n monorepo: MonorepoType;\r\n /** Monorepo 子项目路径 */\r\n appDirs: string[];\r\n /** 框架检测置信度 */\r\n frameworkConfidence: number;\r\n /** 组件测试配置 */\r\n componentTestSetup?: ComponentTestSetup;\r\n}\r\n\r\n/**\r\n * 检测 Vue 版本 — 兼容 Monorepo,从子项目 package.json 中读取\r\n * 优先级:子项目 vue 依赖 > 根目录 vue 依赖\r\n */\r\nfunction detectVueVersion(cwd: string, rootDeps: Record<string, string>): 2 | 3 | null {\r\n // 1. 先尝试根目录\r\n if (rootDeps['vue']) {\r\n const v = parseVueMajorVersion(rootDeps['vue']);\r\n if (v) return v;\r\n }\r\n\r\n // 2. Monorepo: 遍历子项目查找 vue 依赖\r\n const appDirs = discoverAppDirs(cwd);\r\n for (const appDir of appDirs) {\r\n const subPkgPath = path.join(cwd, appDir, 'package.json');\r\n if (fs.existsSync(subPkgPath)) {\r\n try {\r\n const subPkg = JSON.parse(fs.readFileSync(subPkgPath, 'utf-8'));\r\n const subDeps = {\r\n ...(subPkg.dependencies as Record<string, string>),\r\n ...(subPkg.devDependencies as Record<string, string>),\r\n };\r\n if (subDeps['vue']) {\r\n const v = parseVueMajorVersion(subDeps['vue']);\r\n if (v) return v;\r\n }\r\n } catch {\r\n // 解析失败跳过\r\n }\r\n }\r\n }\r\n\r\n // 3. 检查 packages/ 目录下的子包\r\n const packagesPath = path.join(cwd, 'packages');\r\n if (fs.existsSync(packagesPath)) {\r\n try {\r\n const entries = fs.readdirSync(packagesPath, { withFileTypes: true });\r\n for (const entry of entries) {\r\n if (!entry.isDirectory()) continue;\r\n const subPkgPath = path.join(packagesPath, entry.name, 'package.json');\r\n if (fs.existsSync(subPkgPath)) {\r\n try {\r\n const subPkg = JSON.parse(fs.readFileSync(subPkgPath, 'utf-8'));\r\n const subDeps = {\r\n ...(subPkg.dependencies as Record<string, string>),\r\n ...(subPkg.devDependencies as Record<string, string>),\r\n };\r\n if (subDeps['vue']) {\r\n const v = parseVueMajorVersion(subDeps['vue']);\r\n if (v) return v;\r\n }\r\n } catch {\r\n // 解析失败跳过\r\n }\r\n }\r\n }\r\n } catch {\r\n // 目录读取失败跳过\r\n }\r\n }\r\n\r\n return null;\r\n}\r\n\r\n/**\r\n * 从 vue 依赖版本字符串中解析主版本号\r\n */\r\nfunction parseVueMajorVersion(versionStr: string): 2 | 3 | null {\r\n const clean = versionStr.replace(/^[\\^~>=<\\s]+/, '');\r\n // 处理 workspace:* 或 file: 协议 — 无法从版本号判断,返回 null 让后续逻辑推断\r\n if (clean === '*' || clean.startsWith('workspace:') || clean.startsWith('file:')) {\r\n return null;\r\n }\r\n const major = parseInt(clean.split('.')[0], 10);\r\n if (isNaN(major)) return null;\r\n return major >= 3 ? 3 : 2;\r\n}\r\n\r\n/**\r\n * 当版本号无法直接解析时,通过其他线索推断 Vue 主版本号\r\n * - 检查 vue-demi(Vue 2/3 通用库,通常与 Vue 2 组合式 API 一起使用)\r\n * - 检查 @vue/composition-api(仅 Vue 2)\r\n * - 检查 nuxt(Nuxt 3 = Vue 3,Nuxt 2 = Vue 2)\r\n * - 默认推断为 Vue 3(2024+ 新项目的主流)\r\n */\r\nfunction inferVueVersion(cwd: string, allDeps: Record<string, string>): 2 | 3 {\r\n // Nuxt 3 = Vue 3\r\n if (allDeps['nuxt']) {\r\n const nuxtVer = allDeps['nuxt'].replace(/^[\\^~>=<\\s]+/, '');\r\n const major = parseInt(nuxtVer.split('.')[0], 10);\r\n if (!isNaN(major) && major >= 3) return 3;\r\n if (!isNaN(major) && major < 3) return 2;\r\n }\r\n\r\n // @vue/composition-api 仅 Vue 2 使用\r\n if (allDeps['@vue/composition-api']) return 2;\r\n\r\n // vue-demi 的存在暗示可能使用了组合式 API,但不确定版本\r\n // 检查是否有 Vue 3 特有的依赖\r\n const vue3Indicators = [\r\n 'vue-tsc', // Vue 3 TypeScript 支持\r\n '@vue/compiler-sfc', // Vue 3 SFC 编译器\r\n 'unplugin-vue', // Vue 3 Vite 插件\r\n 'vite-plugin-vue', // Vue 3 Vite 插件\r\n ];\r\n for (const dep of vue3Indicators) {\r\n if (allDeps[dep]) return 3;\r\n }\r\n\r\n // 检查 vite.config 文件中是否有 vue 插件\r\n const viteConfigPaths = ['vite.config.ts', 'vite.config.js'];\r\n for (const cfg of viteConfigPaths) {\r\n const cfgPath = path.join(cwd, cfg);\r\n if (fs.existsSync(cfgPath)) {\r\n try {\r\n const content = fs.readFileSync(cfgPath, 'utf-8');\r\n // @vitejs/plugin-vue2 或 vite-plugin-vue2 明确是 Vue 2\r\n if (content.includes('plugin-vue2') || content.includes('vite-plugin-vue2')) return 2;\r\n // @vitejs/plugin-vue 是 Vue 3\r\n if (content.includes('@vitejs/plugin-vue') || content.includes('unplugin-vue')) return 3;\r\n } catch {\r\n // 读取失败跳过\r\n }\r\n }\r\n }\r\n\r\n // 默认推断为 Vue 3\r\n return 3;\r\n}\r\n\r\n/**\r\n * 检测项目信息\r\n */\r\nexport function detectProject(cwd: string = process.cwd()): ProjectInfo {\r\n const info: ProjectInfo = {\r\n isVue: false,\r\n isVite: false,\r\n typescript: false,\r\n srcDir: 'src',\r\n packageManager: 'npm',\r\n dependencies: [],\r\n componentDirs: [],\r\n pageDirs: [],\r\n hasTests: false,\r\n testFrameworks: [],\r\n name: path.basename(cwd),\r\n framework: 'vue',\r\n frameworkDisplayName: 'Vue',\r\n uiLibrary: 'none',\r\n monorepo: 'none',\r\n appDirs: [],\r\n frameworkConfidence: 0,\r\n };\r\n\r\n // 读取 package.json\r\n const pkgPath = path.join(cwd, 'package.json');\r\n let pkg: Record<string, unknown> = {};\r\n let allDeps: Record<string, string> = {};\r\n\r\n if (fs.existsSync(pkgPath)) {\r\n try {\r\n pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));\r\n } catch {\r\n // package.json 解析失败,跳过\r\n }\r\n\r\n allDeps = {\r\n ...(pkg.dependencies as Record<string, string>),\r\n ...(pkg.devDependencies as Record<string, string>),\r\n };\r\n\r\n info.dependencies = Object.keys(allDeps);\r\n info.name = (pkg.name as string) || info.name;\r\n\r\n // 检测 Vue(优先从子项目 package.json 读取,兼容 Monorepo)\r\n const vueVersion = detectVueVersion(cwd, allDeps);\r\n if (vueVersion) {\r\n info.isVue = true;\r\n info.vueVersion = vueVersion;\r\n } else if (allDeps['vue']) {\r\n // vue 依赖存在但版本号无法解析(如 workspace:*),仍标记为 Vue 项目\r\n info.isVue = true;\r\n info.vueVersion = inferVueVersion(cwd, allDeps);\r\n } else {\r\n // 无 vue 依赖,尝试从文件系统检测(项目中有 .vue 文件)\r\n info.isVue = detectVueByFileSystem(cwd);\r\n if (info.isVue) {\r\n info.vueVersion = inferVueVersion(cwd, allDeps);\r\n }\r\n }\r\n\r\n // 检测 Vite\r\n info.isVite = !!allDeps['vite'] || fs.existsSync(path.join(cwd, 'vite.config.ts')) || fs.existsSync(path.join(cwd, 'vite.config.js'));\r\n\r\n // 检测 TypeScript\r\n info.typescript =\r\n !!allDeps['typescript'] ||\r\n fs.existsSync(path.join(cwd, 'tsconfig.json')) ||\r\n fs.existsSync(path.join(cwd, 'tsconfig.app.json'));\r\n\r\n // 检测已有测试框架\r\n if (allDeps['vitest']) info.testFrameworks.push('vitest');\r\n if (allDeps['@playwright/test']) info.testFrameworks.push('playwright');\r\n if (allDeps['jest']) info.testFrameworks.push('jest');\r\n if (allDeps['cypress']) info.testFrameworks.push('cypress');\r\n if (allDeps['@vue/test-utils']) info.testFrameworks.push('@vue/test-utils');\r\n }\r\n\r\n // 获取根目录文件列表(用于框架检测)\r\n const rootFiles = getRootFiles(cwd);\r\n\r\n // 使用框架注册表检测\r\n const frameworkResult = detectFramework({\r\n cwd,\r\n dependencies: allDeps,\r\n rootFiles,\r\n });\r\n\r\n if (frameworkResult) {\r\n info.framework = frameworkResult.framework;\r\n info.frameworkDisplayName = frameworkResult.displayName;\r\n info.frameworkConfidence = frameworkResult.confidence;\r\n info.uiLibrary = frameworkResult.uiLibrary;\r\n info.monorepo = frameworkResult.monorepo;\r\n info.appDirs = frameworkResult.appDirs;\r\n info.srcDir = frameworkResult.srcDir;\r\n info.componentTestSetup = frameworkResult.componentTestSetup;\r\n\r\n // 框架注册表返回的目录\r\n info.componentDirs = frameworkResult.componentDirs.filter((d) =>\r\n fs.existsSync(path.join(cwd, d)),\r\n );\r\n info.pageDirs = frameworkResult.pageDirs.filter((d) =>\r\n fs.existsSync(path.join(cwd, d)),\r\n );\r\n\r\n // Vben / Nuxt 本质也是 Vue\r\n if (frameworkResult.framework === 'vben' || frameworkResult.framework === 'nuxt') {\r\n info.isVue = true;\r\n info.vueVersion = 3;\r\n }\r\n\r\n // 框架注册表检测到 Vue 类型时,确保 isVue 被标记\r\n if (frameworkResult.framework === 'vue' && !info.isVue) {\r\n info.isVue = true;\r\n }\r\n\r\n // 框架注册表检测到 Vue 但 vueVersion 未设置时,推断版本\r\n if (info.isVue && !info.vueVersion) {\r\n info.vueVersion = detectVueVersion(cwd, allDeps) || inferVueVersion(cwd, allDeps);\r\n }\r\n } else {\r\n // 回退到原始检测逻辑\r\n info.srcDir = detectSrcDir(cwd);\r\n info.uiLibrary = detectUILibrary(allDeps);\r\n info.monorepo = detectMonorepo(cwd, rootFiles);\r\n info.appDirs = discoverAppDirs(cwd);\r\n info.componentDirs = discoverComponentDirs(cwd, info.srcDir);\r\n info.pageDirs = discoverPageDirs(cwd, info.srcDir);\r\n }\r\n\r\n // 检测已有测试目录\r\n const possibleTestDirs = ['tests', 'test', '__tests__', 'spec'];\r\n for (const dir of possibleTestDirs) {\r\n if (fs.existsSync(path.join(cwd, dir))) {\r\n info.hasTests = true;\r\n break;\r\n }\r\n }\r\n\r\n // 检测包管理器\r\n if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) {\r\n info.packageManager = 'pnpm';\r\n } else if (fs.existsSync(path.join(cwd, 'yarn.lock'))) {\r\n info.packageManager = 'yarn';\r\n } else if (fs.existsSync(path.join(cwd, 'bun.lockb'))) {\r\n info.packageManager = 'bun';\r\n }\r\n\r\n return info;\r\n}\r\n\r\n/**\r\n * 通过文件系统检测是否为 Vue 项目\r\n * 当 package.json 中没有 vue 依赖时,检查源码目录中是否存在 .vue 文件\r\n */\r\nfunction detectVueByFileSystem(cwd: string): boolean {\r\n const possibleSrcDirs = ['src', 'lib', 'app'];\r\n for (const srcDir of possibleSrcDirs) {\r\n const srcPath = path.join(cwd, srcDir);\r\n if (fs.existsSync(srcPath)) {\r\n try {\r\n if (hasVueFilesInDir(srcPath, 2)) return true;\r\n } catch {\r\n // 权限不足等情况跳过\r\n }\r\n }\r\n }\r\n\r\n // 也检查根目录下的 components / pages\r\n const rootVueDirs = ['components', 'pages', 'views'];\r\n for (const dir of rootVueDirs) {\r\n const dirPath = path.join(cwd, dir);\r\n if (fs.existsSync(dirPath)) {\r\n try {\r\n if (hasVueFilesInDir(dirPath, 1)) return true;\r\n } catch {\r\n // 权限不足等情况跳过\r\n }\r\n }\r\n }\r\n\r\n return false;\r\n}\r\n\r\n/**\r\n * 递归检查目录中是否存在 .vue 文件(限制递归深度防止性能问题)\r\n */\r\nfunction hasVueFilesInDir(dir: string, maxDepth: number): boolean {\r\n if (maxDepth < 0) return false;\r\n try {\r\n const entries = fs.readdirSync(dir, { withFileTypes: true });\r\n for (const entry of entries) {\r\n if (entry.name === 'node_modules' || entry.name === 'dist') continue;\r\n if (entry.isFile() && entry.name.endsWith('.vue')) return true;\r\n if (entry.isDirectory()) {\r\n if (hasVueFilesInDir(path.join(dir, entry.name), maxDepth - 1)) return true;\r\n }\r\n }\r\n } catch {\r\n // 权限不足等情况跳过\r\n }\r\n return false;\r\n}\r\n\r\n/**\r\n * 获取根目录文件和目录列表\r\n */\r\nfunction getRootFiles(cwd: string): string[] {\r\n try {\r\n return fs.readdirSync(cwd);\r\n } catch {\r\n return [];\r\n }\r\n}\r\n\r\n/**\r\n * 检测源码目录(回退逻辑)\r\n */\r\nfunction detectSrcDir(cwd: string): string {\r\n const possibleSrcDirs = ['src', 'lib', 'app'];\r\n for (const dir of possibleSrcDirs) {\r\n if (fs.existsSync(path.join(cwd, dir))) {\r\n return dir;\r\n }\r\n }\r\n return 'src';\r\n}\r\n\r\n/**\r\n * 发现 Vue 组件目录(回退逻辑)\r\n */\r\nfunction discoverComponentDirs(cwd: string, srcDir: string): string[] {\r\n const dirs: string[] = [];\r\n const srcPath = path.join(cwd, srcDir);\r\n\r\n if (!fs.existsSync(srcPath)) return dirs;\r\n\r\n const commonDirs = [\r\n 'components',\r\n 'src/components',\r\n 'src/views/components',\r\n 'src/shared/components',\r\n ];\r\n\r\n for (const dir of commonDirs) {\r\n const fullPath = path.join(cwd, dir);\r\n if (fs.existsSync(fullPath)) {\r\n dirs.push(dir);\r\n }\r\n }\r\n\r\n // 递归查找含 .vue 文件的目录\r\n try {\r\n const entries = fs.readdirSync(srcPath, { withFileTypes: true });\r\n for (const entry of entries) {\r\n if (entry.isDirectory() && !dirs.includes(`${srcDir}/${entry.name}`)) {\r\n const subDir = path.join(srcPath, entry.name);\r\n const hasVueFiles = fs.readdirSync(subDir).some((f) => f.endsWith('.vue'));\r\n if (hasVueFiles && entry.name !== 'node_modules') {\r\n dirs.push(`${srcDir}/${entry.name}`);\r\n }\r\n }\r\n }\r\n } catch {\r\n // 权限不足等情况跳过\r\n }\r\n\r\n return dirs;\r\n}\r\n\r\n/**\r\n * 发现页面/视图目录(回退逻辑)\r\n */\r\nfunction discoverPageDirs(cwd: string, _srcDir: string): string[] {\r\n const dirs: string[] = [];\r\n const commonDirs = ['pages', 'views', 'src/pages', 'src/views'];\r\n\r\n for (const dir of commonDirs) {\r\n if (fs.existsSync(path.join(cwd, dir))) {\r\n dirs.push(dir);\r\n }\r\n }\r\n\r\n return dirs;\r\n}\r\n\r\n/**\r\n * 发现项目中的 Vue 组件文件\r\n * @returns 组件文件相对路径列表\r\n */\r\nexport function discoverVueComponents(cwd: string, srcDir: string): string[] {\r\n const components: string[] = [];\r\n const searchPaths = [path.join(cwd, srcDir)];\r\n\r\n // Monorepo: 也扫描子项目\r\n const appDirs = discoverAppDirs(cwd);\r\n for (const appDir of appDirs) {\r\n const appSrcPath = path.join(cwd, appDir, 'src');\r\n if (fs.existsSync(appSrcPath)) {\r\n searchPaths.push(appSrcPath);\r\n }\r\n }\r\n\r\n for (const searchPath of searchPaths) {\r\n if (!fs.existsSync(searchPath)) continue;\r\n try {\r\n walkForVueFiles(searchPath, cwd, components);\r\n } catch {\r\n // 权限不足等情况跳过\r\n }\r\n }\r\n\r\n return components;\r\n}\r\n\r\n/**\r\n * 递归查找 .vue 文件\r\n */\r\nfunction walkForVueFiles(dir: string, cwd: string, result: string[]): void {\r\n const entries = fs.readdirSync(dir, { withFileTypes: true });\r\n for (const entry of entries) {\r\n const fullPath = path.join(dir, entry.name);\r\n if (entry.isDirectory() && entry.name !== 'node_modules' && entry.name !== 'dist') {\r\n walkForVueFiles(fullPath, cwd, result);\r\n } else if (entry.isFile() && entry.name.endsWith('.vue')) {\r\n result.push(path.relative(cwd, fullPath).replace(/\\\\/g, '/'));\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * 发现项目中的工具函数/服务文件\r\n * @returns 文件相对路径列表\r\n */\r\nexport function discoverUtilityFiles(cwd: string, srcDir: string): string[] {\r\n const files: string[] = [];\r\n const searchPaths = [path.join(cwd, srcDir)];\r\n\r\n // Monorepo: 也扫描子项目和共享包\r\n const appDirs = discoverAppDirs(cwd);\r\n for (const appDir of appDirs) {\r\n const appSrcPath = path.join(cwd, appDir, 'src');\r\n if (fs.existsSync(appSrcPath)) {\r\n searchPaths.push(appSrcPath);\r\n }\r\n }\r\n\r\n const utilityPatterns = [\r\n /^(utils|helpers|services|composables|hooks|api|lib)/,\r\n ];\r\n\r\n for (const searchPath of searchPaths) {\r\n if (!fs.existsSync(searchPath)) continue;\r\n try {\r\n walkForUtilityFiles(searchPath, cwd, searchPath, utilityPatterns, files);\r\n } catch {\r\n // 权限不足等情况跳过\r\n }\r\n }\r\n\r\n return files;\r\n}\r\n\r\n/**\r\n * 递归查找工具函数文件\r\n */\r\nfunction walkForUtilityFiles(\r\n dir: string,\r\n cwd: string,\r\n rootSearchPath: string,\r\n patterns: RegExp[],\r\n result: string[],\r\n): void {\r\n const entries = fs.readdirSync(dir, { withFileTypes: true });\r\n for (const entry of entries) {\r\n const fullPath = path.join(dir, entry.name);\r\n if (entry.isDirectory()) {\r\n if (entry.name !== 'node_modules' && entry.name !== 'dist' && entry.name !== 'components') {\r\n const relativePath = path.relative(rootSearchPath, fullPath).replace(/\\\\/g, '/');\r\n if (patterns.some((p) => p.test(relativePath))) {\r\n walkForUtilityFiles(fullPath, cwd, rootSearchPath, patterns, result);\r\n }\r\n }\r\n } else if (entry.isFile() && (entry.name.endsWith('.ts') || entry.name.endsWith('.js'))) {\r\n result.push(path.relative(cwd, fullPath).replace(/\\\\/g, '/'));\r\n }\r\n }\r\n}\r\n","/**\r\n * 配置管理服务 - 读取/解析/校验/生成 qat.config.ts\r\n */\r\n\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport chalk from 'chalk';\r\nimport type { QATConfig } from '../types/index.js';\r\n\r\n/** 默认配置 */\r\nexport const DEFAULT_CONFIG: QATConfig = {\r\n project: {\r\n framework: 'vue',\r\n vite: true,\r\n srcDir: 'src',\r\n },\r\n vitest: {\r\n enabled: true,\r\n coverage: true,\r\n globals: true,\r\n environment: 'happy-dom',\r\n },\r\n playwright: {\r\n enabled: true,\r\n browsers: ['chromium'],\r\n baseURL: 'http://localhost:5173',\r\n screenshot: 'only-on-failure',\r\n },\r\n visual: {\r\n enabled: true,\r\n threshold: 0.1,\r\n baselineDir: 'tests/visual/baseline',\r\n diffDir: 'tests/visual/diff',\r\n },\r\n lighthouse: {\r\n enabled: true,\r\n urls: ['http://localhost:5173'],\r\n runs: 3,\r\n thresholds: {\r\n performance: 80,\r\n accessibility: 90,\r\n },\r\n },\r\n mock: {\r\n enabled: true,\r\n port: 3456,\r\n routesDir: 'tests/mock/routes',\r\n },\r\n report: {\r\n outputDir: 'qat-report',\r\n open: false,\r\n },\r\n};\r\n\r\n/** 配置缓存 */\r\nlet cachedConfig: QATConfig | null = null;\r\n\r\n/**\r\n * defineConfig 辅助函数 - 提供类型提示,用于用户 qat.config.ts\r\n */\r\nexport function defineConfig(config: Partial<QATConfig>): Partial<QATConfig> {\r\n return config;\r\n}\r\n\r\n/**\r\n * 自动查找配置文件 (优先 .js,兼容 .ts)\r\n */\r\nfunction findConfigFile(): string {\r\n const cwd = process.cwd();\r\n const jsPath = path.join(cwd, 'qat.config.js');\r\n const tsPath = path.join(cwd, 'qat.config.ts');\r\n if (fs.existsSync(jsPath)) return jsPath;\r\n if (fs.existsSync(tsPath)) return tsPath;\r\n return 'qat.config.js';\r\n}\r\n\r\n/**\r\n * 加载配置文件\r\n * @param configPath 配置文件路径,默认为当前目录下 qat.config.ts\r\n * @param forceReload 强制重新加载(跳过缓存)\r\n */\r\nexport async function loadConfig(configPath?: string, forceReload = false): Promise<QATConfig> {\r\n if (cachedConfig && !forceReload) {\r\n return cachedConfig;\r\n }\r\n\r\n const filePath = configPath || process.env.QAT_CONFIG_PATH || findConfigFile();\r\n\r\n try {\r\n const configFile = await importConfig(filePath);\r\n const config = validateConfig(configFile);\r\n cachedConfig = config;\r\n return config;\r\n } catch (error) {\r\n if (isFileNotFoundError(error)) {\r\n if (process.env.QAT_VERBOSE === 'true') {\r\n console.log(chalk.yellow('未找到配置文件,使用默认配置'));\r\n }\r\n cachedConfig = { ...DEFAULT_CONFIG };\r\n return cachedConfig;\r\n }\r\n throw new Error(`配置文件加载失败: ${error instanceof Error ? error.message : String(error)}`);\r\n }\r\n}\r\n\r\n/**\r\n * 清除配置缓存\r\n */\r\nexport function clearConfigCache(): void {\r\n cachedConfig = null;\r\n}\r\n\r\n/**\r\n * 动态导入配置文件\r\n */\r\nasync function importConfig(filePath: string): Promise<Partial<QATConfig>> {\r\n const { resolve } = await import('node:path');\r\n const { pathToFileURL } = await import('node:url');\r\n const absolutePath = resolve(process.cwd(), filePath);\r\n\r\n if (!fs.existsSync(absolutePath)) {\r\n throw new Error(`Cannot find module '${absolutePath}'`);\r\n }\r\n\r\n // Windows 兼容:ESM import() 需要 file:// URL,不能直接用绝对路径\r\n const fileUrl = pathToFileURL(absolutePath).href;\r\n\r\n try {\r\n const module = await import(fileUrl);\r\n return module.default || module;\r\n } catch {\r\n // 尝试 .js 扩展名(.ts 文件可能无法直接加载)\r\n const jsPath = absolutePath.replace(/\\.ts$/, '.js');\r\n if (jsPath !== absolutePath && fs.existsSync(jsPath)) {\r\n const jsUrl = pathToFileURL(jsPath).href;\r\n const module = await import(jsUrl);\r\n return module.default || module;\r\n }\r\n throw new Error(`无法加载配置文件: ${absolutePath}`);\r\n }\r\n}\r\n\r\n/**\r\n * 校验配置结构 - 深度合并默认配置并校验字段合法性\r\n */\r\nexport function validateConfig(config: Partial<QATConfig>): QATConfig {\r\n // 深度合并\r\n const merged: QATConfig = {\r\n project: { ...DEFAULT_CONFIG.project, ...config.project },\r\n vitest: { ...DEFAULT_CONFIG.vitest, ...config.vitest },\r\n playwright: { ...DEFAULT_CONFIG.playwright, ...config.playwright },\r\n visual: { ...DEFAULT_CONFIG.visual, ...config.visual },\r\n lighthouse: { ...DEFAULT_CONFIG.lighthouse, ...config.lighthouse },\r\n mock: { ...DEFAULT_CONFIG.mock, ...config.mock },\r\n report: { ...DEFAULT_CONFIG.report, ...config.report },\r\n };\r\n\r\n // AI 配置 - 始终合并,确保存在\r\n if (config.ai) {\r\n merged.ai = {\r\n provider: config.ai.provider || 'openai',\r\n apiKey: config.ai.apiKey,\r\n baseUrl: config.ai.baseUrl,\r\n model: config.ai.model,\r\n };\r\n }\r\n\r\n // 校验必填字段\r\n if (!merged.project.srcDir) {\r\n throw new Error('配置校验失败: project.srcDir 不能为空');\r\n }\r\n\r\n // 校验数值范围\r\n if (merged.visual.threshold < 0 || merged.visual.threshold > 1) {\r\n throw new Error('配置校验失败: visual.threshold 必须在 0-1 之间');\r\n }\r\n\r\n if (merged.lighthouse.runs < 1) {\r\n throw new Error('配置校验失败: lighthouse.runs 不能小于 1');\r\n }\r\n\r\n if (merged.mock.port < 1 || merged.mock.port > 65535) {\r\n throw new Error('配置校验失败: mock.port 必须在 1-65535 之间');\r\n }\r\n\r\n // 校验浏览器列表\r\n const validBrowsers = ['chromium', 'firefox', 'webkit'];\r\n for (const browser of merged.playwright.browsers) {\r\n if (!validBrowsers.includes(browser)) {\r\n throw new Error(`配置校验失败: 不支持的浏览器 \"${browser}\",可选值: ${validBrowsers.join(', ')}`);\r\n }\r\n }\r\n\r\n return merged;\r\n}\r\n\r\n/**\r\n * 生成 qat.config.js 文件内容\r\n * AI 配置存储在 ~/.qat/ai.json,不在此文件中展示\r\n */\r\nexport function generateConfigFile(overrides: Partial<QATConfig> = {}): string {\r\n const config = validateConfig(overrides);\r\n\r\n return `// @ts-check\r\n/**\r\n * QAT 配置文件 - Quick Auto Testing\r\n * 修改后无需重启,下次运行 qat 命令时自动生效\r\n *\r\n * AI 模型配置请运行: qat change\r\n * AI 状态查看请运行: qat status\r\n */\r\nimport { defineConfig } from 'qat-cli';\r\n\r\nexport default defineConfig({\r\n project: {\r\n framework: '${config.project.framework}',\r\n vite: ${config.project.vite ?? true},\r\n srcDir: '${config.project.srcDir}',\r\n },\r\n vitest: {\r\n enabled: ${config.vitest.enabled},\r\n coverage: ${config.vitest.coverage},\r\n globals: ${config.vitest.globals},\r\n environment: '${config.vitest.environment}',\r\n },\r\n playwright: {\r\n enabled: ${config.playwright.enabled},\r\n browsers: [${config.playwright.browsers.map(b => `'${b}'`).join(', ')}],\r\n baseURL: '${config.playwright.baseURL}',\r\n screenshot: '${config.playwright.screenshot}',\r\n },\r\n visual: {\r\n enabled: ${config.visual.enabled},\r\n threshold: ${config.visual.threshold},\r\n baselineDir: '${config.visual.baselineDir}',\r\n diffDir: '${config.visual.diffDir}',\r\n },\r\n lighthouse: {\r\n enabled: ${config.lighthouse.enabled},\r\n urls: [${config.lighthouse.urls.map(u => `'${u}'`).join(', ')}],\r\n runs: ${config.lighthouse.runs},\r\n thresholds: {${Object.entries(config.lighthouse.thresholds)\r\n .map(([k, v]) => `\\n ${k}: ${v},`)\r\n .join('')}\\n },\r\n },\r\n mock: {\r\n enabled: ${config.mock.enabled},\r\n port: ${config.mock.port},\r\n routesDir: '${config.mock.routesDir}',\r\n },\r\n report: {\r\n outputDir: '${config.report.outputDir}',\r\n open: ${config.report.open},\r\n },\r\n});\r\n`;\r\n}\r\n\r\n/**\r\n * 写入配置文件到磁盘\r\n */\r\nexport async function writeConfigFile(\r\n cwd: string,\r\n overrides: Partial<QATConfig> = {},\r\n force = false,\r\n): Promise<string> {\r\n const configPath = path.join(cwd, 'qat.config.js');\r\n\r\n if (fs.existsSync(configPath) && !force) {\r\n throw new Error(`配置文件已存在: ${configPath},使用 --force 覆盖`);\r\n }\r\n\r\n const content = generateConfigFile(overrides);\r\n fs.writeFileSync(configPath, content, 'utf-8');\r\n\r\n return configPath;\r\n}\r\n\r\nfunction isFileNotFoundError(error: unknown): boolean {\r\n if (error instanceof Error) {\r\n return (\r\n error.message.includes('Cannot find') ||\r\n error.message.includes('ENOENT') ||\r\n error.message.includes('无法加载配置文件')\r\n );\r\n }\r\n return false;\r\n}\r\n","/**\r\n * Mock服务 - Express服务器,路由管理,数据模板\r\n */\r\n\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport type { Request, Response, NextFunction } from 'express';\r\n\r\n/** Mock路由配置 */\r\nexport interface MockRoute {\r\n method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\r\n path: string;\r\n status?: number;\r\n response?: unknown;\r\n delay?: number;\r\n headers?: Record<string, string>;\r\n}\r\n\r\n/** Mock服务器状态 */\r\nexport interface MockServerState {\r\n running: boolean;\r\n port: number;\r\n routes: MockRoute[];\r\n pid?: number;\r\n}\r\n\r\n/** 全局Mock服务器状态 */\r\nlet serverState: MockServerState = {\r\n running: false,\r\n port: 3456,\r\n routes: [],\r\n};\r\n\r\n/** 服务器实例引用 */\r\nlet serverInstance: ReturnType<typeof import('http').createServer> | null = null;\r\n\r\n/**\r\n * 获取Mock服务器状态\r\n */\r\nexport function getMockServerState(): MockServerState {\r\n return { ...serverState };\r\n}\r\n\r\n/**\r\n * 加载Mock路由配置\r\n * 支持从目录加载 .json 和 .js/.ts 路由文件\r\n */\r\nexport async function loadMockRoutes(routesDir: string): Promise<MockRoute[]> {\r\n const absDir = path.resolve(process.cwd(), routesDir);\r\n\r\n if (!fs.existsSync(absDir)) {\r\n return [];\r\n }\r\n\r\n const routes: MockRoute[] = [];\r\n const entries = fs.readdirSync(absDir, { withFileTypes: true });\r\n\r\n for (const entry of entries) {\r\n if (!entry.isFile()) continue;\r\n\r\n const filePath = path.join(absDir, entry.name);\r\n const ext = path.extname(entry.name);\r\n\r\n try {\r\n if (ext === '.json') {\r\n const content = fs.readFileSync(filePath, 'utf-8');\r\n const parsed = JSON.parse(content);\r\n if (Array.isArray(parsed)) {\r\n routes.push(...parsed);\r\n } else if (parsed.method && parsed.path) {\r\n routes.push(parsed as MockRoute);\r\n }\r\n } else if (ext === '.js' || ext === '.mjs') {\r\n const module = await import(filePath);\r\n const exported = module.default || module;\r\n if (Array.isArray(exported)) {\r\n routes.push(...exported);\r\n } else if (exported.method && exported.path) {\r\n routes.push(exported as MockRoute);\r\n }\r\n }\r\n } catch (error) {\r\n console.warn(`加载路由文件失败 ${entry.name}: ${error instanceof Error ? error.message : String(error)}`);\r\n }\r\n }\r\n\r\n return routes;\r\n}\r\n\r\n/**\r\n * 创建默认Mock路由\r\n */\r\nexport function createDefaultRoutes(): MockRoute[] {\r\n return [\r\n {\r\n method: 'GET',\r\n path: '/api/health',\r\n status: 200,\r\n response: { status: 'ok', timestamp: Date.now() },\r\n },\r\n {\r\n method: 'GET',\r\n path: '/api/*',\r\n status: 200,\r\n response: { message: 'Mock API response', data: null },\r\n delay: 100,\r\n },\r\n {\r\n method: 'POST',\r\n path: '/api/*',\r\n status: 201,\r\n response: { message: 'Created successfully', data: null },\r\n },\r\n {\r\n method: 'PUT',\r\n path: '/api/*',\r\n status: 200,\r\n response: { message: 'Updated successfully', data: null },\r\n },\r\n {\r\n method: 'DELETE',\r\n path: '/api/*',\r\n status: 204,\r\n response: null,\r\n },\r\n ];\r\n}\r\n\r\n/**\r\n * 启动Mock服务器\r\n */\r\nexport async function startMockServer(port: number, routes: MockRoute[]): Promise<void> {\r\n if (serverState.running) {\r\n throw new Error(`Mock服务器已在运行 (端口: ${serverState.port})`);\r\n }\r\n\r\n const express = await import('express');\r\n const app = express.default();\r\n\r\n // 中间件\r\n app.use(express.default.json());\r\n\r\n // 请求日志\r\n app.use((req: Request, _res: Response, next: NextFunction) => {\r\n if (process.env.QAT_VERBOSE === 'true') {\r\n console.log(` [Mock] ${req.method} ${req.url}`);\r\n }\r\n next();\r\n });\r\n\r\n // 注册路由\r\n const allRoutes = routes.length > 0 ? routes : createDefaultRoutes();\r\n\r\n for (const route of allRoutes) {\r\n const handler = async (req: Request, res: Response) => {\r\n // 模拟延迟\r\n if (route.delay && route.delay > 0) {\r\n await new Promise((resolve) => setTimeout(resolve, route.delay));\r\n }\r\n\r\n // 设置自定义响应头\r\n if (route.headers) {\r\n for (const [key, value] of Object.entries(route.headers)) {\r\n res.setHeader(key, value);\r\n }\r\n }\r\n\r\n res.setHeader('X-Mock-Server', 'qat');\r\n\r\n // 替换响应中的请求参数\r\n let response = route.response;\r\n if (typeof response === 'object' && response !== null) {\r\n const responseStr = JSON.stringify(response)\r\n .replace(/\\{\\{params\\.(\\w+)\\}\\}/g, (_match, key: string) => (req.params[key] as string) || '')\r\n .replace(/\\{\\{query\\.(\\w+)\\}\\}/g, (_match, key: string) => (req.query[key] as string) || '')\r\n .replace(/\\{\\{body\\.(\\w+)\\}\\}/g, (_match, key: string) => String((req.body as Record<string, unknown>)?.[key] ?? ''));\r\n try {\r\n response = JSON.parse(responseStr);\r\n } catch {\r\n // 保持原始响应\r\n }\r\n }\r\n\r\n res.status(route.status || 200).json(response);\r\n };\r\n\r\n // 根据方法注册路由\r\n const expressRoute = route.path;\r\n switch (route.method) {\r\n case 'GET':\r\n app.get(expressRoute, handler);\r\n break;\r\n case 'POST':\r\n app.post(expressRoute, handler);\r\n break;\r\n case 'PUT':\r\n app.put(expressRoute, handler);\r\n break;\r\n case 'DELETE':\r\n app.delete(expressRoute, handler);\r\n break;\r\n case 'PATCH':\r\n app.patch(expressRoute, handler);\r\n break;\r\n }\r\n }\r\n\r\n // 404 兜底\r\n app.use((req, res) => {\r\n res.status(404).json({\r\n error: 'Not Found',\r\n message: `No mock route defined for ${req.method} ${req.url}`,\r\n hint: '在 tests/mock/routes/ 目录下添加路由配置',\r\n });\r\n });\r\n\r\n // 启动服务器\r\n return new Promise((resolve, reject) => {\r\n serverInstance = app.listen(port, () => {\r\n serverState = {\r\n running: true,\r\n port,\r\n routes: allRoutes,\r\n pid: process.pid,\r\n };\r\n resolve();\r\n });\r\n\r\n serverInstance.on('error', (err: Error) => {\r\n reject(new Error(`Mock服务器启动失败: ${err.message}`));\r\n });\r\n });\r\n}\r\n\r\n/**\r\n * 停止Mock服务器\r\n */\r\nexport async function stopMockServer(): Promise<void> {\r\n if (!serverInstance || !serverState.running) {\r\n serverState = { ...serverState, running: false };\r\n return;\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n serverInstance!.close((err) => {\r\n if (err) {\r\n reject(new Error(`Mock服务器停止失败: ${err.message}`));\r\n return;\r\n }\r\n\r\n serverInstance = null;\r\n serverState = {\r\n running: false,\r\n port: serverState.port,\r\n routes: [],\r\n };\r\n resolve();\r\n });\r\n });\r\n}\r\n\r\n/**\r\n * 生成Mock路由配置文件模板\r\n */\r\nexport function generateMockRouteTemplate(name: string): string {\r\n return `[\r\n {\r\n method: 'GET',\r\n path: '/api/${name}',\r\n status: 200,\r\n response: {\r\n message: 'Success',\r\n data: {\r\n id: 1,\r\n name: '${name}',\r\n },\r\n },\r\n delay: 100,\r\n },\r\n {\r\n method: 'POST',\r\n path: '/api/${name}',\r\n status: 201,\r\n response: {\r\n message: 'Created',\r\n data: {\r\n id: 2,\r\n name: '{{body.name}}',\r\n },\r\n },\r\n },\r\n {\r\n method: 'DELETE',\r\n path: '/api/${name}/:id',\r\n status: 204,\r\n response: null,\r\n },\r\n]\r\n`;\r\n}\r\n\r\n/**\r\n * 初始化Mock路由目录\r\n */\r\nexport function initMockRoutesDir(routesDir: string): void {\r\n const absDir = path.resolve(process.cwd(), routesDir);\r\n\r\n if (!fs.existsSync(absDir)) {\r\n fs.mkdirSync(absDir, { recursive: true });\r\n }\r\n\r\n // 生成示例路由文件\r\n const examplePath = path.join(absDir, 'example.json');\r\n if (!fs.existsSync(examplePath)) {\r\n const exampleRoutes: MockRoute[] = [\r\n {\r\n method: 'GET',\r\n path: '/api/users',\r\n status: 200,\r\n response: {\r\n message: 'Success',\r\n data: [\r\n { id: 1, name: 'Alice' },\r\n { id: 2, name: 'Bob' },\r\n ],\r\n },\r\n },\r\n {\r\n method: 'GET',\r\n path: '/api/users/:id',\r\n status: 200,\r\n response: {\r\n message: 'Success',\r\n data: { id: 1, name: 'Alice' },\r\n },\r\n },\r\n ];\r\n fs.writeFileSync(examplePath, JSON.stringify(exampleRoutes, null, 2), 'utf-8');\r\n }\r\n}\r\n","/**\r\n * 默认空实现 - 未配置AI时的占位Provider\r\n */\r\n\r\nimport type { AIProvider, AICapability, AIGenerateTestRequest, AIGenerateTestResponse, AIAnalyzeResultRequest, AIAnalyzeResultResponse, AIReviewTestRequest, AIReviewTestResponse } from '../types/ai.js';\r\nimport type { TestError } from '../types/index.js';\r\n\r\nconst NOOP_CAPABILITIES: AICapability = {\r\n generateTest: false,\r\n analyzeResult: false,\r\n suggestFix: false,\r\n};\r\n\r\nexport class NoopAIProvider implements AIProvider {\r\n readonly name = 'noop';\r\n readonly capabilities = NOOP_CAPABILITIES;\r\n\r\n async generateTest(_req: AIGenerateTestRequest): Promise<AIGenerateTestResponse> {\r\n throw new Error(\r\n 'AI功能未配置。请在 qat.config.ts 中配置 ai.provider 后使用此功能。',\r\n );\r\n }\r\n\r\n async analyzeResult(_req: AIAnalyzeResultRequest): Promise<AIAnalyzeResultResponse> {\r\n throw new Error(\r\n 'AI功能未配置。请在 qat.config.ts 中配置 ai.provider 后使用此功能。',\r\n );\r\n }\r\n\r\n async suggestFix(_error: TestError): Promise<string[]> {\r\n throw new Error(\r\n 'AI功能未配置。请在 qat.config.ts 中配置 ai.provider 后使用此功能。',\r\n );\r\n }\r\n\r\n async reviewTest(_req: AIReviewTestRequest): Promise<AIReviewTestResponse> {\r\n throw new Error(\r\n 'AI功能未配置。请在 qat.config.ts 中配置 ai.provider 后使用此功能。',\r\n );\r\n }\r\n}\r\n","/**\r\n * OpenAI 兼容 Provider - 支持 OpenAI / DeepSeek / Moonshot / Ollama 等\r\n */\r\n\r\nimport type {\r\n AIProvider,\r\n AICapability,\r\n AIGenerateTestRequest,\r\n AIGenerateTestResponse,\r\n AIAnalyzeResultRequest,\r\n AIAnalyzeResultResponse,\r\n AIReviewTestRequest,\r\n AIReviewTestResponse,\r\n} from '../types/ai.js';\r\nimport type { TestError } from '../types/index.js';\r\n\r\n/** OpenAI Chat Completion 响应格式 */\r\ninterface ChatCompletionResponse {\r\n choices: Array<{\r\n message: {\r\n content: string;\r\n };\r\n finish_reason: string;\r\n }>;\r\n usage?: {\r\n prompt_tokens: number;\r\n completion_tokens: number;\r\n total_tokens: number;\r\n };\r\n}\r\n\r\nexport class OpenAICompatibleProvider implements AIProvider {\r\n readonly name: string;\r\n readonly capabilities: AICapability = {\r\n generateTest: true,\r\n analyzeResult: true,\r\n suggestFix: true,\r\n };\r\n\r\n private apiKey: string;\r\n private model: string;\r\n private baseUrl: string;\r\n\r\n constructor(config: { provider: string; apiKey?: string; model?: string; baseUrl?: string }) {\r\n this.name = config.provider;\r\n this.apiKey = config.apiKey || '';\r\n this.model = config.model || this.getDefaultModel(config.provider);\r\n this.baseUrl = config.baseUrl || this.getDefaultBaseUrl(config.provider);\r\n }\r\n\r\n async generateTest(req: AIGenerateTestRequest): Promise<AIGenerateTestResponse> {\r\n const systemPrompt = this.buildGenerateTestSystemPrompt(req);\r\n const userPrompt = this.buildGenerateTestUserPrompt(req);\r\n\r\n const content = await this.chat(systemPrompt, userPrompt);\r\n return this.parseGenerateTestResponse(content);\r\n }\r\n\r\n async analyzeResult(req: AIAnalyzeResultRequest): Promise<AIAnalyzeResultResponse> {\r\n const systemPrompt = `你是一个专业的测试分析专家。分析测试运行结果,找出问题根因,给出具体可操作的改进建议。\r\n输出格式:\r\n1. 分析摘要(1-3句话)\r\n2. 改进建议列表(每条建议具体、可操作)`;\r\n\r\n const resultSummary = req.testResults.map((r) => {\r\n const failed = r.suites.flatMap((s) => s.tests.filter((t) => t.status === 'failed'));\r\n return `类型: ${r.type}, 状态: ${r.status}, 耗时: ${r.duration}ms, 失败用例: ${failed.length}`;\r\n }).join('\\n');\r\n\r\n const errorDetails = req.errorLogs?.join('\\n') || req.testResults\r\n .flatMap((r) => r.suites.flatMap((s) => s.tests.filter((t) => t.status === 'failed' && t.error)))\r\n .map((t) => `[${t.name}] ${t.error?.message}`)\r\n .join('\\n') || '无错误详情';\r\n\r\n const userPrompt = `测试结果:\\n${resultSummary}\\n\\n错误详情:\\n${errorDetails}`;\r\n\r\n const content = await this.chat(systemPrompt, userPrompt);\r\n\r\n return {\r\n analysis: content.split('\\n')[0] || content.slice(0, 200),\r\n suggestions: content.split('\\n').filter((l) => l.trim().startsWith('-') || l.trim().startsWith('•') || l.trim().match(/^\\d+\\./)).map((l) => l.replace(/^[-•\\d.]+\\s*/, '').trim()).filter(Boolean),\r\n severity: req.testResults.some((r) => r.status === 'failed') ? 'error' : 'info',\r\n };\r\n }\r\n\r\n async suggestFix(error: TestError): Promise<string[]> {\r\n const systemPrompt = `你是一个专业的代码修复专家。根据测试错误信息,给出具体的修复建议。\r\n每条建议应该包含:\r\n1. 问题定位\r\n2. 修复方案\r\n3. 示例代码(如果适用)`;\r\n\r\n const userPrompt = `错误信息: ${error.message}\r\n${error.stack ? `堆栈: ${error.stack}` : ''}\r\n${error.expected ? `期望值: ${error.expected}` : ''}\r\n${error.actual ? `实际值: ${error.actual}` : ''}`;\r\n\r\n const content = await this.chat(systemPrompt, userPrompt);\r\n\r\n return content\r\n .split('\\n')\r\n .filter((l) => l.trim().startsWith('-') || l.trim().startsWith('•') || l.trim().match(/^\\d+\\./))\r\n .map((l) => l.replace(/^[-•\\d.]+\\s*/, '').trim())\r\n .filter(Boolean);\r\n }\r\n\r\n async reviewTest(req: AIReviewTestRequest): Promise<AIReviewTestResponse> {\r\n const systemPrompt = this.buildReviewTestSystemPrompt(req);\r\n const userPrompt = this.buildReviewTestUserPrompt(req);\r\n\r\n const content = await this.chat(systemPrompt, userPrompt);\r\n return this.parseReviewTestResponse(content);\r\n }\r\n\r\n // ─── 内部方法 ──────────────────────────────────────────────\r\n\r\n /**\r\n * 测试连通性 - 发送一条简单请求验证 API 可达\r\n * @returns 连通结果\r\n */\r\n async testConnection(): Promise<{ ok: boolean; message: string; latencyMs?: number }> {\r\n const url = `${this.baseUrl}/chat/completions`;\r\n const start = Date.now();\r\n\r\n const headers: Record<string, string> = {\r\n 'Content-Type': 'application/json',\r\n };\r\n\r\n if (this.apiKey) {\r\n headers['Authorization'] = `Bearer ${this.apiKey}`;\r\n }\r\n\r\n try {\r\n const response = await fetch(url, {\r\n method: 'POST',\r\n headers,\r\n body: JSON.stringify({\r\n model: this.model,\r\n messages: [{ role: 'user', content: 'Hi' }],\r\n max_tokens: 5,\r\n }),\r\n signal: AbortSignal.timeout(15000), // 15s timeout\r\n });\r\n\r\n const latencyMs = Date.now() - start;\r\n\r\n if (response.ok) {\r\n const data = (await response.json()) as ChatCompletionResponse;\r\n if (data.choices?.[0]?.message?.content !== undefined) {\r\n return { ok: true, message: `连通正常 (${latencyMs}ms)`, latencyMs };\r\n }\r\n return { ok: false, message: `API 返回格式异常: ${JSON.stringify(data).slice(0, 200)}`, latencyMs };\r\n }\r\n\r\n // 非 2xx\r\n const text = await response.text().catch(() => '');\r\n let detail = text.slice(0, 300);\r\n\r\n // 尝试提取错误信息\r\n try {\r\n const errObj = JSON.parse(text);\r\n if (errObj.error?.message) detail = errObj.error.message;\r\n } catch { /* ignore */ }\r\n\r\n if (response.status === 401) {\r\n return { ok: false, message: `认证失败: API Key 无效或已过期`, latencyMs };\r\n }\r\n if (response.status === 404) {\r\n return { ok: false, message: `接口不存在: 请检查 baseUrl 是否正确 (状态 404)`, latencyMs };\r\n }\r\n if (response.status === 429) {\r\n return { ok: false, message: `请求频率超限: 请稍后重试 (429)`, latencyMs };\r\n }\r\n\r\n return { ok: false, message: `HTTP ${response.status}: ${detail}`, latencyMs };\r\n } catch (error) {\r\n const latencyMs = Date.now() - start;\r\n if (error instanceof TypeError && error.message.includes('fetch')) {\r\n return { ok: false, message: `网络错误: 无法连接到 ${this.baseUrl},请检查地址是否正确`, latencyMs };\r\n }\r\n if (error instanceof Error && error.name === 'TimeoutError') {\r\n return { ok: false, message: `连接超时: ${this.baseUrl} 未在 15s 内响应`, latencyMs };\r\n }\r\n return { ok: false, message: `连接失败: ${error instanceof Error ? error.message : String(error)}`, latencyMs };\r\n }\r\n }\r\n\r\n private async chat(systemPrompt: string, userPrompt: string): Promise<string> {\r\n const url = `${this.baseUrl}/chat/completions`;\r\n\r\n const body = {\r\n model: this.model,\r\n messages: [\r\n { role: 'system', content: systemPrompt },\r\n { role: 'user', content: userPrompt },\r\n ],\r\n temperature: 0.3,\r\n max_tokens: 4096,\r\n };\r\n\r\n const headers: Record<string, string> = {\r\n 'Content-Type': 'application/json',\r\n };\r\n\r\n // Ollama 不需要 Authorization header\r\n if (this.apiKey) {\r\n headers['Authorization'] = `Bearer ${this.apiKey}`;\r\n }\r\n\r\n const response = await fetch(url, {\r\n method: 'POST',\r\n headers,\r\n body: JSON.stringify(body),\r\n signal: AbortSignal.timeout(60000), // 60s timeout\r\n });\r\n\r\n if (!response.ok) {\r\n const text = await response.text().catch(() => '');\r\n throw new Error(`AI API 请求失败 (${response.status}): ${text.slice(0, 500)}`);\r\n }\r\n\r\n const data = (await response.json()) as ChatCompletionResponse;\r\n\r\n if (!data.choices?.[0]?.message?.content) {\r\n throw new Error('AI API 返回空响应');\r\n }\r\n\r\n return data.choices[0].message.content;\r\n }\r\n\r\n private buildGenerateTestSystemPrompt(req: AIGenerateTestRequest): string {\r\n const typeMap: Record<string, string> = {\r\n unit: '单元测试(Vitest + @vue/test-utils)',\r\n component: '组件测试(Vitest + @vue/test-utils + mount)',\r\n e2e: 'E2E端到端测试(Playwright)',\r\n api: 'API接口测试(Vitest + fetch)',\r\n visual: '视觉回归测试(Playwright screenshot)',\r\n performance: '性能测试(Playwright + performance metrics)',\r\n };\r\n\r\n return `你是一个专业的前端测试工程师,擅长编写高质量的${typeMap[req.type] || req.type}。\r\n要求:\r\n1. 只输出测试代码,不要多余的解释\r\n2. 代码必须可直接运行,包含所有必要的 import\r\n3. 测试用例覆盖:正常路径、边界条件、错误处理\r\n4. 使用中文描述 it/test 块名称\r\n5. Vue 组件测试使用 @vue/test-utils 的 mount\r\n6. 如果有 props/emits 信息,务必针对每个 prop 和 emit 生成测试`;\r\n }\r\n\r\n private buildGenerateTestUserPrompt(req: AIGenerateTestRequest): string {\r\n let prompt = `请为以下文件生成${req.type}测试代码:\\n目标文件: ${req.target}\\n`;\r\n\r\n if (req.analysis) {\r\n prompt += '\\n源码分析结果:\\n';\r\n\r\n if (req.analysis.exports?.length > 0) {\r\n prompt += `导出项:\\n${req.analysis.exports.map((e) => {\r\n const params = e.params?.length ? `(${e.params.join(', ')})` : '';\r\n const asyncFlag = e.isAsync ? 'async ' : '';\r\n return ` - ${asyncFlag}${e.name}${params} [${e.kind}]`;\r\n }).join('\\n')}\\n`;\r\n }\r\n\r\n if (req.analysis.props?.length) {\r\n prompt += `Props:\\n${req.analysis.props.map((p) =>\r\n ` - ${p.name}: ${p.type}${p.required ? ' (必填)' : ' (可选)'}`,\r\n ).join('\\n')}\\n`;\r\n }\r\n\r\n if (req.analysis.emits?.length) {\r\n prompt += `Emits:\\n${req.analysis.emits.map((e) =>\r\n ` - ${e.name}${e.params?.length ? `(${e.params.join(', ')})` : ''}`,\r\n ).join('\\n')}\\n`;\r\n }\r\n\r\n if (req.analysis.methods?.length) {\r\n prompt += `Methods: ${req.analysis.methods.join(', ')}\\n`;\r\n }\r\n\r\n if (req.analysis.computed?.length) {\r\n prompt += `Computed: ${req.analysis.computed.join(', ')}\\n`;\r\n }\r\n }\r\n\r\n if (req.context) {\r\n prompt += `\\n源码内容:\\n\\`\\`\\`typescript\\n${req.context}\\n\\`\\`\\`\\n`;\r\n }\r\n\r\n if (req.framework) {\r\n prompt += `\\n框架: ${req.framework}`;\r\n }\r\n\r\n return prompt;\r\n }\r\n\r\n private parseGenerateTestResponse(content: string): AIGenerateTestResponse {\r\n // 提取代码块\r\n const codeBlockMatch = content.match(/```(?:typescript|ts|javascript|js)?\\s*\\n([\\s\\S]*?)```/);\r\n const code = codeBlockMatch\r\n ? codeBlockMatch[1].trim()\r\n : content.replace(/^(?:```[\\s\\S]*?\\n)?/, '').replace(/\\n?```$/, '').trim();\r\n\r\n // 提取描述(代码块之前的文字)\r\n const description = codeBlockMatch\r\n ? content.split('```')[0].trim().slice(0, 200)\r\n : 'AI generated test';\r\n\r\n return {\r\n code,\r\n description: description || 'AI generated test',\r\n confidence: 0.8,\r\n };\r\n }\r\n\r\n private buildReviewTestSystemPrompt(_req: AIReviewTestRequest): string {\r\n return `你是一位严谨的测试审计专家。你的职责是审查 AI 生成的测试用例是否与源码贴切且准确。\r\n\r\n审查标准:\r\n1. **测试类型匹配**:测试类型是否与源码文件性质匹配(如 .vue 应为组件测试,utils 应为单元测试)\r\n2. **覆盖完整性**:是否覆盖了源码中的核心导出项(函数、组件的 props/emits/methods)\r\n3. **断言有效性**:断言是否真实检验了被测行为,而非空断言或永真断言\r\n4. **导入正确性**:import 路径和模块是否正确\r\n5. **代码可运行性**:测试代码是否可直接运行,无语法错误\r\n\r\n输出格式(严格遵守):\r\nAPPROVED: true 或 false\r\nSCORE: 0.0 到 1.0 之间的评分\r\nFEEDBACK: 一句话审计意见\r\nISSUES: 问题列表(每行一个,格式 \"- 问题描述\")\r\nSUGGESTIONS: 改进建议列表(每行一个,格式 \"- 建议描述\")`;\r\n }\r\n\r\n private buildReviewTestUserPrompt(req: AIReviewTestRequest): string {\r\n let prompt = `请审查以下测试用例是否与源码贴切且准确。\r\n\r\n被测文件: ${req.target}\r\n测试类型: ${req.testType}\r\n\r\n--- 源码内容 ---\r\n\\`\\`\\`typescript\r\n${req.sourceCode}\r\n\\`\\`\\`\r\n\r\n--- 生成的测试代码 ---\r\n\\`\\`\\`typescript\r\n${req.testCode}\r\n\\`\\`\\``;\r\n\r\n if (req.analysis) {\r\n prompt += '\\n\\n--- 源码分析 ---';\r\n\r\n if (req.analysis.exports?.length > 0) {\r\n prompt += `\\n导出项:\\n${req.analysis.exports.map((e) => {\r\n const params = e.params?.length ? `(${e.params.join(', ')})` : '';\r\n const asyncFlag = e.isAsync ? 'async ' : '';\r\n return ` - ${asyncFlag}${e.name}${params} [${e.kind}]`;\r\n }).join('\\n')}`;\r\n }\r\n\r\n if (req.analysis.props?.length) {\r\n prompt += `\\nProps:\\n${req.analysis.props.map((p) =>\r\n ` - ${p.name}: ${p.type}${p.required ? ' (必填)' : ' (可选)'}`,\r\n ).join('\\n')}`;\r\n }\r\n\r\n if (req.analysis.emits?.length) {\r\n prompt += `\\nEmits:\\n${req.analysis.emits.map((e) =>\r\n ` - ${e.name}${e.params?.length ? `(${e.params.join(', ')})` : ''}`,\r\n ).join('\\n')}`;\r\n }\r\n }\r\n\r\n if (req.generationDescription) {\r\n prompt += `\\n\\n生成者说明: ${req.generationDescription}`;\r\n }\r\n\r\n return prompt;\r\n }\r\n\r\n private parseReviewTestResponse(content: string): AIReviewTestResponse {\r\n const approvedMatch = content.match(/APPROVED:\\s*(true|false)/i);\r\n const scoreMatch = content.match(/SCORE:\\s*([\\d.]+)/);\r\n const feedbackMatch = content.match(/FEEDBACK:\\s*(.+)/);\r\n\r\n const approved = approvedMatch ? approvedMatch[1].toLowerCase() === 'true' : false;\r\n const score = scoreMatch ? parseFloat(scoreMatch[1]) : (approved ? 0.7 : 0.3);\r\n const feedback = feedbackMatch ? feedbackMatch[1].trim() : '审计完成';\r\n\r\n // 提取问题列表\r\n const issues: string[] = [];\r\n const issuesMatch = content.match(/ISSUES:\\s*\\n([\\s\\S]*?)(?=SUGGESTIONS:|$)/);\r\n if (issuesMatch) {\r\n const lines = issuesMatch[1].split('\\n');\r\n for (const line of lines) {\r\n const trimmed = line.replace(/^[-•\\d.]+\\s*/, '').trim();\r\n if (trimmed) issues.push(trimmed);\r\n }\r\n }\r\n\r\n // 提取建议列表\r\n const suggestions: string[] = [];\r\n const suggestionsMatch = content.match(/SUGGESTIONS:\\s*\\n([\\s\\S]*?)$/);\r\n if (suggestionsMatch) {\r\n const lines = suggestionsMatch[1].split('\\n');\r\n for (const line of lines) {\r\n const trimmed = line.replace(/^[-•\\d.]+\\s*/, '').trim();\r\n if (trimmed) suggestions.push(trimmed);\r\n }\r\n }\r\n\r\n return {\r\n approved,\r\n score: Math.max(0, Math.min(1, score)),\r\n feedback,\r\n issues,\r\n suggestions,\r\n };\r\n }\r\n\r\n private getDefaultModel(provider: string): string {\r\n const modelMap: Record<string, string> = {\r\n openai: 'gpt-4o-mini',\r\n deepseek: 'deepseek-chat',\r\n moonshot: 'moonshot-v1-8k',\r\n zhipu: 'glm-4-flash',\r\n qwen: 'qwen-turbo',\r\n ollama: 'qwen2.5-coder:7b',\r\n };\r\n return modelMap[provider] || 'gpt-4o-mini';\r\n }\r\n\r\n private getDefaultBaseUrl(provider: string): string {\r\n const urlMap: Record<string, string> = {\r\n openai: 'https://api.openai.com/v1',\r\n deepseek: 'https://api.deepseek.com/v1',\r\n moonshot: 'https://api.moonshot.cn/v1',\r\n zhipu: 'https://open.bigmodel.cn/api/paas/v4',\r\n qwen: 'https://dashscope.aliyuncs.com/compatible-mode/v1',\r\n ollama: 'http://localhost:11434/v1',\r\n };\r\n return urlMap[provider] || 'https://api.openai.com/v1';\r\n }\r\n}\r\n","/**\r\n * AI Provider 管理中心 - 注册、加载、调度 AI Provider\r\n */\r\n\r\nimport type { AIConfig, AIPresetProvider } from '../types/ai.js';\r\nimport { NoopAIProvider } from './noop-provider.js';\r\nimport { OpenAICompatibleProvider } from './openai-provider.js';\r\n\r\n/** Provider 构造函数类型 */\r\nexport type AIProviderConstructor = new (config: AIConfig) => NoopAIProvider | OpenAICompatibleProvider;\r\n\r\n/** Provider 注册表 */\r\nconst providerRegistry = new Map<string, AIProviderConstructor>();\r\n\r\n/** 当前活跃的 Provider 实例 */\r\nlet activeProvider: NoopAIProvider | OpenAICompatibleProvider | null = null;\r\n\r\n// ─── 自动注册内置 Provider ──────────────────────────────────────\r\n\r\nconst BUILTIN_PROVIDERS: Array<{ id: string; ProviderClass: AIProviderConstructor }> = [\r\n { id: 'openai', ProviderClass: OpenAICompatibleProvider },\r\n { id: 'deepseek', ProviderClass: OpenAICompatibleProvider },\r\n { id: 'moonshot', ProviderClass: OpenAICompatibleProvider },\r\n { id: 'zhipu', ProviderClass: OpenAICompatibleProvider },\r\n { id: 'qwen', ProviderClass: OpenAICompatibleProvider },\r\n { id: 'ollama', ProviderClass: OpenAICompatibleProvider },\r\n];\r\n\r\nfor (const { id, ProviderClass } of BUILTIN_PROVIDERS) {\r\n providerRegistry.set(id, ProviderClass);\r\n}\r\n\r\n/** 预置 Provider 信息(用于 init 向导展示) */\r\nexport const AI_PRESET_PROVIDERS: AIPresetProvider[] = [\r\n { id: 'openai', name: 'OpenAI (GPT-4o)', baseUrl: 'https://api.openai.com/v1', defaultModel: 'gpt-4o-mini', requiresApiKey: true },\r\n { id: 'deepseek', name: 'DeepSeek', baseUrl: 'https://api.deepseek.com/v1', defaultModel: 'deepseek-chat', requiresApiKey: true },\r\n { id: 'moonshot', name: 'Moonshot (月之暗面)', baseUrl: 'https://api.moonshot.cn/v1', defaultModel: 'moonshot-v1-8k', requiresApiKey: true },\r\n { id: 'zhipu', name: '智谱 (GLM-4)', baseUrl: 'https://open.bigmodel.cn/api/paas/v4', defaultModel: 'glm-4-flash', requiresApiKey: true },\r\n { id: 'qwen', name: '通义千问 (Qwen)', baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', defaultModel: 'qwen-turbo', requiresApiKey: true },\r\n { id: 'ollama', name: 'Ollama (本地)', baseUrl: 'http://localhost:11434/v1', defaultModel: 'qwen2.5-coder:7b', requiresApiKey: false },\r\n];\r\n\r\n/**\r\n * 注册 AI Provider\r\n * @param name Provider 名称\r\n * @param ProviderClass Provider 类\r\n */\r\nexport function registerAIProvider(name: string, ProviderClass: AIProviderConstructor): void {\r\n providerRegistry.set(name, ProviderClass);\r\n}\r\n\r\n/**\r\n * 获取已注册的 Provider 名称列表\r\n */\r\nexport function getRegisteredProviders(): string[] {\r\n return Array.from(providerRegistry.keys());\r\n}\r\n\r\n/**\r\n * 获取 AI Provider 实例\r\n * @param config AI 配置,若未配置则返回 NoopProvider\r\n */\r\nexport function getAIProvider(config?: AIConfig): NoopAIProvider | OpenAICompatibleProvider {\r\n if (activeProvider) {\r\n return activeProvider;\r\n }\r\n\r\n if (!config || !config.provider) {\r\n activeProvider = new NoopAIProvider();\r\n return activeProvider;\r\n }\r\n\r\n const ProviderClass = providerRegistry.get(config.provider);\r\n if (!ProviderClass) {\r\n console.warn(\r\n `未找到 AI Provider \"${config.provider}\",已注册的 Provider: ${\r\n providerRegistry.size > 0 ? getRegisteredProviders().join(', ') : '无'\r\n }。将使用默认 Provider。`,\r\n );\r\n activeProvider = new NoopAIProvider();\r\n return activeProvider;\r\n }\r\n\r\n activeProvider = new ProviderClass(config);\r\n return activeProvider;\r\n}\r\n\r\n/**\r\n * 重置 Provider 实例(用于配置变更后重新初始化)\r\n */\r\nexport function resetAIProvider(): void {\r\n activeProvider = null;\r\n}\r\n\r\n/**\r\n * 检查 AI 功能是否可用\r\n */\r\nexport function isAIAvailable(config?: AIConfig): boolean {\r\n if (!config || !config.provider) return false;\r\n return providerRegistry.has(config.provider);\r\n}\r\n\r\n/**\r\n * 创建独立的 AI Provider 实例(不使用单例缓存)\r\n * 用于需要多个独立上下文的场景,如生成者+审计员双模型\r\n */\r\nexport function createAIProvider(config: AIConfig): NoopAIProvider | OpenAICompatibleProvider {\r\n if (!config || !config.provider) {\r\n return new NoopAIProvider();\r\n }\r\n\r\n const ProviderClass = providerRegistry.get(config.provider);\r\n if (!ProviderClass) {\r\n return new NoopAIProvider();\r\n }\r\n\r\n return new ProviderClass(config);\r\n}\r\n\r\n/**\r\n * 测试 AI 连通性\r\n */\r\nexport async function testAIConnection(config: AIConfig): Promise<{ ok: boolean; message: string; latencyMs?: number }> {\r\n const ProviderClass = providerRegistry.get(config.provider);\r\n if (!ProviderClass) {\r\n return { ok: false, message: `未注册的 Provider: ${config.provider},可选: ${getRegisteredProviders().join(', ')}` };\r\n }\r\n\r\n const provider = new ProviderClass(config);\r\n\r\n if (!(provider instanceof OpenAICompatibleProvider)) {\r\n return { ok: false, message: `${config.provider} 不支持连通性测试` };\r\n }\r\n\r\n return provider.testConnection();\r\n}\r\n\r\n// 重新导出类型\r\nexport type {\r\n AICapability,\r\n AIGenerateTestRequest,\r\n AIGenerateTestResponse,\r\n AIAnalyzeResultRequest,\r\n AIAnalyzeResultResponse,\r\n AIReviewTestRequest,\r\n AIReviewTestResponse,\r\n AIProvider,\r\n AIConfig,\r\n} from '../types/ai.js';\r\n","/**\r\n * 测试用例审计服务 - 生成者 AI 和审计员 AI 双模型审核流程\r\n *\r\n * 流程:\r\n * 1. 生成者 AI 生成测试用例\r\n * 2. 审计员 AI 审查是否贴切准确\r\n * 3. 审计不通过 → 带反馈重新生成,最多 MAX_RETRIES 次\r\n * 4. 最终结果写入测试文件,失败的记录在报告中\r\n *\r\n * 两个模型上下文完全独立:\r\n * - 生成者只接收源码和分析结果\r\n * - 审计员只接收源码 + 生成的测试代码,不接收生成者的内部状态\r\n */\r\n\r\nimport chalk from 'chalk';\r\nimport ora from 'ora';\r\nimport type { AIConfig, TestType } from '../types/index.js';\r\nimport type {\r\n AIGenerateTestResponse,\r\n AIReviewTestRequest,\r\n AIReviewTestResponse,\r\n SourceAnalysisSummary,\r\n} from '../types/ai.js';\r\nimport { createAIProvider } from '../ai/provider.js';\r\n\r\n/** 最大重试次数(避免死循环) */\r\nconst MAX_RETRIES = 3;\r\n\r\n/** 审计通过阈值 */\r\nconst REVIEW_THRESHOLD = 0.6;\r\n\r\n/** 审计结果 */\r\nexport interface ReviewResult {\r\n /** 最终测试代码 */\r\n code: string;\r\n /** 最终描述 */\r\n description: string;\r\n /** 置信度 */\r\n confidence: number;\r\n /** 是否通过审计 */\r\n approved: boolean;\r\n /** 尝试次数 */\r\n attempts: number;\r\n /** 审计评分 */\r\n reviewScore: number;\r\n /** 审计反馈 */\r\n reviewFeedback: string;\r\n /** 审计发现的问题 */\r\n reviewIssues: string[];\r\n /** 审计改进建议 */\r\n reviewSuggestions: string[];\r\n}\r\n\r\n/** 审计报告条目 */\r\nexport interface ReviewReportEntry {\r\n /** 被测文件 */\r\n target: string;\r\n /** 测试类型 */\r\n testType: TestType;\r\n /** 是否通过 */\r\n approved: boolean;\r\n /** 尝试次数 */\r\n attempts: number;\r\n /** 评分 */\r\n score: number;\r\n /** 失败原因/反馈 */\r\n feedback: string;\r\n /** 问题列表 */\r\n issues: string[];\r\n}\r\n\r\n/**\r\n * 带审计的测试生成流程\r\n *\r\n * 生成者和审计员使用同一个 AI 配置但独立的 Provider 实例,\r\n * 确保上下文不会互相污染。\r\n */\r\nexport async function generateWithReview(params: {\r\n testType: TestType;\r\n targetPath: string;\r\n sourceCode: string;\r\n analysis?: SourceAnalysisSummary;\r\n aiConfig: AIConfig;\r\n framework?: string;\r\n onAttempt?: (attempt: number, maxRetries: number) => void;\r\n}): Promise<ReviewResult> {\r\n const { testType, targetPath, sourceCode, analysis, aiConfig, framework, onAttempt } = params;\r\n\r\n // 创建两个独立的 Provider 实例,确保上下文隔离\r\n // 生成者和审计员使用同一个 AI 配置,但各自持有独立的实例\r\n const generatorProvider = createAIProvider(aiConfig);\r\n const reviewerProvider = createAIProvider(aiConfig);\r\n\r\n if (!generatorProvider.capabilities.generateTest) {\r\n throw new Error('当前 AI Provider 不支持测试生成');\r\n }\r\n\r\n let currentCode = '';\r\n let currentDescription = '';\r\n let currentConfidence = 0;\r\n let lastReview: AIReviewTestResponse | null = null;\r\n let approved = false;\r\n let attempts = 0;\r\n\r\n for (let i = 0; i < MAX_RETRIES; i++) {\r\n attempts = i + 1;\r\n onAttempt?.(attempts, MAX_RETRIES);\r\n\r\n // ─── Step 1: 生成者 AI 生成测试 ───\r\n const generationPrompt = i === 0\r\n ? undefined // 首次生成,无需额外提示\r\n : `上次生成的测试未通过审计,请根据以下反馈重新生成:\r\n审计评分: ${(lastReview!.score * 100).toFixed(0)}%\r\n审计意见: ${lastReview!.feedback}\r\n具体问题:\r\n${lastReview!.issues.map((issue) => `- ${issue}`).join('\\n')}\r\n改进建议:\r\n${lastReview!.suggestions.map((s) => `- ${s}`).join('\\n')}\r\n\r\n请针对以上问题重新生成更贴切、更准确的测试用例。`;\r\n\r\n const generateResponse: AIGenerateTestResponse = await generatorProvider.generateTest({\r\n type: testType,\r\n target: targetPath,\r\n context: i === 0 ? sourceCode : `${sourceCode}\\n\\n--- 审计反馈 ---\\n${generationPrompt}`,\r\n framework: (framework as 'vue') || undefined,\r\n analysis,\r\n });\r\n\r\n currentCode = generateResponse.code;\r\n currentDescription = generateResponse.description;\r\n currentConfidence = generateResponse.confidence;\r\n\r\n // ─── Step 2: 审计员 AI 审查 ───\r\n const reviewRequest: AIReviewTestRequest = {\r\n target: targetPath,\r\n sourceCode,\r\n analysis,\r\n testCode: currentCode,\r\n testType,\r\n generationDescription: currentDescription,\r\n };\r\n\r\n lastReview = await reviewerProvider.reviewTest(reviewRequest);\r\n approved = lastReview.approved && lastReview.score >= REVIEW_THRESHOLD;\r\n\r\n if (approved) {\r\n break;\r\n }\r\n }\r\n\r\n return {\r\n code: currentCode,\r\n description: currentDescription,\r\n confidence: currentConfidence,\r\n approved,\r\n attempts,\r\n reviewScore: lastReview?.score ?? 0,\r\n reviewFeedback: lastReview?.feedback ?? '',\r\n reviewIssues: lastReview?.issues ?? [],\r\n reviewSuggestions: lastReview?.suggestions ?? [],\r\n };\r\n}\r\n\r\n/**\r\n * 批量生成并审计测试用例\r\n */\r\nexport async function batchGenerateWithReview(\r\n targets: Array<{\r\n testType: TestType;\r\n targetPath: string;\r\n sourceCode: string;\r\n analysis?: SourceAnalysisSummary;\r\n }>,\r\n aiConfig: AIConfig,\r\n framework?: string,\r\n): Promise<{\r\n results: Map<string, ReviewResult>;\r\n report: ReviewReportEntry[];\r\n}> {\r\n const results = new Map<string, ReviewResult>();\r\n const report: ReviewReportEntry[] = [];\r\n\r\n const spinner = ora(`正在生成并审计测试用例 (0/${targets.length})...`).start();\r\n let completed = 0;\r\n\r\n for (const target of targets) {\r\n completed++;\r\n spinner.text = `正在生成并审计测试用例 (${completed}/${targets.length}) — ${target.targetPath}`;\r\n\r\n try {\r\n const result = await generateWithReview({\r\n testType: target.testType,\r\n targetPath: target.targetPath,\r\n sourceCode: target.sourceCode,\r\n analysis: target.analysis,\r\n aiConfig,\r\n framework,\r\n });\r\n\r\n results.set(target.targetPath, result);\r\n\r\n report.push({\r\n target: target.targetPath,\r\n testType: target.testType,\r\n approved: result.approved,\r\n attempts: result.attempts,\r\n score: result.reviewScore,\r\n feedback: result.reviewFeedback,\r\n issues: result.reviewIssues,\r\n });\r\n } catch (error) {\r\n report.push({\r\n target: target.targetPath,\r\n testType: target.testType,\r\n approved: false,\r\n attempts: 0,\r\n score: 0,\r\n feedback: error instanceof Error ? error.message : String(error),\r\n issues: [],\r\n });\r\n }\r\n }\r\n\r\n const approvedCount = report.filter((r) => r.approved).length;\r\n const failedCount = report.length - approvedCount;\r\n\r\n if (failedCount > 0) {\r\n spinner.warn(`已完成 ${report.length} 个测试用例审计 (${approvedCount} 通过, ${failedCount} 未通过)`);\r\n } else {\r\n spinner.succeed(`已完成 ${report.length} 个测试用例审计 (全部通过)`);\r\n }\r\n\r\n return { results, report };\r\n}\r\n\r\n/**\r\n * 打印审计报告\r\n */\r\nexport function printReviewReport(report: ReviewReportEntry[]): void {\r\n if (report.length === 0) return;\r\n\r\n const approved = report.filter((r) => r.approved);\r\n const failed = report.filter((r) => !r.approved);\r\n\r\n console.log();\r\n console.log(chalk.cyan(' 审计报告:'));\r\n console.log(chalk.gray(' ─────────────────────────────'));\r\n\r\n for (const entry of approved) {\r\n console.log(` ${chalk.green('✓')} ${chalk.gray(entry.target)} ${chalk.green(`(${(entry.score * 100).toFixed(0)}%`)}${entry.attempts > 1 ? chalk.yellow(` ${entry.attempts}次尝试`) : ''})`);\r\n }\r\n\r\n for (const entry of failed) {\r\n console.log(` ${chalk.red('✗')} ${chalk.gray(entry.target)} ${chalk.red(`(${(entry.score * 100).toFixed(0)}%)`)}`);\r\n if (entry.issues.length > 0) {\r\n for (const issue of entry.issues.slice(0, 3)) {\r\n console.log(chalk.gray(` - ${issue}`));\r\n }\r\n }\r\n if (entry.feedback) {\r\n console.log(chalk.gray(` 反馈: ${entry.feedback}`));\r\n }\r\n }\r\n\r\n console.log();\r\n}\r\n","/**\r\n * 源码分析器 - 扫描 TS/JS/Vue 文件,提取导出、Props、Events、API调用 等信息\r\n * 用于生成个性化的测试用例和 Mock 路由\r\n */\r\n\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\n\r\n// ─── 分析结果类型 ────────────────────────────────────────────\r\n\r\n/** 导出项类型 */\r\nexport type ExportKind = 'function' | 'class' | 'const' | 'type' | 'default' | 'component';\r\n\r\n/** 导出项信息 */\r\nexport interface ExportInfo {\r\n /** 导出名称 */\r\n name: string;\r\n /** 导出类型 */\r\n kind: ExportKind;\r\n /** 函数参数列表 */\r\n params: string[];\r\n /** 函数是否有返回值类型注解 */\r\n returnType?: string;\r\n /** 是否为异步函数 */\r\n isAsync: boolean;\r\n}\r\n\r\n/** Vue 组件 Props 信息 */\r\nexport interface PropInfo {\r\n /** prop 名称 */\r\n name: string;\r\n /** prop 类型(如 String, Number, Boolean, Array, Object) */\r\n type: string;\r\n /** 是否必填 */\r\n required: boolean;\r\n /** 默认值 */\r\n defaultValue?: string;\r\n}\r\n\r\n/** Vue 组件 Emits 信息 */\r\nexport interface EmitInfo {\r\n /** 事件名称 */\r\n name: string;\r\n /** 事件参数 */\r\n params: string[];\r\n}\r\n\r\n/** Vue 组件分析结果 */\r\nexport interface VueComponentAnalysis {\r\n /** 组件名称 */\r\n componentName: string;\r\n /** Props 列表 */\r\n props: PropInfo[];\r\n /** Emits 列表 */\r\n emits: EmitInfo[];\r\n /** 组件中定义的方法名 */\r\n methods: string[];\r\n /** 组件中定义的 computed 名 */\r\n computed: string[];\r\n /** 是否使用 setup 语法 */\r\n usesSetup: boolean;\r\n}\r\n\r\n/** API 调用信息 */\r\nexport interface APICallInfo {\r\n /** HTTP 方法 */\r\n method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\r\n /** 请求路径 */\r\n url: string;\r\n /** 来源文件 */\r\n sourceFile: string;\r\n}\r\n\r\n/** TS/JS 文件分析结果 */\r\nexport interface ModuleAnalysis {\r\n /** 文件路径 */\r\n filePath: string;\r\n /** 导出项列表 */\r\n exports: ExportInfo[];\r\n /** Vue 组件分析(仅 .vue 文件) */\r\n vueAnalysis?: VueComponentAnalysis;\r\n /** 发现的 API 调用 */\r\n apiCalls: APICallInfo[];\r\n}\r\n\r\n// ─── 主分析函数 ──────────────────────────────────────────────\r\n\r\n/**\r\n * 分析一个源码文件\r\n */\r\nexport function analyzeFile(filePath: string): ModuleAnalysis {\r\n const absolutePath = path.resolve(process.cwd(), filePath);\r\n\r\n if (!fs.existsSync(absolutePath)) {\r\n return { filePath, exports: [], apiCalls: [] };\r\n }\r\n\r\n const content = fs.readFileSync(absolutePath, 'utf-8');\r\n const ext = path.extname(filePath);\r\n\r\n const result: ModuleAnalysis = {\r\n filePath,\r\n exports: extractExports(content, ext),\r\n apiCalls: extractAPICalls(content, filePath),\r\n };\r\n\r\n // Vue 组件分析\r\n if (ext === '.vue') {\r\n result.vueAnalysis = analyzeVueComponent(content, path.basename(filePath, '.vue'));\r\n // Vue 文件的 <script> 部分也提取 API 调用\r\n const scriptMatch = content.match(/<script[^>]*>([\\s\\S]*?)<\\/script>/);\r\n if (scriptMatch) {\r\n result.apiCalls = extractAPICalls(scriptMatch[1], filePath);\r\n }\r\n }\r\n\r\n return result;\r\n}\r\n\r\n/**\r\n * 批量分析文件\r\n */\r\nexport function analyzeFiles(filePaths: string[]): ModuleAnalysis[] {\r\n return filePaths.map(analyzeFile);\r\n}\r\n\r\n// ─── 导出提取 ────────────────────────────────────────────────\r\n\r\n/**\r\n * 从源码中提取导出项\r\n */\r\nfunction extractExports(content: string, ext: string): ExportInfo[] {\r\n const exports: ExportInfo[] = [];\r\n\r\n // 命名导出: export function foo() / export const bar / export class Baz\r\n const namedExportRegex = /export\\s+(async\\s+)?function\\s+(\\w+)\\s*\\(([^)]*)\\)(\\s*:\\s*([^{]+))?/g;\r\n let match: RegExpExecArray | null;\r\n\r\n while ((match = namedExportRegex.exec(content)) !== null) {\r\n exports.push({\r\n name: match[2],\r\n kind: 'function',\r\n params: parseParams(match[3]),\r\n returnType: match[5]?.trim(),\r\n isAsync: !!match[1],\r\n });\r\n }\r\n\r\n // export const/let/var\r\n const constExportRegex = /export\\s+(const|let|var)\\s+(\\w+)\\s*(?::\\s*([^{=]+))?\\s*[={]/g;\r\n while ((match = constExportRegex.exec(content)) !== null) {\r\n const typeAnnotation = match[3]?.trim();\r\n const kind: ExportKind = typeAnnotation === 'DefineComponent' || isComponentType(typeAnnotation)\r\n ? 'component'\r\n : 'const';\r\n\r\n exports.push({\r\n name: match[2],\r\n kind,\r\n params: [],\r\n returnType: typeAnnotation,\r\n isAsync: false,\r\n });\r\n }\r\n\r\n // export class\r\n const classExportRegex = /export\\s+class\\s+(\\w+)/g;\r\n while ((match = classExportRegex.exec(content)) !== null) {\r\n exports.push({\r\n name: match[1],\r\n kind: 'class',\r\n params: [],\r\n isAsync: false,\r\n });\r\n }\r\n\r\n // export type/interface\r\n const typeExportRegex = /export\\s+(type|interface)\\s+(\\w+)/g;\r\n while ((match = typeExportRegex.exec(content)) !== null) {\r\n exports.push({\r\n name: match[2],\r\n kind: 'type',\r\n params: [],\r\n isAsync: false,\r\n });\r\n }\r\n\r\n // export default\r\n if (/export\\s+default\\s+/.test(content)) {\r\n // export default function\r\n const defaultFuncMatch = /export\\s+default\\s+(async\\s+)?function\\s+(\\w*)\\s*\\(([^)]*)\\)/.exec(content);\r\n if (defaultFuncMatch) {\r\n exports.push({\r\n name: defaultFuncMatch[2] || 'default',\r\n kind: 'default',\r\n params: parseParams(defaultFuncMatch[3]),\r\n isAsync: !!defaultFuncMatch[1],\r\n });\r\n } else {\r\n // export default expression\r\n exports.push({\r\n name: 'default',\r\n kind: ext === '.vue' ? 'component' : 'default',\r\n params: [],\r\n isAsync: false,\r\n });\r\n }\r\n }\r\n\r\n // re-export: export { foo, bar } from './module'\r\n // 这些我们不需要生成测试\r\n\r\n return exports;\r\n}\r\n\r\n/**\r\n * 解析函数参数字符串\r\n */\r\nfunction parseParams(paramsStr: string): string[] {\r\n if (!paramsStr.trim()) return [];\r\n\r\n return paramsStr\r\n .split(',')\r\n .map((p) => {\r\n // 提取参数名(去掉类型注解和默认值)\r\n const trimmed = p.trim();\r\n const nameMatch = trimmed.match(/(?:\\.\\.\\.)?(\\w+)/);\r\n return nameMatch ? nameMatch[1] : trimmed;\r\n })\r\n .filter((p) => p && p !== '_' && !p.startsWith('__'));\r\n}\r\n\r\n/**\r\n * 判断是否为组件类型\r\n */\r\nfunction isComponentType(typeStr?: string): boolean {\r\n if (!typeStr) return false;\r\n return /DefineComponent|FunctionalComponent|Component\\b/i.test(typeStr);\r\n}\r\n\r\n// ─── Vue 组件分析 ────────────────────────────────────────────\r\n\r\n/**\r\n * 分析 Vue 组件\r\n */\r\nfunction analyzeVueComponent(content: string, componentName: string): VueComponentAnalysis {\r\n const analysis: VueComponentAnalysis = {\r\n componentName,\r\n props: [],\r\n emits: [],\r\n methods: [],\r\n computed: [],\r\n usesSetup: false,\r\n };\r\n\r\n // 检测是否使用 <script setup>\r\n analysis.usesSetup = /<script[^>]*setup[^>]*>/.test(content);\r\n\r\n // 提取 <script> 部分内容\r\n const scriptMatch = content.match(/<script[^>]*>([\\s\\S]*?)<\\/script>/);\r\n if (!scriptMatch) return analysis;\r\n\r\n const scriptContent = scriptMatch[1];\r\n\r\n // 解析 Props\r\n analysis.props = extractProps(scriptContent, analysis.usesSetup);\r\n\r\n // 解析 Emits\r\n analysis.emits = extractEmits(scriptContent, analysis.usesSetup);\r\n\r\n // 解析 Methods(仅 Options API)\r\n if (!analysis.usesSetup) {\r\n analysis.methods = extractMethods(scriptContent);\r\n analysis.computed = extractComputed(scriptContent);\r\n }\r\n\r\n return analysis;\r\n}\r\n\r\n/**\r\n * 提取 Props\r\n */\r\nfunction extractProps(scriptContent: string, usesSetup: boolean): PropInfo[] {\r\n const props: PropInfo[] = [];\r\n\r\n if (usesSetup) {\r\n // defineProps 的对象形式: defineProps({ foo: { type: String, required: true } })\r\n const definePropsObjMatch = scriptContent.match(/defineProps\\s*<[^>]*>\\s*\\(\\s*\\)|defineProps\\s*\\(\\s*\\{([\\s\\S]*?)\\}\\s*\\)/);\r\n if (definePropsObjMatch && definePropsObjMatch[1]) {\r\n const objContent = definePropsObjMatch[1];\r\n // 匹配每个 prop 定义\r\n const propRegex = /(\\w+)\\s*:\\s*\\{([^}]*)\\}/g;\r\n let propMatch: RegExpExecArray | null;\r\n while ((propMatch = propRegex.exec(objContent)) !== null) {\r\n const propBody = propMatch[2];\r\n props.push({\r\n name: propMatch[1],\r\n type: extractPropType(propBody),\r\n required: /required\\s*:\\s*true/.test(propBody),\r\n defaultValue: extractPropDefault(propBody),\r\n });\r\n }\r\n }\r\n\r\n // defineProps 的数组形式: defineProps(['foo', 'bar'])\r\n const definePropsArrMatch = scriptContent.match(/defineProps\\s*\\(\\s*\\[([^\\]]*)\\]\\s*\\)/);\r\n if (definePropsArrMatch && definePropsArrMatch[1]) {\r\n const names = definePropsArrMatch[1].match(/'([^']+)'/g);\r\n if (names) {\r\n for (const name of names) {\r\n const cleanName = name.replace(/'/g, '');\r\n if (!props.some((p) => p.name === cleanName)) {\r\n props.push({\r\n name: cleanName,\r\n type: 'any',\r\n required: false,\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n // defineProps 的类型形式: defineProps<{ foo: string, bar?: number }>()\r\n const typePropsMatch = scriptContent.match(/defineProps\\s*<\\s*\\{([^}]*)\\}/);\r\n if (typePropsMatch && typePropsMatch[1] && props.length === 0) {\r\n const typeContent = typePropsMatch[1];\r\n const typePropRegex = /(\\??\\s*)(\\w+)\\s*:\\s*([^;,\\n]+)/g;\r\n let typeMatch: RegExpExecArray | null;\r\n while ((typeMatch = typePropRegex.exec(typeContent)) !== null) {\r\n props.push({\r\n name: typeMatch[2],\r\n type: typeMatch[3].trim(),\r\n required: !typeMatch[1].includes('?'),\r\n });\r\n }\r\n }\r\n } else {\r\n // Options API: props: { ... }\r\n const propsObjMatch = scriptContent.match(/props\\s*:\\s*\\{([\\s\\S]*?)\\}\\s*,?\\s*(?:emits|data|computed|methods|setup|components|watch|$)/);\r\n if (propsObjMatch && propsObjMatch[1]) {\r\n const propRegex = /(\\w+)\\s*:\\s*\\{([^}]*)\\}/g;\r\n let propMatch: RegExpExecArray | null;\r\n while ((propMatch = propRegex.exec(propsObjMatch[1])) !== null) {\r\n const propBody = propMatch[2];\r\n props.push({\r\n name: propMatch[1],\r\n type: extractPropType(propBody),\r\n required: /required\\s*:\\s*true/.test(propBody),\r\n defaultValue: extractPropDefault(propBody),\r\n });\r\n }\r\n }\r\n\r\n // Array 形式: props: ['foo', 'bar']\r\n const propsArrMatch = scriptContent.match(/props\\s*:\\s*\\[([^\\]]*)\\]/);\r\n if (propsArrMatch && propsArrMatch[1]) {\r\n const names = propsArrMatch[1].match(/'([^']+)'/g);\r\n if (names) {\r\n for (const name of names) {\r\n const cleanName = name.replace(/'/g, '');\r\n if (!props.some((p) => p.name === cleanName)) {\r\n props.push({\r\n name: cleanName,\r\n type: 'any',\r\n required: false,\r\n });\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return props;\r\n}\r\n\r\n/**\r\n * 提取 prop 类型\r\n */\r\nfunction extractPropType(propBody: string): string {\r\n const typeMatch = propBody.match(/type\\s*:\\s*(\\w+)/);\r\n if (typeMatch) return typeMatch[1];\r\n return 'any';\r\n}\r\n\r\n/**\r\n * 提取 prop 默认值\r\n */\r\nfunction extractPropDefault(propBody: string): string | undefined {\r\n const defaultMatch = propBody.match(/default\\s*:\\s*([^,}\\n]+)/);\r\n if (defaultMatch) return defaultMatch[1].trim();\r\n return undefined;\r\n}\r\n\r\n/**\r\n * 提取 Emits\r\n */\r\nfunction extractEmits(scriptContent: string, usesSetup: boolean): EmitInfo[] {\r\n const emits: EmitInfo[] = [];\r\n\r\n if (usesSetup) {\r\n // defineEmits(['foo', 'bar'])\r\n const arrMatch = scriptContent.match(/defineEmits\\s*\\(\\s*\\[([^\\]]*)\\]\\s*\\)/);\r\n if (arrMatch && arrMatch[1]) {\r\n const names = arrMatch[1].match(/'([^']+)'/g);\r\n if (names) {\r\n for (const name of names) {\r\n emits.push({\r\n name: name.replace(/'/g, ''),\r\n params: [],\r\n });\r\n }\r\n }\r\n }\r\n\r\n // defineEmits<{ foo: [...], bar: [...] }>()\r\n const typeMatch = scriptContent.match(/defineEmits\\s*<\\s*\\{([^}]*)\\}/);\r\n if (typeMatch && typeMatch[1] && emits.length === 0) {\r\n const emitRegex = /(\\w+)\\s*:\\s*\\(([^)]*)\\)/g;\r\n let emitMatch: RegExpExecArray | null;\r\n while ((emitMatch = emitRegex.exec(typeMatch[1])) !== null) {\r\n emits.push({\r\n name: emitMatch[1],\r\n params: parseParams(emitMatch[2]),\r\n });\r\n }\r\n }\r\n } else {\r\n // Options API: emits: ['foo', 'bar'] or emits: { ... }\r\n const arrMatch = scriptContent.match(/emits\\s*:\\s*\\[([^\\]]*)\\]/);\r\n if (arrMatch && arrMatch[1]) {\r\n const names = arrMatch[1].match(/'([^']+)'/g);\r\n if (names) {\r\n for (const name of names) {\r\n emits.push({\r\n name: name.replace(/'/g, ''),\r\n params: [],\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n return emits;\r\n}\r\n\r\n/**\r\n * 提取 Options API 方法\r\n */\r\nfunction extractMethods(scriptContent: string): string[] {\r\n const methods: string[] = [];\r\n const methodsMatch = scriptContent.match(/methods\\s*:\\s*\\{([\\s\\S]*?)\\}\\s*,?\\s*(?:computed|watch|components|mounted|created|setup|$)/);\r\n if (methodsMatch && methodsMatch[1]) {\r\n const methodRegex = /(?:async\\s+)?(\\w+)\\s*\\(/g;\r\n let match: RegExpExecArray | null;\r\n while ((match = methodRegex.exec(methodsMatch[1])) !== null) {\r\n const name = match[1];\r\n if (name !== 'constructor' && !methods.includes(name)) {\r\n methods.push(name);\r\n }\r\n }\r\n }\r\n return methods;\r\n}\r\n\r\n/**\r\n * 提取 computed 属性\r\n */\r\nfunction extractComputed(scriptContent: string): string[] {\r\n const computed: string[] = [];\r\n const computedMatch = scriptContent.match(/computed\\s*:\\s*\\{([\\s\\S]*?)\\}\\s*,?\\s*(?:methods|watch|components|mounted|created|setup|$)/);\r\n if (computedMatch && computedMatch[1]) {\r\n const propRegex = /(\\w+)\\s*(?:\\(\\)|\\{)/g;\r\n let match: RegExpExecArray | null;\r\n while ((match = propRegex.exec(computedMatch[1])) !== null) {\r\n const name = match[1];\r\n if (!computed.includes(name)) {\r\n computed.push(name);\r\n }\r\n }\r\n }\r\n return computed;\r\n}\r\n\r\n// ─── API 调用提取 ────────────────────────────────────────────\r\n\r\n/**\r\n * 从源码中提取 API 调用信息\r\n */\r\nfunction extractAPICalls(content: string, sourceFile: string): APICallInfo[] {\r\n const calls: APICallInfo[] = [];\r\n const seen = new Set<string>();\r\n\r\n // 1. fetch('url', { method: 'POST' }) 或 fetch(`url`)\r\n const fetchRegex = /fetch\\s*\\(\\s*[`'\"]([^`'\"]+)[`'\"]/g;\r\n let match: RegExpExecArray | null;\r\n while ((match = fetchRegex.exec(content)) !== null) {\r\n const url = cleanTemplateUrl(match[1]);\r\n // 检查同一行附近是否有 method 指定\r\n const afterFetch = content.slice(match.index, match.index + 300);\r\n const methodMatch = afterFetch.match(/method\\s*:\\s*['\"]?(GET|POST|PUT|DELETE|PATCH)['\"]?/i);\r\n const method = methodMatch ? methodMatch[1].toUpperCase() as APICallInfo['method'] : 'GET';\r\n const key = `${method}:${url}`;\r\n if (!seen.has(key) && url.startsWith('/')) {\r\n seen.add(key);\r\n calls.push({ method, url, sourceFile });\r\n }\r\n }\r\n\r\n // 2. axios.get('url') / axios.post('url') / axios.put / axios.delete / axios.patch\r\n const axiosRegex = /axios\\s*\\.\\s*(get|post|put|delete|patch)\\s*\\(\\s*[`'\"]([^`'\"]+)[`'\"]/gi;\r\n while ((match = axiosRegex.exec(content)) !== null) {\r\n const method = match[1].toUpperCase() as APICallInfo['method'];\r\n const url = cleanTemplateUrl(match[2]);\r\n const key = `${method}:${url}`;\r\n if (!seen.has(key) && url.startsWith('/')) {\r\n seen.add(key);\r\n calls.push({ method, url, sourceFile });\r\n }\r\n }\r\n\r\n // 3. useFetch('url') / useFetch('url', { method: 'POST' }) (Nuxt/composable)\r\n const useFetchRegex = /useFetch\\s*\\(\\s*[`'\"]([^`'\"]+)[`'\"]/g;\r\n while ((match = useFetchRegex.exec(content)) !== null) {\r\n const url = cleanTemplateUrl(match[1]);\r\n const afterUseFetch = content.slice(match.index, match.index + 300);\r\n const methodMatch = afterUseFetch.match(/method\\s*:\\s*['\"]?(GET|POST|PUT|DELETE|PATCH)['\"]?/i);\r\n const method = methodMatch ? methodMatch[1].toUpperCase() as APICallInfo['method'] : 'GET';\r\n const key = `${method}:${url}`;\r\n if (!seen.has(key) && url.startsWith('/')) {\r\n seen.add(key);\r\n calls.push({ method, url, sourceFile });\r\n }\r\n }\r\n\r\n // 4. $fetch('url', { method: 'POST' }) (Nuxt)\r\n const dollarFetchRegex = /\\$fetch\\s*\\(\\s*[`'\"]([^`'\"]+)[`'\"]/g;\r\n while ((match = dollarFetchRegex.exec(content)) !== null) {\r\n const url = cleanTemplateUrl(match[1]);\r\n const afterFetch = content.slice(match.index, match.index + 300);\r\n const methodMatch = afterFetch.match(/method\\s*:\\s*['\"]?(GET|POST|PUT|DELETE|PATCH)['\"]?/i);\r\n const method = methodMatch ? methodMatch[1].toUpperCase() as APICallInfo['method'] : 'GET';\r\n const key = `${method}:${url}`;\r\n if (!seen.has(key) && url.startsWith('/')) {\r\n seen.add(key);\r\n calls.push({ method, url, sourceFile });\r\n }\r\n }\r\n\r\n // 5. httpRequest.get('url') / http.post('url') 等自定义实例\r\n const httpRegex = /\\w+\\s*\\.\\s*(get|post|put|delete|patch)\\s*\\(\\s*[`'\"]([^`'\"]+)[`'\"]/gi;\r\n while ((match = httpRegex.exec(content)) !== null) {\r\n // 排除 axios 已匹配的\r\n const fullLine = content.slice(Math.max(0, match.index - 20), match.index + match[0].length);\r\n if (/axios/i.test(fullLine)) continue;\r\n const method = match[1].toUpperCase() as APICallInfo['method'];\r\n const url = cleanTemplateUrl(match[2]);\r\n const key = `${method}:${url}`;\r\n if (!seen.has(key) && url.startsWith('/')) {\r\n seen.add(key);\r\n calls.push({ method, url, sourceFile });\r\n }\r\n }\r\n\r\n return calls;\r\n}\r\n\r\n/**\r\n * 清理模板 URL 中的变量插值\r\n * `/api/users/${id}` → `/api/users/:id`\r\n */\r\nfunction cleanTemplateUrl(url: string): string {\r\n return url\r\n .replace(/\\$\\{[^}]*\\}/g, ':param')\r\n .replace(/\\/+/g, '/')\r\n .replace(/:param/g, (_match, offset, str) => {\r\n // 尝试从前面的路径推断参数名\r\n const before = str.slice(Math.max(0, offset - 20), offset);\r\n const nameMatch = before.match(/(\\w+)Id|by(\\w+)|(\\w+)Id/i);\r\n if (nameMatch) {\r\n const name = (nameMatch[1] || nameMatch[2] || nameMatch[3] || 'param').toLowerCase();\r\n return `:${name}`;\r\n }\r\n return ':id';\r\n });\r\n}\r\n\r\n// ─── 扫描目录提取 API ────────────────────────────────────────\r\n\r\n/**\r\n * 扫描项目源码目录,提取所有 API 调用\r\n */\r\nexport function scanAPICalls(srcDir: string): APICallInfo[] {\r\n const absDir = path.resolve(process.cwd(), srcDir);\r\n\r\n if (!fs.existsSync(absDir)) {\r\n return [];\r\n }\r\n\r\n const allCalls: APICallInfo[] = [];\r\n const seen = new Set<string>();\r\n\r\n function walk(dir: string): void {\r\n const entries = fs.readdirSync(dir, { withFileTypes: true });\r\n for (const entry of entries) {\r\n // 跳过 node_modules, dist, .git 等\r\n if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'dist') continue;\r\n\r\n const fullPath = path.join(dir, entry.name);\r\n if (entry.isDirectory()) {\r\n walk(fullPath);\r\n } else if (entry.isFile() && /\\.(ts|js|vue|mjs)$/.test(entry.name)) {\r\n try {\r\n const content = fs.readFileSync(fullPath, 'utf-8');\r\n const calls = extractAPICalls(content, path.relative(process.cwd(), fullPath));\r\n for (const call of calls) {\r\n const key = `${call.method}:${call.url}`;\r\n if (!seen.has(key)) {\r\n seen.add(key);\r\n allCalls.push(call);\r\n }\r\n }\r\n } catch {\r\n // 忽略读取失败\r\n }\r\n }\r\n }\r\n }\r\n\r\n walk(absDir);\r\n return allCalls;\r\n}\r\n\r\n/**\r\n * 根据 API 调用信息自动生成 Mock 路由配置\r\n */\r\nexport function generateMockRoutesFromAPICalls(apiCalls: APICallInfo[]): MockRouteCandidate[] {\r\n const routes: MockRouteCandidate[] = [];\r\n\r\n for (const call of apiCalls) {\r\n // 从 URL 路径推断资源名\r\n const segments = call.url.split('/').filter(Boolean);\r\n const resourceName = segments[segments.length - 1]?.replace(/^:/, '') || 'resource';\r\n\r\n let response: Record<string, unknown> | null;\r\n let status = 200;\r\n\r\n switch (call.method) {\r\n case 'GET':\r\n if (call.url.includes(':')) {\r\n // 单个资源\r\n response = { message: 'Success', data: { id: 1, name: `${resourceName}-item` } };\r\n } else {\r\n // 列表\r\n response = { message: 'Success', data: [{ id: 1, name: `${resourceName}-1` }], total: 1 };\r\n }\r\n break;\r\n case 'POST':\r\n status = 201;\r\n response = { message: 'Created', data: { id: 2, name: `{{body.name}}` } };\r\n break;\r\n case 'PUT':\r\n response = { message: 'Updated', data: { id: 1, name: `{{body.name}}` } };\r\n break;\r\n case 'DELETE':\r\n status = 204;\r\n response = null;\r\n break;\r\n case 'PATCH':\r\n response = { message: 'Updated', data: { id: 1, name: `{{body.name}}` } };\r\n break;\r\n default:\r\n response = { message: 'Success' };\r\n }\r\n\r\n routes.push({\r\n method: call.method,\r\n path: call.url,\r\n status,\r\n response,\r\n delay: 100,\r\n sourceFile: call.sourceFile,\r\n });\r\n }\r\n\r\n return routes;\r\n}\r\n\r\n/** 自动生成的 Mock 路由候选项 */\r\nexport interface MockRouteCandidate {\r\n method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\r\n path: string;\r\n status?: number;\r\n response?: unknown;\r\n delay?: number;\r\n /** 来源文件 */\r\n sourceFile: string;\r\n}\r\n","/**\r\n * 模板引擎 - Handlebars渲染测试用例模板,支持自定义模板注册和Vue版本适配\r\n */\r\n\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport { fileURLToPath } from 'node:url';\r\nimport Handlebars from 'handlebars';\r\nimport type { TestType, FrameworkType, UILibrary } from '../types/index.js';\r\n\r\nconst __filename = fileURLToPath(import.meta.url);\r\nconst __dirname = path.dirname(__filename);\r\n\r\n/** 导出项信息(简化版,用于模板上下文) */\r\nexport interface TemplateExportInfo {\r\n name: string;\r\n kind: string;\r\n params: string[];\r\n isAsync: boolean;\r\n returnType?: string;\r\n}\r\n\r\n/** Props 信息(简化版,用于模板上下文) */\r\nexport interface TemplatePropInfo {\r\n name: string;\r\n type: string;\r\n required: boolean;\r\n defaultValue?: string;\r\n}\r\n\r\n/** Emits 信息(简化版,用于模板上下文) */\r\nexport interface TemplateEmitInfo {\r\n name: string;\r\n params: string[];\r\n}\r\n\r\n/** 模板变量 */\r\nexport interface TemplateContext {\r\n /** 测试用例名称 */\r\n name: string;\r\n /** camelCase 格式名称(用于 import 变量名) */\r\n camelName?: string;\r\n /** PascalCase 格式名称(用于组件名) */\r\n pascalName?: string;\r\n /** 被测目标文件路径 */\r\n target: string;\r\n /** 前端框架 */\r\n framework: FrameworkType;\r\n /** Vue 主版本 */\r\n vueVersion?: 2 | 3;\r\n /** 是否使用 TypeScript */\r\n typescript?: boolean;\r\n /** UI 组件库 */\r\n uiLibrary?: UILibrary;\r\n /** 额外的导入语句 */\r\n imports?: string[];\r\n /** 组件测试需要的额外导入 */\r\n extraImports?: string[];\r\n /** 组件测试全局插件 */\r\n globalPlugins?: string[];\r\n /** 组件测试全局 stubs */\r\n globalStubs?: string[];\r\n /** mount 配置选项代码片段 */\r\n mountOptions?: string;\r\n /** ── 源码分析结果 ── */\r\n /** 是否有源码分析数据 */\r\n hasAnalysis?: boolean;\r\n /** 导出项列表 */\r\n exports?: TemplateExportInfo[];\r\n /** 函数类型导出 */\r\n functionExports?: TemplateExportInfo[];\r\n /** 非函数类型导出(const/class/component) */\r\n valueExports?: TemplateExportInfo[];\r\n /** Vue 组件 Props */\r\n props?: TemplatePropInfo[];\r\n /** Vue 组件 Emits */\r\n emits?: TemplateEmitInfo[];\r\n /** Vue 组件方法(Options API) */\r\n methods?: string[];\r\n /** Vue 组件 computed(Options API) */\r\n computed?: string[];\r\n /** 是否为 Vue 组件 */\r\n isVueComponent?: boolean;\r\n /** 必填 props */\r\n requiredProps?: TemplatePropInfo[];\r\n /** 有默认值的 props */\r\n optionalProps?: TemplatePropInfo[];\r\n /** 自定义变量 */\r\n [key: string]: unknown;\r\n}\r\n\r\n/** 模板类型到文件名映射 */\r\nconst TEMPLATE_MAP: Record<TestType, string> = {\r\n unit: 'unit-test.hbs',\r\n component: 'component-test.hbs',\r\n e2e: 'e2e-test.hbs',\r\n api: 'api-test.hbs',\r\n visual: 'visual-test.hbs',\r\n performance: 'performance-test.hbs',\r\n};\r\n\r\n/** 自定义模板注册表 */\r\nconst customTemplates = new Map<string, string>();\r\n\r\n// 注册 Handlebars 辅助函数\r\nHandlebars.registerHelper('eq', (a: unknown, b: unknown) => a === b);\r\nHandlebars.registerHelper('neq', (a: unknown, b: unknown) => a !== b);\r\nHandlebars.registerHelper('includes', (arr: unknown[], val: unknown) => Array.isArray(arr) && arr.includes(val));\r\nHandlebars.registerHelper('join', (arr: unknown[], sep: string) => Array.isArray(arr) ? arr.join(sep) : '');\r\nHandlebars.registerHelper('camelCase', (str: string) => toCamelCase(str));\r\nHandlebars.registerHelper('pascalCase', (str: string) => toPascalCase(str));\r\nHandlebars.registerHelper('length', (arr: unknown) => Array.isArray(arr) ? arr.length : 0);\r\nHandlebars.registerHelper('gt', (a: unknown, b: unknown) => Number(a) > Number(b));\r\nHandlebars.registerHelper('and', (...args: unknown[]) => {\r\n args.pop(); // Handlebars options\r\n return args.every(Boolean);\r\n});\r\nHandlebars.registerHelper('or', (...args: unknown[]) => {\r\n args.pop(); // Handlebars options\r\n return args.some(Boolean);\r\n});\r\nHandlebars.registerHelper('propDefaultValue', (prop: TemplatePropInfo) => {\r\n if (!prop.defaultValue) return 'undefined';\r\n const map: Record<string, string> = { String: \"''\", Number: '0', Boolean: 'false', Array: '[]', Object: '{}' };\r\n return map[prop.type] || prop.defaultValue;\r\n});\r\nHandlebars.registerHelper('propTestValue', (prop: TemplatePropInfo) => {\r\n const map: Record<string, string> = {\r\n String: \"'test-value'\",\r\n Number: '42',\r\n Boolean: 'true',\r\n Array: '[1, 2, 3]',\r\n Object: '{ key: \"value\" }',\r\n };\r\n return map[prop.type] || \"'test-value'\";\r\n});\r\n\r\n/**\r\n * 注册自定义模板\r\n * @param type 测试类型\r\n * @param templateContent 模板内容(Handlebars 格式)\r\n */\r\nexport function registerTemplate(type: string, templateContent: string): void {\r\n customTemplates.set(type, templateContent);\r\n}\r\n\r\n/**\r\n * 渲染测试用例模板\r\n */\r\nexport function renderTemplate(type: TestType, context: TemplateContext): string {\r\n const templateContent = loadTemplate(type);\r\n const template = Handlebars.compile(templateContent);\r\n\r\n // 设置默认值(context 中的值优先)\r\n const fullContext: TemplateContext = {\r\n vueVersion: 3,\r\n typescript: true,\r\n imports: [],\r\n extraImports: [],\r\n globalPlugins: [],\r\n globalStubs: [],\r\n mountOptions: '',\r\n hasAnalysis: false,\r\n exports: [],\r\n functionExports: [],\r\n valueExports: [],\r\n props: [],\r\n emits: [],\r\n methods: [],\r\n computed: [],\r\n isVueComponent: false,\r\n requiredProps: [],\r\n optionalProps: [],\r\n ...context,\r\n framework: context.framework || 'vue',\r\n camelName: context.camelName || toCamelCase(context.name),\r\n pascalName: context.pascalName || toPascalCase(context.name),\r\n };\r\n\r\n // 如果有分析数据,派生辅助字段\r\n if (fullContext.exports && fullContext.exports.length > 0) {\r\n fullContext.hasAnalysis = true;\r\n fullContext.functionExports = fullContext.exports.filter(\r\n (e) => e.kind === 'function' || e.kind === 'default',\r\n );\r\n fullContext.valueExports = fullContext.exports.filter(\r\n (e) => e.kind !== 'function' && e.kind !== 'default' && e.kind !== 'type',\r\n );\r\n }\r\n\r\n if (fullContext.props && fullContext.props.length > 0) {\r\n fullContext.isVueComponent = true;\r\n fullContext.requiredProps = fullContext.props.filter((p) => p.required);\r\n fullContext.optionalProps = fullContext.props.filter((p) => !p.required);\r\n }\r\n\r\n if (fullContext.emits && fullContext.emits.length > 0) {\r\n fullContext.isVueComponent = true;\r\n }\r\n\r\n return template(fullContext);\r\n}\r\n\r\n/**\r\n * 加载模板文件\r\n */\r\nfunction loadTemplate(type: TestType): string {\r\n // 优先使用自定义注册的模板\r\n const custom = customTemplates.get(type);\r\n if (custom) return custom;\r\n\r\n // 查找文件系统中的模板\r\n const templateDir = getTemplateDir();\r\n const templateFile = TEMPLATE_MAP[type];\r\n const templatePath = path.join(templateDir, templateFile);\r\n\r\n if (fs.existsSync(templatePath)) {\r\n return fs.readFileSync(templatePath, 'utf-8');\r\n }\r\n\r\n // 使用内置模板\r\n return getBuiltinTemplate(type);\r\n}\r\n\r\n/**\r\n * 获取模板目录\r\n */\r\nfunction getTemplateDir(): string {\r\n // 优先查找项目目录下的 templates\r\n const projectTemplates = path.join(process.cwd(), 'templates');\r\n if (fs.existsSync(projectTemplates)) {\r\n return projectTemplates;\r\n }\r\n // 使用内置模板目录\r\n return path.join(__dirname, '..', 'templates');\r\n}\r\n\r\n/**\r\n * 内置模板 - 适配 Vue 2/3 和 TypeScript\r\n */\r\nfunction getBuiltinTemplate(type: TestType): string {\r\n const templates: Record<TestType, string> = {\r\n unit: `import { describe, it, expect } from 'vitest';\r\n{{#if hasAnalysis}}\r\n{{#each valueExports}}\r\nimport { {{name}} } from '{{../target}}';\r\n{{/each}}\r\n{{#each functionExports}}\r\nimport { {{name}} } from '{{../target}}';\r\n{{/each}}\r\n{{else}}\r\nimport { {{camelName}} } from '{{target}}';\r\n{{/if}}\r\n\r\ndescribe('{{name}}', () => {\r\n{{#if hasAnalysis}}\r\n {{#each functionExports}}\r\n describe('{{name}}()', () => {\r\n {{#if isAsync}}\r\n it('should resolve successfully', async () => {\r\n const result = await {{name}}({{#each params}}{{#unless @first}}, {{/unless}}{{this}}: undefined{{/each}});\r\n expect(result).toBeDefined();\r\n });\r\n {{else}}\r\n it('should return a value', () => {\r\n const result = {{name}}({{#each params}}{{#unless @first}}, {{/unless}}undefined as any{{/each}});\r\n expect(result).toBeDefined();\r\n });\r\n {{/if}}\r\n {{#if returnType}}\r\n it('should return correct type', () => {\r\n {{#if isAsync}}\r\n const result = await {{name}}({{#each params}}{{#unless @first}}, {{/unless}}undefined as any{{/each}});\r\n {{else}}\r\n const result = {{name}}({{#each params}}{{#unless @first}}, {{/unless}}undefined as any{{/each}});\r\n {{/if}}\r\n expect(result).toBeDefined();\r\n });\r\n {{/if}}\r\n {{#if params.length}}\r\n it('should handle edge cases for parameters', () => {\r\n {{#if isAsync}}\r\n const result = await {{name}}({{#each params}}{{#unless @first}}, {{/unless}}undefined as any{{/each}});\r\n {{else}}\r\n const result = {{name}}({{#each params}}{{#unless @first}}, {{/unless}}undefined as any{{/each}});\r\n {{/if}}\r\n expect(result).toBeDefined();\r\n });\r\n {{/if}}\r\n });\r\n\r\n {{/each}}\r\n {{#each valueExports}}\r\n describe('{{name}}', () => {\r\n it('should be defined', () => {\r\n expect({{name}}).toBeDefined();\r\n });\r\n {{#if eq kind 'const'}}\r\n it('should have expected value', () => {\r\n // Adjust assertion based on actual value\r\n expect({{name}}).toBeDefined();\r\n });\r\n {{/if}}\r\n {{#if eq kind 'class'}}\r\n it('should be instantiable', () => {\r\n const instance = new {{name}}();\r\n expect(instance).toBeInstanceOf({{name}});\r\n });\r\n {{/if}}\r\n });\r\n\r\n {{/each}}\r\n{{else}}\r\n it('should work correctly', () => {\r\n // TODO: add test logic\r\n expect(true).toBe(true);\r\n });\r\n{{/if}}\r\n});\r\n`,\r\n\r\n component: `import { describe, it, expect } from 'vitest';\r\nimport { mount } from '@vue/test-utils';\r\n{{#each extraImports}}\r\n{{this}}\r\n{{/each}}\r\nimport {{pascalName}} from '{{target}}';\r\n\r\ndescribe('{{name}}', () => {\r\n {{#if isVueComponent}}\r\n const mountComponent = (propsData: Record<string, any> = {}) => {\r\n return mount({{pascalName}}, {\r\n {{#if mountOptions}}\r\n ...{{mountOptions}},\r\n {{/if}}\r\n props: propsData,\r\n });\r\n };\r\n\r\n it('renders correctly', () => {\r\n const wrapper = mountComponent({{#if requiredProps}}{\r\n {{#each requiredProps}}\r\n {{name}}: {{propTestValue this}},\r\n {{/each}}\r\n }{{/if}});\r\n expect(wrapper.exists()).toBe(true);\r\n });\r\n\r\n {{#if requiredProps}}\r\n it('renders with required props', () => {\r\n const wrapper = mountComponent({\r\n {{#each requiredProps}}\r\n {{name}}: {{propTestValue this}},\r\n {{/each}}\r\n });\r\n expect(wrapper.exists()).toBe(true);\r\n });\r\n\r\n {{/if}}\r\n {{#if optionalProps}}\r\n it('renders without optional props', () => {\r\n const wrapper = mountComponent({{#if requiredProps}}{\r\n {{#each requiredProps}}\r\n {{name}}: {{propTestValue this}},\r\n {{/each}}\r\n }{{/if}});\r\n expect(wrapper.exists()).toBe(true);\r\n });\r\n\r\n it('applies optional props correctly', () => {\r\n const wrapper = mountComponent({\r\n {{#each requiredProps}}\r\n {{name}}: {{propTestValue this}},\r\n {{/each}}\r\n {{#each optionalProps}}\r\n {{name}}: {{propTestValue this}},\r\n {{/each}}\r\n });\r\n expect(wrapper.exists()).toBe(true);\r\n });\r\n\r\n {{/if}}\r\n {{#if emits}}\r\n {{#each emits}}\r\n it('emits {{name}} event', async () => {\r\n const wrapper = mountComponent({{#if ../requiredProps}}{\r\n {{#each ../requiredProps}}\r\n {{name}}: {{propTestValue this}},\r\n {{/each}}\r\n }{{/if}});\r\n // TODO: trigger action that emits '{{name}}'\r\n // await wrapper.find('button').trigger('click');\r\n // expect(wrapper.emitted('{{name}}')).toBeTruthy();\r\n });\r\n\r\n {{/each}}\r\n {{/if}}\r\n {{#if computed}}\r\n {{#each computed}}\r\n it('computes {{this}} correctly', () => {\r\n const wrapper = mountComponent({{#if ../requiredProps}}{\r\n {{#each ../requiredProps}}\r\n {{name}}: {{propTestValue this}},\r\n {{/each}}\r\n }{{/if}});\r\n // TODO: verify computed property '{{this}}'\r\n // expect(wrapper.vm.{{this}}).toBeDefined();\r\n });\r\n\r\n {{/each}}\r\n {{/if}}\r\n {{#if methods}}\r\n {{#each methods}}\r\n it('calls {{this}} method', async () => {\r\n const wrapper = mountComponent({{#if ../requiredProps}}{\r\n {{#each ../requiredProps}}\r\n {{name}}: {{propTestValue this}},\r\n {{/each}}\r\n }{{/if}});\r\n // TODO: test method '{{this}}'\r\n // await wrapper.vm.{{this}}();\r\n // expect(someCondition).toBe(true);\r\n });\r\n\r\n {{/each}}\r\n {{/if}}\r\n {{else}}\r\n it('renders correctly', () => {\r\n const wrapper = mount({{pascalName}}{{#if mountOptions}}, {{mountOptions}}{{/if}});\r\n expect(wrapper.exists()).toBe(true);\r\n });\r\n\r\n it('renders default slot content', () => {\r\n const wrapper = mount({{pascalName}}, {\r\n {{#if mountOptions}}\r\n ...{{mountOptions}},\r\n {{/if}}\r\n slots: {\r\n default: 'Test Content',\r\n },\r\n });\r\n expect(wrapper.text()).toContain('Test Content');\r\n });\r\n\r\n it('handles user interaction', async () => {\r\n const wrapper = mount({{pascalName}}{{#if mountOptions}}, {{mountOptions}}{{/if}});\r\n // TODO: add interaction test\r\n });\r\n {{/if}}\r\n});\r\n`,\r\n\r\n e2e: `import { test, expect } from '@playwright/test';\r\n\r\ntest.describe('{{name}}', () => {\r\n test.beforeEach(async ({ page }) => {\r\n await page.goto('/');\r\n });\r\n\r\n test('should display page correctly', async ({ page }) => {\r\n // TODO: 添加E2E测试断言\r\n await expect(page).toHaveTitle(/.*/);\r\n });\r\n\r\n test('should navigate between pages', async ({ page }) => {\r\n // TODO: 添加导航测试\r\n });\r\n\r\n test('should be responsive', async ({ page }) => {\r\n await page.setViewportSize({ width: 375, height: 667 });\r\n // TODO: 添加移动端断言\r\n });\r\n});\r\n`,\r\n\r\n api: `import { describe, it, expect, beforeAll, afterAll } from 'vitest';\r\n{{#if imports}}\r\n{{#each imports}}\r\nimport {{this}};\r\n{{/each}}\r\n{{/if}}\r\n\r\ndescribe('{{name}} API', () => {\r\n const baseUrl = process.env.API_BASE_URL || 'http://localhost:3456';\r\n\r\n it('should return 200 for GET request', async () => {\r\n const response = await fetch(\\`\\${baseUrl}/api/{{camelName}}\\`);\r\n expect(response.status).toBe(200);\r\n });\r\n\r\n it('should return expected response format', async () => {\r\n const response = await fetch(\\`\\${baseUrl}/api/{{camelName}}\\`);\r\n const data = await response.json();\r\n expect(data).toBeDefined();\r\n });\r\n\r\n it('should handle error responses', async () => {\r\n const response = await fetch(\\`\\${baseUrl}/api/{{camelName}}/nonexistent\\`, {\r\n method: 'GET',\r\n });\r\n expect(response.status).toBeGreaterThanOrEqual(400);\r\n });\r\n});\r\n`,\r\n\r\n visual: `import { test, expect } from '@playwright/test';\r\n\r\ntest.describe('{{name}} visual regression', () => {\r\n test.beforeEach(async ({ page }) => {\r\n await page.goto('/');\r\n });\r\n\r\n test('should match baseline screenshot - desktop', async ({ page }) => {\r\n await expect(page).toHaveScreenshot('{{name}}-desktop.png');\r\n });\r\n\r\n test('should match baseline screenshot - mobile', async ({ page }) => {\r\n await page.setViewportSize({ width: 375, height: 667 });\r\n await expect(page).toHaveScreenshot('{{name}}-mobile.png');\r\n });\r\n});\r\n`,\r\n\r\n performance: `import { test, expect } from '@playwright/test';\r\n\r\ntest.describe('{{name}} performance', () => {\r\n test('should meet Core Web Vitals thresholds', async ({ page }) => {\r\n await page.goto('/');\r\n\r\n // 测量页面加载性能\r\n const performanceMetrics = await page.evaluate(() => {\r\n const [entry] = performance.getEntriesByType('navigation') as PerformanceNavigationTiming[];\r\n return {\r\n domContentLoaded: entry?.domContentLoadedEventEnd - entry?.domContentLoadedEventStart ?? 0,\r\n loadComplete: entry?.loadEventEnd - entry?.loadEventStart ?? 0,\r\n domInteractive: entry?.domInteractive ?? 0,\r\n };\r\n });\r\n\r\n // DOM 内容加载应在 2s 内\r\n expect(performanceMetrics.domInteractive).toBeLessThan(2000);\r\n });\r\n\r\n test('should not have memory leaks', async ({ page }) => {\r\n await page.goto('/');\r\n\r\n const metrics = await page.metrics();\r\n expect(metrics.JSHeapUsedSize).toBeLessThan(50 * 1024 * 1024); // 50MB\r\n });\r\n});\r\n`,\r\n };\r\n\r\n return templates[type];\r\n}\r\n\r\n/**\r\n * 生成测试文件到磁盘\r\n * @returns 生成的文件路径\r\n */\r\nexport function generateTestFile(\r\n type: TestType,\r\n context: TemplateContext,\r\n outputDir: string,\r\n): string {\r\n const content = renderTemplate(type, context);\r\n\r\n // 根据类型确定输出子目录\r\n const subDirMap: Record<TestType, string> = {\r\n unit: 'unit',\r\n component: 'component',\r\n e2e: 'e2e',\r\n api: 'api',\r\n visual: 'visual',\r\n performance: 'performance',\r\n };\r\n\r\n const subDir = subDirMap[type];\r\n const dir = path.join(outputDir, subDir);\r\n\r\n // 确保目录存在\r\n if (!fs.existsSync(dir)) {\r\n fs.mkdirSync(dir, { recursive: true });\r\n }\r\n\r\n // 生成文件名\r\n const fileName = `${context.name}.${type === 'e2e' ? 'spec' : 'test'}.ts`;\r\n const filePath = path.join(dir, fileName);\r\n\r\n // 检查文件是否已存在\r\n if (fs.existsSync(filePath)) {\r\n throw new Error(`测试文件已存在: ${filePath}`);\r\n }\r\n\r\n fs.writeFileSync(filePath, content, 'utf-8');\r\n return filePath;\r\n}\r\n\r\n/**\r\n * Convert string to camelCase\r\n */\r\nfunction toCamelCase(str: string): string {\r\n return str\r\n .replace(/[-_\\s]+(.)?/g, (_, c: string) => (c ? c.toUpperCase() : ''))\r\n .replace(/^[A-Z]/, (c) => c.toLowerCase());\r\n}\r\n\r\n/**\r\n * Convert string to PascalCase\r\n */\r\nfunction toPascalCase(str: string): string {\r\n const camel = toCamelCase(str);\r\n return camel.charAt(0).toUpperCase() + camel.slice(1);\r\n}\r\n","/**\r\n * 全局 AI 配置管理 - 存储在 ~/.qat/ai.json\r\n * 与项目无关,所有项目共享同一 AI 配置\r\n */\r\n\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport os from 'node:os';\r\nimport type { AIConfig } from '../types/index.js';\r\n\r\n/** 全局配置目录 */\r\nconst QAT_DIR = path.join(os.homedir(), '.qat');\r\n/** 全局 AI 配置文件路径 */\r\nconst AI_CONFIG_PATH = path.join(QAT_DIR, 'ai.json');\r\n\r\n/** 全局 AI 配置结构 */\r\nexport interface GlobalAIConfig {\r\n provider: string;\r\n apiKey: string;\r\n baseUrl: string;\r\n model: string;\r\n}\r\n\r\n/**\r\n * 加载全局 AI 配置\r\n * @returns 配置对象,未配置时返回 null\r\n */\r\nexport function loadGlobalAIConfig(): GlobalAIConfig | null {\r\n if (!fs.existsSync(AI_CONFIG_PATH)) {\r\n return null;\r\n }\r\n\r\n try {\r\n const content = fs.readFileSync(AI_CONFIG_PATH, 'utf-8');\r\n const config = JSON.parse(content) as GlobalAIConfig;\r\n\r\n // 至少要有 baseUrl 和 model 才算已配置\r\n if (!config.baseUrl || !config.model) {\r\n return null;\r\n }\r\n\r\n return config;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * 保存全局 AI 配置\r\n */\r\nexport function saveGlobalAIConfig(config: GlobalAIConfig): void {\r\n // 确保目录存在\r\n if (!fs.existsSync(QAT_DIR)) {\r\n fs.mkdirSync(QAT_DIR, { recursive: true });\r\n }\r\n\r\n fs.writeFileSync(AI_CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');\r\n}\r\n\r\n/**\r\n * 检查全局 AI 是否已配置\r\n */\r\nexport function isGlobalAIConfigured(): boolean {\r\n return loadGlobalAIConfig() !== null;\r\n}\r\n\r\n/**\r\n * 将全局 AI 配置转换为 QATConfig.ai 格式\r\n */\r\nexport function toAIConfig(globalConfig: GlobalAIConfig): AIConfig {\r\n return {\r\n provider: globalConfig.provider || 'openai',\r\n apiKey: globalConfig.apiKey || undefined,\r\n baseUrl: globalConfig.baseUrl,\r\n model: globalConfig.model,\r\n };\r\n}\r\n\r\n/**\r\n * 脱敏 API Key 用于显示\r\n */\r\nexport function maskApiKey(apiKey: string): string {\r\n if (!apiKey) return '(未设置)';\r\n if (apiKey.length <= 8) return '****';\r\n return apiKey.slice(0, 4) + '****' + apiKey.slice(-4);\r\n}\r\n\r\n/**\r\n * 获取配置文件路径(用于提示用户)\r\n */\r\nexport function getAIConfigPath(): string {\r\n return AI_CONFIG_PATH;\r\n}\r\n","/**\r\n * create 命令 - 交互式创建各类型测试用例\r\n */\r\n\r\nimport { Command } from 'commander';\r\nimport chalk from 'chalk';\r\nimport inquirer from 'inquirer';\r\nimport ora from 'ora';\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport type { CreateOptions, TestType } from '../types/index.js';\r\nimport { loadConfig } from '../services/config.js';\r\nimport { detectProject, discoverVueComponents, discoverUtilityFiles } from '../services/detector.js';\r\nimport { renderTemplate } from '../services/template.js';\r\nimport { getAIProvider, isAIAvailable, createAIProvider } from '../ai/provider.js';\r\nimport { analyzeFile } from '../services/source-analyzer.js';\r\nimport type { PropInfo, EmitInfo } from '../services/source-analyzer.js';\r\nimport { loadGlobalAIConfig, toAIConfig } from '../services/global-config.js';\r\nimport { generateWithReview, printReviewReport, type ReviewReportEntry } from '../services/test-reviewer.js';\r\n\r\nconst TEST_TYPE_LABELS: Record<TestType, string> = {\r\n unit: '单元测试',\r\n component: '组件测试',\r\n e2e: 'E2E端到端测试',\r\n api: 'API接口测试',\r\n visual: '视觉回归测试',\r\n performance: '性能测试',\r\n};\r\n\r\nconst TEST_TYPE_DESCRIPTIONS: Record<TestType, string> = {\r\n unit: '测试工具函数、composables、services 等独立模块',\r\n component: '测试 Vue 组件的渲染、交互、事件等',\r\n e2e: '测试完整的用户操作流程(页面导航、表单提交等)',\r\n api: '测试 API 接口的请求和响应',\r\n visual: '通过截图比对检测 UI 视觉变化',\r\n performance: '测试页面加载性能和 Core Web Vitals',\r\n};\r\n\r\nexport function registerCreateCommand(program: Command): void {\r\n program\r\n .command('create')\r\n .description('创建测试用例 - 交互式生成各类型测试文件')\r\n .option('-t, --type <types...>', '测试类型 (unit|component|e2e|api|visual|performance),支持多选')\r\n .option('-n, --name <name>', '测试用例名称')\r\n .option('--target <paths...>', '被测目标文件/组件路径,支持多选')\r\n .action(async (options: CreateOptions) => {\r\n try {\r\n await executeCreate(options);\r\n } catch (error) {\r\n console.error(chalk.red(`\\n ✗ ${error instanceof Error ? error.message : String(error)}\\n`));\r\n process.exit(1);\r\n }\r\n });\r\n}\r\n\r\nasync function executeCreate(options: CreateOptions): Promise<void> {\r\n // 加载配置\r\n const config = await loadConfig();\r\n\r\n // 用全局 AI 配置覆盖(AI 配置不存储在 qat.config.js 中)\r\n const globalAI = loadGlobalAIConfig();\r\n if (globalAI) {\r\n config.ai = toAIConfig(globalAI);\r\n }\r\n const projectInfo = detectProject();\r\n\r\n let { type, name, target } = options;\r\n\r\n // 1. 选择测试类型(多选)\r\n let types: TestType[];\r\n if (type) {\r\n types = Array.isArray(type) ? type : [type];\r\n } else {\r\n const answers = await inquirer.prompt([\r\n {\r\n type: 'checkbox',\r\n name: 'types',\r\n message: '选择测试类型 (空格选择/取消,回车确认):',\r\n choices: Object.entries(TEST_TYPE_LABELS).map(([value, label]) => ({\r\n name: `${label} - ${chalk.gray(TEST_TYPE_DESCRIPTIONS[value as TestType])}`,\r\n value,\r\n short: label,\r\n checked: value === 'unit' || value === 'component',\r\n })),\r\n validate: (input: string[]) => {\r\n if (input.length === 0) return '请至少选择一种测试类型';\r\n return true;\r\n },\r\n },\r\n ]);\r\n types = answers.types as TestType[];\r\n }\r\n\r\n // 校验测试类型\r\n for (const t of types) {\r\n if (!TEST_TYPE_LABELS[t]) {\r\n throw new Error(`无效的测试类型: ${t},可选值: ${Object.keys(TEST_TYPE_LABELS).join(', ')}`);\r\n }\r\n }\r\n\r\n // 2. 选择被测目标(多选)\r\n let targets: string[];\r\n if (target) {\r\n targets = Array.isArray(target) ? target : [target];\r\n } else {\r\n targets = await selectTargets(types, projectInfo, config.project.srcDir);\r\n }\r\n\r\n // 3. 输入测试名称\r\n if (!name) {\r\n const defaultName = generateDefaultName(types[0], targets[0]);\r\n const answers = await inquirer.prompt([\r\n {\r\n type: 'input',\r\n name: 'name',\r\n message: '输入测试用例名称:',\r\n default: defaultName,\r\n validate: (input: string) => {\r\n if (!input.trim()) return '名称不能为空';\r\n if (!/^[\\w-]+$/.test(input.trim())) return '名称只能包含字母、数字、下划线和连字符';\r\n return true;\r\n },\r\n },\r\n ]);\r\n name = answers.name;\r\n }\r\n\r\n // 4. AI 生成选项\r\n let useAI = false;\r\n if (isAIAvailable(config.ai) && targets.length > 0) {\r\n const { ai } = await inquirer.prompt([\r\n {\r\n type: 'confirm',\r\n name: 'ai',\r\n message: '是否使用 AI 辅助生成测试用例?',\r\n default: false,\r\n },\r\n ]);\r\n useAI = ai;\r\n }\r\n\r\n // 5. 批量生成测试文件\r\n const createdFiles: { type: TestType; filePath: string }[] = [];\r\n let skippedCount = 0;\r\n const reviewReportEntries: ReviewReportEntry[] = [];\r\n\r\n for (const testType of types) {\r\n for (const testTarget of targets) {\r\n const spinner = ora(`正在生成 ${TEST_TYPE_LABELS[testType]} - ${path.basename(testTarget)}...`).start();\r\n\r\n try {\r\n let content: string;\r\n\r\n if (useAI && testTarget) {\r\n const aiResult = await generateWithAI(testType, name!, testTarget, config);\r\n content = aiResult.code;\r\n if (aiResult.reviewEntry) {\r\n reviewReportEntries.push(aiResult.reviewEntry);\r\n }\r\n } else {\r\n // 源码分析 - 根据目标文件生成个性化测试\r\n const analysis = testTarget ? analyzeFile(testTarget) : undefined;\r\n\r\n content = renderTemplate(testType, {\r\n name: name!,\r\n target: testTarget || `./${config.project.srcDir}`,\r\n framework: projectInfo.framework,\r\n vueVersion: projectInfo.vueVersion,\r\n typescript: projectInfo.typescript,\r\n uiLibrary: projectInfo.uiLibrary,\r\n extraImports: projectInfo.componentTestSetup?.extraImports,\r\n globalPlugins: projectInfo.componentTestSetup?.globalPlugins,\r\n globalStubs: projectInfo.componentTestSetup?.globalStubs,\r\n mountOptions: projectInfo.componentTestSetup?.mountOptions,\r\n // 注入源码分析结果\r\n exports: analysis?.exports.map((e) => ({\r\n name: e.name,\r\n kind: e.kind,\r\n params: e.params,\r\n isAsync: e.isAsync,\r\n returnType: e.returnType,\r\n })),\r\n props: analysis?.vueAnalysis?.props.map((p: PropInfo) => ({\r\n name: p.name,\r\n type: p.type,\r\n required: p.required,\r\n defaultValue: p.defaultValue,\r\n })),\r\n emits: analysis?.vueAnalysis?.emits.map((e: EmitInfo) => ({\r\n name: e.name,\r\n params: e.params,\r\n })),\r\n methods: analysis?.vueAnalysis?.methods,\r\n computed: analysis?.vueAnalysis?.computed,\r\n });\r\n }\r\n\r\n // 确定输出目录\r\n const outputDir = getTestOutputDir(testType);\r\n const filePath = path.join(outputDir, `${name}.${testType === 'e2e' ? 'spec' : 'test'}.ts`);\r\n\r\n // 检查文件是否已存在\r\n if (fs.existsSync(filePath)) {\r\n spinner.warn(`测试文件已存在: ${path.relative(process.cwd(), filePath)}`);\r\n skippedCount++;\r\n continue;\r\n }\r\n\r\n // 确保目录存在\r\n if (!fs.existsSync(outputDir)) {\r\n fs.mkdirSync(outputDir, { recursive: true });\r\n }\r\n\r\n // 写入文件\r\n fs.writeFileSync(filePath, content, 'utf-8');\r\n\r\n spinner.succeed(`${TEST_TYPE_LABELS[testType]} - ${path.basename(testTarget)}`);\r\n createdFiles.push({ type: testType, filePath });\r\n } catch (error) {\r\n spinner.fail(`${TEST_TYPE_LABELS[testType]} - ${path.basename(testTarget)} 创建失败`);\r\n console.log(chalk.gray(` ${error instanceof Error ? error.message : String(error)}`));\r\n }\r\n }\r\n }\r\n\r\n // 6. 显示结果\r\n displayCreateResult(createdFiles, skippedCount, useAI);\r\n\r\n // 7. 打印审计报告\r\n if (reviewReportEntries.length > 0) {\r\n printReviewReport(reviewReportEntries);\r\n }\r\n}\r\n\r\n/**\r\n * 交互式多选被测目标\r\n */\r\nasync function selectTargets(\r\n types: TestType[],\r\n projectInfo: ReturnType<typeof detectProject>,\r\n srcDir: string,\r\n): Promise<string[]> {\r\n // 收集所有可选目标\r\n const allTargets: { name: string; value: string }[] = [];\r\n\r\n // 工具文件\r\n if (types.includes('unit')) {\r\n const utils = discoverUtilityFiles(process.cwd(), srcDir);\r\n for (const f of utils.slice(0, 30)) {\r\n allTargets.push({ name: `${chalk.gray('[unit]')} ${f}`, value: f });\r\n }\r\n }\r\n\r\n // Vue 组件\r\n if (types.includes('component')) {\r\n const components = discoverVueComponents(process.cwd(), srcDir);\r\n for (const f of components.slice(0, 30)) {\r\n allTargets.push({ name: `${chalk.gray('[comp]')} ${f}`, value: f });\r\n }\r\n }\r\n\r\n // 没有发现文件时手动输入\r\n if (allTargets.length === 0) {\r\n const { manualTarget } = await inquirer.prompt([\r\n {\r\n type: 'input',\r\n name: 'manualTarget',\r\n message: '输入被测目标路径:',\r\n default: `./${srcDir}`,\r\n },\r\n ]);\r\n return [manualTarget];\r\n }\r\n\r\n // 多选\r\n const { selectedTargets } = await inquirer.prompt([\r\n {\r\n type: 'checkbox',\r\n name: 'selectedTargets',\r\n message: '选择被测目标 (空格选择/取消,回车确认):',\r\n choices: [\r\n ...allTargets,\r\n { name: chalk.gray('手动输入路径'), value: '__manual__' },\r\n ],\r\n validate: (input: string[]) => {\r\n if (input.length === 0) return '请至少选择一个目标';\r\n return true;\r\n },\r\n },\r\n ]);\r\n\r\n // 处理手动输入\r\n if (selectedTargets.includes('__manual__')) {\r\n const { manualTarget } = await inquirer.prompt([\r\n {\r\n type: 'input',\r\n name: 'manualTarget',\r\n message: '输入目标路径:',\r\n },\r\n ]);\r\n return [\r\n ...selectedTargets.filter((t: string) => t !== '__manual__'),\r\n manualTarget,\r\n ].filter(Boolean);\r\n }\r\n\r\n return selectedTargets;\r\n}\r\n\r\n/**\r\n * 生成默认测试名称\r\n */\r\nfunction generateDefaultName(type: TestType, target?: string): string {\r\n if (!target) return `${type}-test`;\r\n\r\n const basename = path.basename(target, path.extname(target));\r\n const cleaned = basename\r\n .replace(/[^a-zA-Z0-9]/g, '-')\r\n .replace(/-+/g, '-')\r\n .replace(/^-|-$/g, '');\r\n\r\n return cleaned || `${type}-test`;\r\n}\r\n\r\n/**\r\n * AI 辅助生成测试用例(结合源码分析 + 审计员审核)\r\n */\r\nasync function generateWithAI(\r\n type: TestType,\r\n name: string,\r\n target: string,\r\n config: import('../types/index.js').QATConfig,\r\n): Promise<{ code: string; reviewEntry?: ReviewReportEntry }> {\r\n const provider = getAIProvider(config.ai);\r\n\r\n if (!provider.capabilities.generateTest) {\r\n throw new Error('当前 AI Provider 不支持测试生成');\r\n }\r\n\r\n // 读取源码\r\n let sourceCode = '';\r\n const fullPath = path.resolve(process.cwd(), target);\r\n if (fs.existsSync(fullPath)) {\r\n sourceCode = fs.readFileSync(fullPath, 'utf-8');\r\n }\r\n\r\n // 源码分析\r\n const analysis = analyzeFile(target);\r\n\r\n const analysisSummary = analysis.exports.length > 0 || analysis.vueAnalysis ? {\r\n exports: analysis.exports.map((e) => ({\r\n name: e.name,\r\n kind: e.kind,\r\n params: e.params,\r\n isAsync: e.isAsync,\r\n })),\r\n props: analysis.vueAnalysis?.props.map((p: PropInfo) => ({\r\n name: p.name,\r\n type: p.type,\r\n required: p.required,\r\n })),\r\n emits: analysis.vueAnalysis?.emits.map((e: EmitInfo) => ({\r\n name: e.name,\r\n params: e.params,\r\n })),\r\n methods: analysis.vueAnalysis?.methods,\r\n computed: analysis.vueAnalysis?.computed,\r\n } : undefined;\r\n\r\n // 使用审计流程生成\r\n const reviewResult = await generateWithReview({\r\n testType: type,\r\n targetPath: target,\r\n sourceCode,\r\n analysis: analysisSummary,\r\n aiConfig: config.ai,\r\n framework: 'vue',\r\n });\r\n\r\n // 构建文件头注释\r\n const headerComment = [\r\n `// AI Generated Test - ${name}`,\r\n `// 审计: ${reviewResult.approved ? '通过' : '未通过'} (${(reviewResult.reviewScore * 100).toFixed(0)}%) — ${reviewResult.attempts}次尝试`,\r\n reviewResult.reviewFeedback ? `// 审计意见: ${reviewResult.reviewFeedback}` : '',\r\n '',\r\n ].filter(Boolean).join('\\n');\r\n\r\n const code = `${headerComment}\\n${reviewResult.code}`;\r\n\r\n const reviewEntry: ReviewReportEntry = {\r\n target,\r\n testType: type,\r\n approved: reviewResult.approved,\r\n attempts: reviewResult.attempts,\r\n score: reviewResult.reviewScore,\r\n feedback: reviewResult.reviewFeedback,\r\n issues: reviewResult.approved ? [] : reviewResult.reviewIssues,\r\n };\r\n\r\n return { code, reviewEntry };\r\n}\r\n\r\n/**\r\n * 获取测试输出目录\r\n */\r\nfunction getTestOutputDir(type: TestType): string {\r\n const subDirMap: Record<TestType, string> = {\r\n unit: 'tests/unit',\r\n component: 'tests/component',\r\n e2e: 'tests/e2e',\r\n api: 'tests/api',\r\n visual: 'tests/visual',\r\n performance: 'tests/e2e',\r\n };\r\n\r\n return subDirMap[type];\r\n}\r\n\r\n/**\r\n * 显示创建结果\r\n */\r\nfunction displayCreateResult(\r\n createdFiles: { type: TestType; filePath: string }[],\r\n skippedCount: number,\r\n usedAI: boolean,\r\n): void {\r\n if (createdFiles.length === 0 && skippedCount === 0) {\r\n console.log(chalk.yellow('\\n 没有创建任何测试文件\\n'));\r\n return;\r\n }\r\n\r\n console.log(chalk.green(`\\n ✓ 已创建 ${createdFiles.length} 个测试文件`));\r\n if (skippedCount > 0) {\r\n console.log(chalk.yellow(` ⚠ 跳过 ${skippedCount} 个已存在的文件`));\r\n }\r\n console.log();\r\n\r\n for (const { type, filePath } of createdFiles) {\r\n const relativePath = path.relative(process.cwd(), filePath);\r\n console.log(chalk.white(` ${chalk.cyan(TEST_TYPE_LABELS[type])} ${chalk.gray(relativePath)}`));\r\n }\r\n\r\n if (usedAI) {\r\n console.log();\r\n console.log(chalk.magenta(' 生成方式: AI 辅助'));\r\n }\r\n\r\n console.log();\r\n console.log(chalk.gray(' 运行测试:'));\r\n\r\n // 按类型去重\r\n const uniqueTypes = [...new Set(createdFiles.map((f) => f.type))];\r\n if (uniqueTypes.length === 1) {\r\n console.log(chalk.cyan(` qat run -t ${uniqueTypes[0]}`));\r\n } else {\r\n console.log(chalk.cyan(` qat run -t ${uniqueTypes.join(' -t ')}`));\r\n console.log(chalk.gray(' # 或运行全部'));\r\n console.log(chalk.cyan(' qat run'));\r\n }\r\n console.log();\r\n}\r\n","/**\r\n * run 命令 - 按类型/文件/标签运行测试,聚合结果\r\n */\r\n\r\nimport { Command } from 'commander';\r\nimport chalk from 'chalk';\r\nimport inquirer from 'inquirer';\r\nimport ora from 'ora';\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport type { RunOptions, TestType, TestRunResult, TestError } from '../types/index.js';\r\nimport { loadConfig } from '../services/config.js';\r\nimport { runVitest } from '../runners/vitest-runner.js';\r\nimport { runPlaywright } from '../runners/playwright-runner.js';\r\nimport { runLighthouse } from '../runners/lighthouse-runner.js';\r\nimport { getAIProvider, isAIAvailable } from '../ai/provider.js';\r\nimport { loadGlobalAIConfig, toAIConfig } from '../services/global-config.js';\r\n\r\n/** 测试结果存储目录 */\r\nconst RESULTS_DIR = '.qat-results';\r\n\r\n/** 需要 dev server 运行的测试类型 */\r\nconst SERVER_REQUIRED_TYPES: TestType[] = ['e2e', 'visual', 'performance'];\r\n\r\nconst TYPE_RUNNERS: Record<TestType, string> = {\r\n unit: 'Vitest',\r\n component: 'Vitest',\r\n e2e: 'Playwright',\r\n api: 'Vitest',\r\n visual: 'Playwright',\r\n performance: 'Lighthouse',\r\n};\r\n\r\nconst TYPE_LABELS: Record<TestType, string> = {\r\n unit: '单元测试',\r\n component: '组件测试',\r\n e2e: 'E2E测试',\r\n api: 'API测试',\r\n visual: '视觉回归测试',\r\n performance: '性能测试',\r\n};\r\n\r\nexport function registerRunCommand(program: Command): void {\r\n program\r\n .command('run')\r\n .description('运行测试 - 支持按类型/文件/标签运行')\r\n .option('-t, --type <types...>', '测试类型 (unit|component|e2e|api|visual|performance),支持多选')\r\n .option('-f, --file <file>', '指定测试文件路径')\r\n .option('--tag <tag>', '按标签筛选测试用例')\r\n .option('-w, --watch', '监听模式,文件变更自动重跑')\r\n .option('-p, --parallel', '并行执行测试')\r\n .option('-b, --browser <browser>', '指定浏览器 (chromium|firefox|webkit)')\r\n .action(async (options: RunOptions) => {\r\n try {\r\n await executeRun(options);\r\n } catch (error) {\r\n console.error(chalk.red(`\\n ✗ ${error instanceof Error ? error.message : String(error)}\\n`));\r\n process.exit(1);\r\n }\r\n });\r\n}\r\n\r\nasync function executeRun(options: RunOptions): Promise<void> {\r\n const { type, file, tag, watch, parallel, browser } = options;\r\n\r\n // 加载配置\r\n const config = await loadConfig(options.config);\r\n\r\n // 用全局 AI 配置覆盖(AI 配置不存储在 qat.config.js 中)\r\n const globalAI = loadGlobalAIConfig();\r\n if (globalAI) {\r\n config.ai = toAIConfig(globalAI);\r\n }\r\n\r\n // Watch 模式 - 直接委托给 vitest 的 watch\r\n if (watch) {\r\n await runWatchMode(config, options);\r\n return;\r\n }\r\n\r\n // 确定要运行的测试类型\r\n let typesToRun = await determineTypesToRun(type, file, config);\r\n\r\n if (typesToRun.length === 0) {\r\n console.log(chalk.yellow('\\n 没有可运行的测试类型(请在 qat.config.ts 中启用)\\n'));\r\n return;\r\n }\r\n\r\n // 检测需要 dev server 的测试类型\r\n const serverNeededTypes = typesToRun.filter((t) => SERVER_REQUIRED_TYPES.includes(t));\r\n if (serverNeededTypes.length > 0) {\r\n const serverOk = await checkDevServer(config);\r\n if (!serverOk) {\r\n const { action } = await inquirer.prompt([\r\n {\r\n type: 'list',\r\n name: 'action',\r\n message: `运行 ${serverNeededTypes.map((t) => TYPE_LABELS[t]).join('、')} 需要项目服务运行,如何处理?`,\r\n choices: [\r\n { name: '自动启动 dev server 并运行', value: 'start' },\r\n { name: '跳过这些测试类型,仅运行其他测试', value: 'skip' },\r\n { name: '取消运行', value: 'cancel' },\r\n ],\r\n default: 'start',\r\n },\r\n ]);\r\n\r\n if (action === 'cancel') {\r\n console.log(chalk.gray('\\n 已取消运行\\n'));\r\n return;\r\n }\r\n\r\n if (action === 'skip') {\r\n typesToRun = typesToRun.filter((t) => !SERVER_REQUIRED_TYPES.includes(t));\r\n if (typesToRun.length === 0) {\r\n console.log(chalk.yellow('\\n 没有可运行的测试类型\\n'));\r\n return;\r\n }\r\n }\r\n\r\n if (action === 'start') {\r\n const started = await startDevServer(config);\r\n if (!started) {\r\n const { fallback } = await inquirer.prompt([\r\n {\r\n type: 'list',\r\n name: 'fallback',\r\n message: 'dev server 启动失败,如何处理?',\r\n choices: [\r\n { name: '跳过需要服务的测试,仅运行其他测试', value: 'skip' },\r\n { name: '取消运行', value: 'cancel' },\r\n ],\r\n default: 'skip',\r\n },\r\n ]);\r\n\r\n if (fallback === 'cancel') {\r\n console.log(chalk.gray('\\n 已取消运行\\n'));\r\n return;\r\n }\r\n\r\n typesToRun = typesToRun.filter((t) => !SERVER_REQUIRED_TYPES.includes(t));\r\n if (typesToRun.length === 0) {\r\n console.log(chalk.yellow('\\n 没有可运行的测试类型\\n'));\r\n return;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n // 交互选择运行方式(仅在没有通过 CLI 指定具体类型时)\r\n if (!type && !file) {\r\n const { runMode } = await inquirer.prompt([\r\n {\r\n type: 'list',\r\n name: 'runMode',\r\n message: '选择运行方式:',\r\n choices: [\r\n { name: '立即执行', value: 'now' },\r\n { name: '仅查看命令 (不执行)', value: 'dry' },\r\n ],\r\n default: 'now',\r\n },\r\n ]);\r\n\r\n if (runMode === 'dry') {\r\n printDryRunCommands(typesToRun, options, config);\r\n return;\r\n }\r\n }\r\n\r\n // 执行测试\r\n const results: TestRunResult[] = [];\r\n\r\n if (parallel && typesToRun.length > 1) {\r\n const spinner = ora('正在并行运行所有测试...').start();\r\n const runPromises = typesToRun.map((t) => runTestType(t, config, options));\r\n const runResults = await Promise.allSettled(runPromises);\r\n\r\n for (const result of runResults) {\r\n if (result.status === 'fulfilled') {\r\n results.push(result.value);\r\n } else {\r\n results.push(createErrorResult('unknown' as TestType, result.reason));\r\n }\r\n }\r\n spinner.stop();\r\n } else {\r\n for (const testType of typesToRun) {\r\n const label = TYPE_LABELS[testType] || testType;\r\n const spinner = ora(`正在运行 ${label}...`).start();\r\n\r\n try {\r\n const result = await runTestType(testType, config, options);\r\n results.push(result);\r\n spinner[suiteStatusToSpinner(result.status)](`${label} ${result.status === 'passed' ? '通过' : '失败'}`);\r\n } catch (error) {\r\n results.push(createErrorResult(testType, error));\r\n spinner.fail(`${label} 执行错误`);\r\n }\r\n }\r\n }\r\n\r\n // 输出汇总\r\n displaySummary(results, config);\r\n\r\n // 保存测试结果供 qat report 使用\r\n saveRunResults(results);\r\n}\r\n\r\n/**\r\n * 打印 dry-run 命令\r\n */\r\nfunction printDryRunCommands(\r\n types: TestType[],\r\n options: RunOptions,\r\n config: import('../types/index.js').QATConfig,\r\n): void {\r\n console.log();\r\n console.log(chalk.cyan(' 可执行的测试命令:\\n'));\r\n\r\n for (const testType of types) {\r\n const label = TYPE_LABELS[testType];\r\n const runner = TYPE_RUNNERS[testType];\r\n\r\n let cmd: string;\r\n switch (runner) {\r\n case 'Vitest':\r\n cmd = 'npx vitest run';\r\n {\r\n const dirMap: Record<string, string> = {\r\n unit: 'tests/unit',\r\n component: 'tests/component',\r\n api: 'tests/api',\r\n };\r\n const dir = dirMap[testType];\r\n if (dir) cmd += ` --include '${dir}/**/*.test.ts'`;\r\n if (config.vitest.coverage) cmd += ' --coverage';\r\n }\r\n break;\r\n case 'Playwright':\r\n cmd = 'npx playwright test';\r\n {\r\n const dirMap: Record<string, string> = {\r\n e2e: 'tests/e2e',\r\n visual: 'tests/visual',\r\n };\r\n const dir = dirMap[testType];\r\n if (dir) cmd += ` ${dir}`;\r\n if (options.browser) cmd += ` --project ${options.browser}`;\r\n }\r\n break;\r\n case 'Lighthouse':\r\n cmd = `npx lighthouse ${config.lighthouse.urls[0] || 'http://localhost:5173'} --output=json --quiet --chrome-flags=\"--headless --no-sandbox\"`;\r\n break;\r\n default:\r\n cmd = `# ${label}: 未知运行器`;\r\n }\r\n\r\n console.log(chalk.white(` ${label}:`));\r\n console.log(chalk.cyan(` ${cmd}`));\r\n console.log();\r\n }\r\n\r\n console.log(chalk.gray(' 或使用 QAT 统一运行:'));\r\n if (types.length === 1) {\r\n console.log(chalk.cyan(` qat run -t ${types[0]}`));\r\n } else {\r\n console.log(chalk.cyan(` qat run -t ${types.join(' -t ')}`));\r\n console.log(chalk.gray(' # 并行运行'));\r\n console.log(chalk.cyan(' qat run -p'));\r\n }\r\n console.log();\r\n}\r\n\r\n/**\r\n * 确定要运行的测试类型列表\r\n */\r\nasync function determineTypesToRun(\r\n type: string | string[] | undefined,\r\n file: string | undefined,\r\n config: import('../types/index.js').QATConfig,\r\n): Promise<TestType[]> {\r\n // CLI 传入了类型\r\n if (type) {\r\n const types = Array.isArray(type) ? type : [type];\r\n return types as TestType[];\r\n }\r\n\r\n if (file) {\r\n if (file.includes('/e2e/') || file.includes('\\\\e2e\\\\')) return ['e2e'];\r\n if (file.includes('/visual/') || file.includes('\\\\visual\\\\')) return ['visual'];\r\n if (file.includes('/api/') || file.includes('\\\\api\\\\')) return ['api'];\r\n if (file.includes('/component/') || file.includes('\\\\component\\\\')) return ['component'];\r\n return ['unit'];\r\n }\r\n\r\n // 交互多选\r\n const enabledTypes = getEnabledTypes(config);\r\n\r\n const { selectedTypes } = await inquirer.prompt([\r\n {\r\n type: 'checkbox',\r\n name: 'selectedTypes',\r\n message: '选择要运行的测试类型 (空格选择/取消,回车确认):',\r\n choices: enabledTypes.map((t) => ({\r\n name: `${TYPE_LABELS[t]} (${chalk.gray(TYPE_RUNNERS[t])})`,\r\n value: t,\r\n checked: true,\r\n })),\r\n validate: (input: string[]) => {\r\n if (input.length === 0) return '请至少选择一种测试类型';\r\n return true;\r\n },\r\n },\r\n ]);\r\n\r\n return selectedTypes as TestType[];\r\n}\r\n\r\n/**\r\n * 获取配置中启用的测试类型\r\n */\r\nfunction getEnabledTypes(config: import('../types/index.js').QATConfig): TestType[] {\r\n const types: TestType[] = [];\r\n if (config.vitest.enabled) {\r\n types.push('unit', 'component', 'api');\r\n }\r\n if (config.playwright.enabled) {\r\n types.push('e2e');\r\n }\r\n if (config.visual.enabled) {\r\n types.push('visual');\r\n }\r\n if (config.lighthouse.enabled) {\r\n types.push('performance');\r\n }\r\n return types.length > 0 ? types : ['unit', 'component', 'e2e', 'api', 'visual', 'performance'];\r\n}\r\n\r\n/**\r\n * 运行指定类型的测试\r\n */\r\nasync function runTestType(\r\n testType: TestType,\r\n config: import('../types/index.js').QATConfig,\r\n options: RunOptions,\r\n): Promise<TestRunResult> {\r\n const runner = TYPE_RUNNERS[testType];\r\n\r\n switch (runner) {\r\n case 'Vitest': {\r\n return runVitest({\r\n type: testType,\r\n files: options.file ? [options.file] : undefined,\r\n coverage: config.vitest.coverage,\r\n configPath: undefined,\r\n });\r\n }\r\n case 'Playwright': {\r\n return runPlaywright({\r\n type: testType,\r\n files: options.file ? [options.file] : undefined,\r\n browsers: options.browser ? [options.browser as 'chromium' | 'firefox' | 'webkit'] : config.playwright.browsers,\r\n headed: false,\r\n configPath: undefined,\r\n });\r\n }\r\n case 'Lighthouse': {\r\n return runLighthouse({\r\n urls: config.lighthouse.urls,\r\n runs: config.lighthouse.runs,\r\n thresholds: config.lighthouse.thresholds,\r\n });\r\n }\r\n default:\r\n throw new Error(`未知的运行器: ${runner}`);\r\n }\r\n}\r\n\r\n/**\r\n * Watch 模式\r\n */\r\nasync function runWatchMode(\r\n config: import('../types/index.js').QATConfig,\r\n options: RunOptions,\r\n): Promise<void> {\r\n console.log(chalk.cyan(' 监听模式已启动 (Ctrl+C 退出)\\n'));\r\n\r\n const { spawn } = await import('node:child_process');\r\n\r\n const args = ['vitest', '--watch'];\r\n if (options.file) {\r\n args.push(options.file);\r\n }\r\n if (options.type) {\r\n const types = Array.isArray(options.type) ? options.type : [options.type];\r\n const dirMap: Record<string, string> = {\r\n unit: 'tests/unit',\r\n component: 'tests/component',\r\n api: 'tests/api',\r\n };\r\n const dirs = types.map((t) => dirMap[t]).filter(Boolean);\r\n if (dirs.length > 0) {\r\n args.push('--include', dirs.map((d) => `${d}/**/*.test.ts`).join(','));\r\n }\r\n }\r\n\r\n const child = spawn('npx', args, {\r\n cwd: process.cwd(),\r\n stdio: 'inherit',\r\n shell: true,\r\n });\r\n\r\n child.on('error', (err) => {\r\n console.error(chalk.red(`\\n Vitest 启动失败: ${err.message}\\n`));\r\n });\r\n\r\n child.on('exit', (code) => {\r\n if (code !== null && code !== 0) {\r\n process.exit(code);\r\n }\r\n });\r\n\r\n process.on('SIGINT', () => {\r\n child.kill('SIGINT');\r\n process.exit(0);\r\n });\r\n}\r\n\r\n/**\r\n * 创建错误结果\r\n */\r\nfunction createErrorResult(type: TestType, error: unknown): TestRunResult {\r\n return {\r\n type,\r\n status: 'failed',\r\n startTime: Date.now(),\r\n endTime: Date.now(),\r\n duration: 0,\r\n suites: [{\r\n name: 'Runner Error',\r\n file: 'unknown',\r\n type,\r\n status: 'failed',\r\n duration: 0,\r\n tests: [{\r\n name: `${type} runner error`,\r\n file: 'unknown',\r\n status: 'failed',\r\n duration: 0,\r\n error: {\r\n message: error instanceof Error ? error.message : String(error),\r\n },\r\n retries: 0,\r\n }],\r\n }],\r\n };\r\n}\r\n\r\n/**\r\n * 映射测试状态到 spinner 方法\r\n */\r\nfunction suiteStatusToSpinner(status: string): 'succeed' | 'fail' | 'warn' {\r\n switch (status) {\r\n case 'passed': return 'succeed';\r\n case 'failed': return 'fail';\r\n default: return 'warn';\r\n }\r\n}\r\n\r\n/**\r\n * 显示测试汇总\r\n */\r\nasync function displaySummary(results: TestRunResult[], config: import('../types/index.js').QATConfig): Promise<void> {\r\n let totalSuites = 0;\r\n let totalPassed = 0;\r\n let totalFailed = 0;\r\n let totalSkipped = 0;\r\n let totalDuration = 0;\r\n\r\n for (const result of results) {\r\n totalDuration += result.duration;\r\n for (const suite of result.suites) {\r\n totalSuites++;\r\n for (const test of suite.tests) {\r\n if (test.status === 'passed') totalPassed++;\r\n else if (test.status === 'failed') totalFailed++;\r\n else if (test.status === 'skipped') totalSkipped++;\r\n }\r\n }\r\n }\r\n\r\n const total = totalPassed + totalFailed + totalSkipped;\r\n\r\n console.log();\r\n console.log(chalk.cyan(' ─────────────────────────────'));\r\n\r\n if (totalFailed > 0) {\r\n console.log(chalk.red(' ✗ 测试失败'));\r\n } else if (total === 0) {\r\n console.log(chalk.yellow(' ⚠ 没有发现测试用例'));\r\n } else {\r\n console.log(chalk.green(' ✓ 全部通过'));\r\n }\r\n\r\n console.log();\r\n console.log(` ${chalk.white('测试用例:')} ${total} total`);\r\n if (totalPassed > 0) console.log(` ${chalk.green(' 通过:')} ${totalPassed}`);\r\n if (totalFailed > 0) console.log(` ${chalk.red(' 失败:')} ${totalFailed}`);\r\n if (totalSkipped > 0) console.log(` ${chalk.yellow(' 跳过:')} ${totalSkipped}`);\r\n console.log(` ${chalk.gray(' 套件:')} ${totalSuites}`);\r\n console.log(` ${chalk.gray(' 耗时:')} ${formatDuration(totalDuration)}`);\r\n\r\n // 按类型展示\r\n if (results.length > 1) {\r\n console.log();\r\n for (const result of results) {\r\n const label = TYPE_LABELS[result.type] || result.type;\r\n const icon = result.status === 'passed' ? chalk.green('✓') : result.status === 'failed' ? chalk.red('✗') : chalk.yellow('⚠');\r\n const testCount = result.suites.reduce((sum, s) => sum + s.tests.length, 0);\r\n console.log(` ${icon} ${label} (${testCount} tests, ${formatDuration(result.duration)})`);\r\n }\r\n }\r\n\r\n // 性能测试额外信息\r\n for (const result of results) {\r\n if (result.type === 'performance' && result.performance) {\r\n const p = result.performance;\r\n console.log();\r\n console.log(chalk.cyan(' 性能指标:'));\r\n console.log(` Performance: ${scoreColor(p.performance)} ${p.performance}/100`);\r\n console.log(` Accessibility: ${scoreColor(p.accessibility)} ${p.accessibility}/100`);\r\n console.log(` Best Practices: ${scoreColor(p.bestPractices)} ${p.bestPractices}/100`);\r\n console.log(` SEO: ${scoreColor(p.seo)} ${p.seo}/100`);\r\n if (p.lcp !== undefined) console.log(` LCP: ${p.lcp.toFixed(0)}ms`);\r\n if (p.fid !== undefined) console.log(` FID: ${p.fid.toFixed(0)}ms`);\r\n if (p.cls !== undefined) console.log(` CLS: ${p.cls.toFixed(3)}`);\r\n }\r\n }\r\n\r\n // 失败测试详情\r\n const failedTests = results.flatMap((r) =>\r\n r.suites.flatMap((s) =>\r\n s.tests.filter((t) => t.status === 'failed').map((t) => ({ suite: s, test: t }))\r\n )\r\n );\r\n\r\n if (failedTests.length > 0) {\r\n console.log();\r\n console.log(chalk.red(' 失败详情:'));\r\n for (const { suite, test } of failedTests) {\r\n console.log(chalk.red(` ✗ ${suite.name} > ${test.name}`));\r\n if (test.error?.message) {\r\n console.log(chalk.gray(` ${test.error.message.split('\\n')[0]}`));\r\n }\r\n }\r\n }\r\n\r\n console.log(chalk.cyan(' ─────────────────────────────'));\r\n console.log();\r\n\r\n if (totalFailed > 0) {\r\n process.exitCode = 1;\r\n }\r\n\r\n // AI 分析失败测试\r\n if (totalFailed > 0 && isAIAvailable(config.ai)) {\r\n await aiAnalyzeFailures(results, config.ai);\r\n }\r\n}\r\n\r\n/**\r\n * AI 分析失败测试\r\n */\r\nasync function aiAnalyzeFailures(\r\n results: TestRunResult[],\r\n aiConfig: import('../types/index.js').AIConfig,\r\n): Promise<void> {\r\n const failedTests = results.flatMap((r) =>\r\n r.suites.flatMap((s) =>\r\n s.tests\r\n .filter((t) => t.status === 'failed' && t.error)\r\n .map((t) => ({ suite: s, test: t })),\r\n ),\r\n );\r\n\r\n if (failedTests.length === 0) return;\r\n\r\n console.log(chalk.magenta(' 🤖 AI 分析失败原因...'));\r\n console.log();\r\n\r\n const provider = getAIProvider(aiConfig);\r\n\r\n if (!provider.capabilities.suggestFix) return;\r\n\r\n for (const { suite, test } of failedTests.slice(0, 5)) {\r\n try {\r\n const suggestions = await provider.suggestFix(test.error!);\r\n\r\n console.log(chalk.white(` ${suite.name} > ${test.name}`));\r\n\r\n if (test.error?.message) {\r\n console.log(chalk.gray(` 错误: ${test.error.message.split('\\n')[0]}`));\r\n }\r\n\r\n for (const suggestion of suggestions.slice(0, 3)) {\r\n console.log(chalk.cyan(` → ${suggestion}`));\r\n }\r\n console.log();\r\n } catch (error) {\r\n console.log(chalk.gray(` AI 分析失败: ${error instanceof Error ? error.message : String(error)}`));\r\n }\r\n }\r\n}\r\n\r\nfunction formatDuration(ms: number): string {\r\n if (ms < 1000) return `${ms}ms`;\r\n if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;\r\n const min = Math.floor(ms / 60000);\r\n const sec = Math.floor((ms % 60000) / 1000);\r\n return `${min}m ${sec}s`;\r\n}\r\n\r\nfunction scoreColor(score: number): string {\r\n if (score >= 90) return chalk.green('●');\r\n if (score >= 50) return chalk.yellow('●');\r\n return chalk.red('●');\r\n}\r\n\r\n// ─── Dev Server 检测与启动 ────────────────────────────────────\r\n\r\n/**\r\n * 检测 dev server 是否正在运行\r\n */\r\nasync function checkDevServer(config: import('../types/index.js').QATConfig): Promise<boolean> {\r\n const baseUrl = config.playwright.baseURL || 'http://localhost:5173';\r\n\r\n try {\r\n const controller = new AbortController();\r\n const timer = setTimeout(() => controller.abort(), 3000);\r\n const response = await fetch(baseUrl, { signal: controller.signal, method: 'HEAD' });\r\n clearTimeout(timer);\r\n return response.status > 0;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * 自动启动 dev server\r\n * @returns 是否启动成功\r\n */\r\nasync function startDevServer(config: import('../types/index.js').QATConfig): Promise<boolean> {\r\n const { spawn } = await import('node:child_process');\r\n\r\n // 检测包管理器\r\n const hasPnpm = fs.existsSync(path.join(process.cwd(), 'pnpm-lock.yaml'));\r\n const hasYarn = fs.existsSync(path.join(process.cwd(), 'yarn.lock'));\r\n const pkgCmd = hasPnpm ? 'pnpm' : hasYarn ? 'yarn' : 'npm';\r\n\r\n console.log(chalk.cyan(` 正在启动 dev server (${pkgCmd} run dev) ...`));\r\n\r\n const child = spawn(pkgCmd, ['run', 'dev'], {\r\n cwd: process.cwd(),\r\n stdio: 'pipe',\r\n shell: true,\r\n detached: true,\r\n });\r\n\r\n // 等待服务就绪(最多等 30 秒)\r\n const baseUrl = config.playwright.baseURL || 'http://localhost:5173';\r\n const maxWait = 30000;\r\n const interval = 1000;\r\n let waited = 0;\r\n\r\n while (waited < maxWait) {\r\n await new Promise((r) => setTimeout(r, interval));\r\n waited += interval;\r\n\r\n const isUp = await checkDevServer(config);\r\n if (isUp) {\r\n console.log(chalk.green(` ✓ dev server 已启动 (${baseUrl})`));\r\n // 将子进程脱离父进程,让它在后台继续运行\r\n child.unref();\r\n return true;\r\n }\r\n }\r\n\r\n // 超时\r\n child.kill();\r\n console.log(chalk.red(' ✗ dev server 启动超时'));\r\n return false;\r\n}\r\n\r\n// ─── 保存测试结果 ──────────────────────────────────────────────\r\n\r\n/**\r\n * 保存测试结果到 .qat-results/ 供 qat report 使用\r\n */\r\nfunction saveRunResults(results: TestRunResult[]): void {\r\n if (results.length === 0) return;\r\n\r\n const resultsPath = path.join(process.cwd(), RESULTS_DIR);\r\n\r\n if (!fs.existsSync(resultsPath)) {\r\n fs.mkdirSync(resultsPath, { recursive: true });\r\n }\r\n\r\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-');\r\n const fileName = `result-${timestamp}.json`;\r\n const filePath = path.join(resultsPath, fileName);\r\n\r\n const data = {\r\n timestamp: new Date().toISOString(),\r\n results,\r\n };\r\n\r\n fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');\r\n\r\n // 清理旧结果(保留最近 20 次)\r\n try {\r\n const files = fs.readdirSync(resultsPath)\r\n .filter((f) => f.startsWith('result-') && f.endsWith('.json'))\r\n .sort();\r\n\r\n while (files.length > 20) {\r\n const oldest = files.shift()!;\r\n fs.unlinkSync(path.join(resultsPath, oldest));\r\n }\r\n } catch {\r\n // 清理失败不影响主流程\r\n }\r\n}\r\n","/**\r\n * Vitest 运行器 - 通过子进程调用Vitest,解析输出收集结果\r\n */\r\n\r\nimport { execFile } from 'node:child_process';\r\nimport path from 'node:path';\r\nimport type { TestRunResult, TestSuiteResult, TestCaseResult, TestStatus, TestType, CoverageResult } from '../types/index.js';\r\n\r\nexport interface VitestRunnerOptions {\r\n type: TestType;\r\n files?: string[];\r\n watch?: boolean;\r\n coverage?: boolean;\r\n configPath?: string;\r\n /** 额外传递给 vitest 的参数 */\r\n extraArgs?: string[];\r\n}\r\n\r\n/**\r\n * 执行Vitest测试\r\n */\r\nexport async function runVitest(options: VitestRunnerOptions): Promise<TestRunResult> {\r\n const startTime = Date.now();\r\n\r\n const args = buildVitestArgs(options);\r\n\r\n try {\r\n const result = await execVitest(args);\r\n const endTime = Date.now();\r\n\r\n return {\r\n type: options.type,\r\n status: result.success ? 'passed' : 'failed',\r\n startTime,\r\n endTime,\r\n duration: endTime - startTime,\r\n suites: result.suites,\r\n coverage: result.coverage,\r\n };\r\n } catch (error) {\r\n const endTime = Date.now();\r\n return {\r\n type: options.type,\r\n status: 'failed',\r\n startTime,\r\n endTime,\r\n duration: endTime - startTime,\r\n suites: [{\r\n name: 'Vitest Runner',\r\n file: options.files?.[0] || 'unknown',\r\n type: options.type,\r\n status: 'failed',\r\n duration: endTime - startTime,\r\n tests: [{\r\n name: 'Runner Error',\r\n file: options.files?.[0] || 'unknown',\r\n status: 'failed',\r\n duration: 0,\r\n error: {\r\n message: error instanceof Error ? error.message : String(error),\r\n },\r\n retries: 0,\r\n }],\r\n }],\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * 构建vitest命令参数\r\n */\r\nfunction buildVitestArgs(options: VitestRunnerOptions): string[] {\r\n const args = ['vitest', 'run', '--reporter=json'];\r\n\r\n // 指定测试文件\r\n if (options.files && options.files.length > 0) {\r\n args.push(...options.files);\r\n } else {\r\n // 根据测试类型确定包含路径\r\n const includeMap: Record<string, string[]> = {\r\n unit: ['tests/unit'],\r\n component: ['tests/component'],\r\n api: ['tests/api'],\r\n };\r\n const includes = includeMap[options.type];\r\n if (includes) {\r\n args.push('--include', includes.map((d) => `${d}/**/*.test.ts`).join(','));\r\n }\r\n }\r\n\r\n // 覆盖率\r\n if (options.coverage) {\r\n args.push('--coverage');\r\n }\r\n\r\n // 配置文件\r\n if (options.configPath) {\r\n args.push('--config', options.configPath);\r\n }\r\n\r\n // 额外参数\r\n if (options.extraArgs) {\r\n args.push(...options.extraArgs);\r\n }\r\n\r\n return args;\r\n}\r\n\r\n/**\r\n * 执行vitest命令并解析JSON输出\r\n */\r\nasync function execVitest(args: string[]): Promise<{\r\n success: boolean;\r\n suites: TestSuiteResult[];\r\n coverage?: CoverageResult;\r\n}> {\r\n return new Promise((resolve, reject) => {\r\n const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx';\r\n\r\n const child = execFile(npx, args, {\r\n cwd: process.cwd(),\r\n env: { ...process.env, FORCE_COLOR: '0' },\r\n maxBuffer: 50 * 1024 * 1024, // 50MB\r\n shell: true,\r\n }, (error, stdout, stderr) => {\r\n // vitest 非零退出码表示测试失败,不是命令执行错误\r\n const output = stdout || stderr || '';\r\n\r\n try {\r\n const parsed = parseVitestJSONOutput(output);\r\n resolve(parsed);\r\n } catch {\r\n // JSON 解析失败,尝试从文本输出提取结果\r\n if (output) {\r\n resolve(parseVitestTextOutput(output, !!error));\r\n } else if (error && error.message.includes('ENOENT')) {\r\n reject(new Error('未找到 vitest,请确保已安装: npm install -D vitest'));\r\n } else {\r\n resolve({ success: !error, suites: [] });\r\n }\r\n }\r\n });\r\n\r\n child.on('error', (err) => {\r\n reject(new Error(`Vitest 执行失败: ${err.message}`));\r\n });\r\n });\r\n}\r\n\r\n/**\r\n * 解析 vitest JSON 输出\r\n */\r\nfunction parseVitestJSONOutput(output: string): {\r\n success: boolean;\r\n suites: TestSuiteResult[];\r\n coverage?: CoverageResult;\r\n} {\r\n // vitest --reporter=json 输出 JSON 结果\r\n // 尝试从输出中提取 JSON\r\n const jsonMatch = output.match(/\\{[\\s\\S]*\"testResults\"[\\s\\S]*\\}/);\r\n\r\n if (!jsonMatch) {\r\n return parseVitestTextOutput(output, false);\r\n }\r\n\r\n try {\r\n const data = JSON.parse(jsonMatch[0]);\r\n const suites: TestSuiteResult[] = [];\r\n\r\n // vitest JSON reporter 格式\r\n if (data.testResults && Array.isArray(data.testResults)) {\r\n for (const fileResult of data.testResults) {\r\n const suite: TestSuiteResult = {\r\n name: path.basename(fileResult.name || fileResult.assertionResults?.[0]?.ancestorTitles?.[0] || 'unknown'),\r\n file: fileResult.name || 'unknown',\r\n type: 'unit' as TestType,\r\n status: mapVitestStatus(fileResult.status),\r\n duration: fileResult.duration || 0,\r\n tests: (fileResult.assertionResults || []).map((assertion: Record<string, unknown>) => ({\r\n name: assertion.title || assertion.fullName || 'unknown',\r\n file: fileResult.name || 'unknown',\r\n status: mapVitestStatus(assertion.status as string),\r\n duration: (assertion.duration as number) || 0,\r\n error: (assertion.failureMessages as string[] | undefined)?.length\r\n ? { message: ((assertion.failureMessages as string[])[0]) }\r\n : undefined,\r\n retries: 0,\r\n }) as TestCaseResult),\r\n };\r\n suites.push(suite);\r\n }\r\n }\r\n\r\n // 解析覆盖率\r\n let coverage: CoverageResult | undefined;\r\n if (data.coverageMap) {\r\n coverage = extractCoverage(data.coverageMap);\r\n }\r\n\r\n const success = data.success !== false && suites.every((s) => s.status !== 'failed');\r\n\r\n return { success, suites, coverage };\r\n } catch {\r\n return parseVitestTextOutput(output, false);\r\n }\r\n}\r\n\r\n/**\r\n * 解析 vitest 文本输出(JSON 解析失败时的回退方案)\r\n */\r\nfunction parseVitestTextOutput(output: string, hasError: boolean): {\r\n success: boolean;\r\n suites: TestSuiteResult[];\r\n} {\r\n const suites: TestSuiteResult[] = [];\r\n let totalPassed = 0;\r\n let totalFailed = 0;\r\n\r\n // 匹配 vitest 文本输出格式\r\n // ✓ tests/unit/example.test.ts (2 tests)\r\n const suiteRegex = /[✓✗×]\\s+(.+\\.test\\.ts|.+\\.spec\\.ts)\\s*\\((\\d+)\\s+test/i;\r\n const lines = output.split('\\n');\r\n\r\n for (const line of lines) {\r\n const match = line.match(suiteRegex);\r\n if (match) {\r\n const file = match[1];\r\n const testCount = parseInt(match[2], 10);\r\n const isPassed = line.includes('✓');\r\n\r\n if (isPassed) totalPassed += testCount;\r\n else totalFailed += testCount;\r\n\r\n suites.push({\r\n name: path.basename(file),\r\n file,\r\n type: 'unit',\r\n status: isPassed ? 'passed' : 'failed',\r\n duration: 0,\r\n tests: Array.from({ length: testCount }, (_, i) => ({\r\n name: `test ${i + 1}`,\r\n file,\r\n status: isPassed ? ('passed' as TestStatus) : ('failed' as TestStatus),\r\n duration: 0,\r\n retries: 0,\r\n })),\r\n });\r\n }\r\n }\r\n\r\n return {\r\n success: !hasError || totalFailed === 0,\r\n suites,\r\n };\r\n}\r\n\r\n/**\r\n * 映射 vitest 状态到 QAT 状态\r\n */\r\nfunction mapVitestStatus(status: string): TestStatus {\r\n switch (status) {\r\n case 'passed':\r\n case 'pass':\r\n return 'passed';\r\n case 'failed':\r\n case 'fail':\r\n return 'failed';\r\n case 'skipped':\r\n case 'skip':\r\n case 'pending':\r\n return 'skipped';\r\n default:\r\n return 'pending';\r\n }\r\n}\r\n\r\n/**\r\n * 从 vitest coverage map 提取汇总数据\r\n */\r\nfunction extractCoverage(coverageMap: Record<string, unknown>): CoverageResult {\r\n let totalStatements = 0;\r\n let coveredStatements = 0;\r\n let totalFunctions = 0;\r\n let coveredFunctions = 0;\r\n let totalBranches = 0;\r\n let coveredBranches = 0;\r\n let totalLines = 0;\r\n let coveredLines = 0;\r\n\r\n for (const fileCov of Object.values(coverageMap) as Record<string, unknown>[]) {\r\n const s = fileCov.s as Record<string, number> || {};\r\n const f = fileCov.f as Record<string, number> || {};\r\n const b = fileCov.b as Record<string, Record<string, number>> || {};\r\n\r\n for (const count of Object.values(s)) {\r\n totalLines++;\r\n if (count > 0) coveredLines++;\r\n }\r\n\r\n for (const count of Object.values(f)) {\r\n totalFunctions++;\r\n if (count > 0) coveredFunctions++;\r\n }\r\n\r\n for (const branch of Object.values(b)) {\r\n for (const count of Object.values(branch)) {\r\n totalBranches++;\r\n if (count > 0) coveredBranches++;\r\n }\r\n }\r\n }\r\n\r\n totalStatements = totalLines;\r\n coveredStatements = coveredLines;\r\n\r\n return {\r\n lines: totalLines > 0 ? coveredLines / totalLines : 0,\r\n statements: totalStatements > 0 ? coveredStatements / totalStatements : 0,\r\n functions: totalFunctions > 0 ? coveredFunctions / totalFunctions : 0,\r\n branches: totalBranches > 0 ? coveredBranches / totalBranches : 0,\r\n };\r\n}\r\n","/**\r\n * Playwright 运行器 - 通过子进程调用Playwright,解析输出收集结果\r\n */\r\n\r\nimport { execFile } from 'node:child_process';\r\nimport path from 'node:path';\r\nimport type { TestRunResult, TestSuiteResult, TestCaseResult, TestStatus, TestType } from '../types/index.js';\r\n\r\nexport interface PlaywrightRunnerOptions {\r\n type: TestType;\r\n files?: string[];\r\n browsers?: ('chromium' | 'firefox' | 'webkit')[];\r\n headed?: boolean;\r\n configPath?: string;\r\n /** 额外传递给 playwright 的参数 */\r\n extraArgs?: string[];\r\n}\r\n\r\n/**\r\n * 执行Playwright测试\r\n */\r\nexport async function runPlaywright(options: PlaywrightRunnerOptions): Promise<TestRunResult> {\r\n const startTime = Date.now();\r\n\r\n const args = buildPlaywrightArgs(options);\r\n\r\n try {\r\n const result = await execPlaywright(args);\r\n const endTime = Date.now();\r\n\r\n return {\r\n type: options.type,\r\n status: result.success ? 'passed' : 'failed',\r\n startTime,\r\n endTime,\r\n duration: endTime - startTime,\r\n suites: result.suites,\r\n };\r\n } catch (error) {\r\n const endTime = Date.now();\r\n return {\r\n type: options.type,\r\n status: 'failed',\r\n startTime,\r\n endTime,\r\n duration: endTime - startTime,\r\n suites: [{\r\n name: 'Playwright Runner',\r\n file: options.files?.[0] || 'unknown',\r\n type: options.type,\r\n status: 'failed',\r\n duration: endTime - startTime,\r\n tests: [{\r\n name: 'Runner Error',\r\n file: options.files?.[0] || 'unknown',\r\n status: 'failed',\r\n duration: 0,\r\n error: {\r\n message: error instanceof Error ? error.message : String(error),\r\n },\r\n retries: 0,\r\n }],\r\n }],\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * 构建playwright命令参数\r\n */\r\nfunction buildPlaywrightArgs(options: PlaywrightRunnerOptions): string[] {\r\n const args = ['playwright', 'test', '--reporter=json'];\r\n\r\n // 指定测试文件或目录\r\n if (options.files && options.files.length > 0) {\r\n args.push(...options.files);\r\n } else {\r\n // 根据测试类型确定测试目录\r\n const dirMap: Record<string, string> = {\r\n e2e: 'tests/e2e',\r\n visual: 'tests/visual',\r\n performance: 'tests/e2e',\r\n };\r\n const testDir = dirMap[options.type];\r\n if (testDir) {\r\n args.push(testDir);\r\n }\r\n }\r\n\r\n // 指定浏览器\r\n if (options.browsers && options.browsers.length > 0) {\r\n for (const browser of options.browsers) {\r\n args.push('--project', browser);\r\n }\r\n }\r\n\r\n // 有头模式\r\n if (options.headed) {\r\n args.push('--headed');\r\n }\r\n\r\n // 配置文件\r\n if (options.configPath) {\r\n args.push('--config', options.configPath);\r\n }\r\n\r\n // 额外参数\r\n if (options.extraArgs) {\r\n args.push(...options.extraArgs);\r\n }\r\n\r\n return args;\r\n}\r\n\r\n/**\r\n * 执行playwright命令并解析JSON输出\r\n */\r\nasync function execPlaywright(args: string[]): Promise<{\r\n success: boolean;\r\n suites: TestSuiteResult[];\r\n}> {\r\n return new Promise((resolve, reject) => {\r\n const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx';\r\n\r\n const child = execFile(npx, args, {\r\n cwd: process.cwd(),\r\n env: { ...process.env, FORCE_COLOR: '0', PLAYWRIGHT_JSON_OUTPUT_DIR: '' },\r\n maxBuffer: 50 * 1024 * 1024,\r\n shell: true,\r\n }, (error, stdout, stderr) => {\r\n const output = stdout || stderr || '';\r\n\r\n try {\r\n const parsed = parsePlaywrightJSONOutput(output);\r\n resolve(parsed);\r\n } catch {\r\n if (output) {\r\n resolve(parsePlaywrightTextOutput(output, !!error));\r\n } else if (error && error.message.includes('ENOENT')) {\r\n reject(new Error('未找到 playwright,请确保已安装: npm install -D @playwright/test'));\r\n } else {\r\n resolve({ success: !error, suites: [] });\r\n }\r\n }\r\n });\r\n\r\n child.on('error', (err) => {\r\n reject(new Error(`Playwright 执行失败: ${err.message}`));\r\n });\r\n });\r\n}\r\n\r\n/**\r\n * 解析 Playwright JSON 输出\r\n */\r\nfunction parsePlaywrightJSONOutput(output: string): {\r\n success: boolean;\r\n suites: TestSuiteResult[];\r\n} {\r\n // Playwright JSON reporter 输出格式\r\n // 尝试提取 JSON\r\n const jsonMatch = output.match(/\\{[\\s\\S]*\"suites\"[\\s\\S]*\\}/);\r\n\r\n if (!jsonMatch) {\r\n return parsePlaywrightTextOutput(output, false);\r\n }\r\n\r\n try {\r\n const data = JSON.parse(jsonMatch[0]);\r\n const suites: TestSuiteResult[] = [];\r\n\r\n // Playwright JSON reporter 格式\r\n if (data.suites && Array.isArray(data.suites)) {\r\n for (const suiteData of data.suites) {\r\n extractPlaywrightSuites(suiteData, suites);\r\n }\r\n }\r\n\r\n const success = data.stats?.status === 'passed' || suites.every((s) => s.status !== 'failed');\r\n\r\n return { success, suites };\r\n } catch {\r\n return parsePlaywrightTextOutput(output, false);\r\n }\r\n}\r\n\r\n/**\r\n * 递归提取 Playwright 套件\r\n */\r\nfunction extractPlaywrightSuites(\r\n suiteData: Record<string, unknown>,\r\n result: TestSuiteResult[],\r\n parentPath = '',\r\n): void {\r\n const suiteName = (suiteData.title as string) || 'unknown';\r\n const suitePath = parentPath ? `${parentPath} > ${suiteName}` : suiteName;\r\n\r\n // 如果有 specs,这是一个叶子套件\r\n if (suiteData.specs && Array.isArray(suiteData.specs)) {\r\n const tests: TestCaseResult[] = (suiteData.specs as Record<string, unknown>[]).map((spec) => {\r\n const specTitle = (spec.title as string) || 'unknown';\r\n const specFile = (spec.file as string) || 'unknown';\r\n // 从 tests 数组获取状态\r\n const specTests = spec.tests as Record<string, unknown>[] | undefined;\r\n let status: TestStatus = 'pending';\r\n let duration = 0;\r\n let error: { message: string; stack?: string } | undefined;\r\n\r\n if (specTests && specTests.length > 0) {\r\n const lastRun = specTests[specTests.length - 1] as Record<string, unknown>;\r\n const results = lastRun.results as Record<string, unknown>[] | undefined;\r\n if (results && results.length > 0) {\r\n const lastResult = results[results.length - 1] as Record<string, unknown>;\r\n status = mapPlaywrightStatus(lastResult.status as string);\r\n duration = (lastResult.duration as number) || 0;\r\n if (lastResult.error) {\r\n const err = lastResult.error as Record<string, unknown>;\r\n error = {\r\n message: (err.message as string) || '',\r\n stack: err.stack as string | undefined,\r\n };\r\n }\r\n }\r\n }\r\n\r\n return {\r\n name: specTitle,\r\n file: specFile,\r\n status,\r\n duration,\r\n error,\r\n retries: 0,\r\n };\r\n });\r\n\r\n const suiteStatus = tests.some((t) => t.status === 'failed')\r\n ? 'failed'\r\n : tests.every((t) => t.status === 'skipped')\r\n ? 'skipped'\r\n : 'passed';\r\n\r\n result.push({\r\n name: suiteName,\r\n file: ((suiteData.specs as Record<string, unknown>[])[0]?.file as string) || 'unknown',\r\n type: 'e2e',\r\n status: suiteStatus,\r\n duration: tests.reduce((sum, t) => sum + t.duration, 0),\r\n tests,\r\n });\r\n }\r\n\r\n // 递归处理子套件\r\n if (suiteData.suites && Array.isArray(suiteData.suites)) {\r\n for (const child of suiteData.suites as Record<string, unknown>[]) {\r\n extractPlaywrightSuites(child, result, suitePath);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * 解析 Playwright 文本输出\r\n */\r\nfunction parsePlaywrightTextOutput(output: string, hasError: boolean): {\r\n success: boolean;\r\n suites: TestSuiteResult[];\r\n} {\r\n const suites: TestSuiteResult[] = [];\r\n let totalPassed = 0;\r\n let totalFailed = 0;\r\n let totalSkipped = 0;\r\n\r\n // 匹配 Playwright 文本输出\r\n // ✓ 1 test.ts:3:1 › login page › should display login form (2s)\r\n // ✘ 2 test.ts:10:1 › login page › should submit form (1s)\r\n // - 3 test.ts:20:1 › login page › should show error (skipped)\r\n const testRegex = /^\\s*([✓✘×\\-])\\s+\\d+\\s+(.+\\.test\\.ts|.+\\.spec\\.ts)\\s*:\\d+:\\d+\\s*›\\s*(.+?)\\s*\\((\\d+)ms?\\)/;\r\n\r\n for (const line of output.split('\\n')) {\r\n const match = line.match(testRegex);\r\n if (match) {\r\n const symbol = match[1];\r\n const file = match[2];\r\n const testName = match[3];\r\n const duration = parseInt(match[4], 10);\r\n\r\n let status: TestStatus;\r\n if (symbol === '✓') {\r\n status = 'passed';\r\n totalPassed++;\r\n } else if (symbol === '✘' || symbol === '×') {\r\n status = 'failed';\r\n totalFailed++;\r\n } else {\r\n status = 'skipped';\r\n totalSkipped++;\r\n }\r\n\r\n // 按文件分组\r\n let existingSuite = suites.find((s) => s.file === file);\r\n if (!existingSuite) {\r\n existingSuite = {\r\n name: path.basename(file),\r\n file,\r\n type: 'e2e',\r\n status: 'passed',\r\n duration: 0,\r\n tests: [],\r\n };\r\n suites.push(existingSuite);\r\n }\r\n\r\n existingSuite.tests.push({\r\n name: testName,\r\n file,\r\n status,\r\n duration,\r\n retries: 0,\r\n });\r\n\r\n if (status === 'failed') {\r\n existingSuite.status = 'failed';\r\n }\r\n }\r\n }\r\n\r\n // 更新套件状态和持续时间\r\n for (const suite of suites) {\r\n if (suite.tests.some((t) => t.status === 'failed')) {\r\n suite.status = 'failed';\r\n }\r\n suite.duration = suite.tests.reduce((sum, t) => sum + t.duration, 0);\r\n }\r\n\r\n // 匹配最终汇总\r\n // 3 passed, 1 failed, 1 skipped\r\n const summaryRegex = /(\\d+)\\s+passed.*?(\\d+)\\s+failed.*?(\\d+)\\s+skipped/i;\r\n const summaryMatch = output.match(summaryRegex);\r\n if (summaryMatch && suites.length === 0) {\r\n // 从汇总推断\r\n totalPassed = parseInt(summaryMatch[1], 10);\r\n totalFailed = parseInt(summaryMatch[2], 10);\r\n totalSkipped = parseInt(summaryMatch[3], 10);\r\n }\r\n\r\n return {\r\n success: !hasError || totalFailed === 0,\r\n suites,\r\n };\r\n}\r\n\r\n/**\r\n * 映射 Playwright 状态\r\n */\r\nfunction mapPlaywrightStatus(status: string): TestStatus {\r\n switch (status) {\r\n case 'passed':\r\n case 'pass':\r\n return 'passed';\r\n case 'failed':\r\n case 'fail':\r\n case 'timedOut':\r\n case 'interrupted':\r\n return 'failed';\r\n case 'skipped':\r\n case 'skip':\r\n return 'skipped';\r\n default:\r\n return 'pending';\r\n }\r\n}\r\n","/**\r\n * Lighthouse 运行器 - 通过子进程调用Lighthouse,提取性能指标\r\n */\r\n\r\nimport { execFile } from 'node:child_process';\r\nimport type { TestRunResult, TestSuiteResult, TestCaseResult, TestStatus, PerformanceMetrics } from '../types/index.js';\r\n\r\nexport interface LighthouseRunnerOptions {\r\n urls: string[];\r\n runs?: number;\r\n thresholds?: Partial<Record<'performance' | 'accessibility' | 'best-practices' | 'seo', number>>;\r\n /** 额外传递给 lighthouse 的参数 */\r\n extraArgs?: string[];\r\n}\r\n\r\n/**\r\n * 执行Lighthouse性能测试\r\n */\r\nexport async function runLighthouse(options: LighthouseRunnerOptions): Promise<TestRunResult> {\r\n const startTime = Date.now();\r\n const runs = options.runs || 3;\r\n const urls = options.urls;\r\n\r\n if (urls.length === 0) {\r\n return {\r\n type: 'performance',\r\n status: 'skipped',\r\n startTime,\r\n endTime: Date.now(),\r\n duration: 0,\r\n suites: [],\r\n };\r\n }\r\n\r\n const allResults: LighthouseRunResult[] = [];\r\n\r\n for (const url of urls) {\r\n try {\r\n const urlResults: LighthouseRunResult[] = [];\r\n\r\n for (let i = 0; i < runs; i++) {\r\n const result = await runLighthouseOnce(url, options.extraArgs);\r\n urlResults.push(result);\r\n }\r\n\r\n // 取中位数\r\n const median = getMedianResult(urlResults);\r\n allResults.push(median);\r\n } catch (error) {\r\n allResults.push({\r\n url,\r\n error: error instanceof Error ? error.message : String(error),\r\n scores: { performance: 0, accessibility: 0, bestPractices: 0, seo: 0 },\r\n metrics: {},\r\n });\r\n }\r\n }\r\n\r\n const endTime = Date.now();\r\n\r\n // 构建测试结果\r\n const suites: TestSuiteResult[] = allResults.map((result) => {\r\n const tests: TestCaseResult[] = [\r\n makeTestResult('Performance Score', result.scores.performance, options.thresholds?.performance),\r\n makeTestResult('Accessibility Score', result.scores.accessibility, options.thresholds?.accessibility),\r\n makeTestResult('Best Practices Score', result.scores.bestPractices, options.thresholds?.['best-practices']),\r\n makeTestResult('SEO Score', result.scores.seo, options.thresholds?.seo),\r\n ];\r\n\r\n // 添加核心 Web 指标测试\r\n if (result.metrics.lcp !== undefined) {\r\n tests.push(makeTestResult('Largest Contentful Paint', result.metrics.lcp, 2500, true));\r\n }\r\n if (result.metrics.fid !== undefined) {\r\n tests.push(makeTestResult('First Input Delay', result.metrics.fid, 100, true));\r\n }\r\n if (result.metrics.cls !== undefined) {\r\n tests.push(makeTestResult('Cumulative Layout Shift', result.metrics.cls, 0.1, true));\r\n }\r\n\r\n const suiteStatus: TestStatus = result.error\r\n ? 'failed'\r\n : tests.some((t) => t.status === 'failed')\r\n ? 'failed'\r\n : 'passed';\r\n\r\n return {\r\n name: `Performance: ${result.url}`,\r\n file: result.url,\r\n type: 'performance',\r\n status: suiteStatus,\r\n duration: endTime - startTime,\r\n tests,\r\n };\r\n });\r\n\r\n // 计算平均指标\r\n const avgMetrics = calculateAverageMetrics(allResults);\r\n\r\n const overallStatus = suites.some((s) => s.status === 'failed') ? 'failed' : 'passed';\r\n\r\n return {\r\n type: 'performance',\r\n status: overallStatus,\r\n startTime,\r\n endTime,\r\n duration: endTime - startTime,\r\n suites,\r\n performance: avgMetrics,\r\n };\r\n}\r\n\r\ninterface LighthouseRunResult {\r\n url: string;\r\n error?: string;\r\n scores: {\r\n performance: number;\r\n accessibility: number;\r\n bestPractices: number;\r\n seo: number;\r\n };\r\n metrics: {\r\n lcp?: number;\r\n fid?: number;\r\n cls?: number;\r\n };\r\n}\r\n\r\n/**\r\n * 单次 Lighthouse 运行\r\n */\r\nasync function runLighthouseOnce(url: string, extraArgs?: string[]): Promise<LighthouseRunResult> {\r\n const args = [\r\n 'lighthouse', url,\r\n '--output=json',\r\n '--quiet',\r\n '--chrome-flags=--headless --no-sandbox',\r\n '--only-categories=performance,accessibility,best-practices,seo',\r\n ];\r\n\r\n if (extraArgs) {\r\n args.push(...extraArgs);\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx';\r\n\r\n execFile(npx, args, {\r\n cwd: process.cwd(),\r\n maxBuffer: 50 * 1024 * 1024,\r\n shell: true,\r\n }, (error, stdout) => {\r\n if (error && !stdout) {\r\n reject(new Error(`Lighthouse 执行失败: ${error.message}`));\r\n return;\r\n }\r\n\r\n try {\r\n const data = JSON.parse(stdout);\r\n const categories = data.categories || {};\r\n const audits = data.audits || {};\r\n\r\n resolve({\r\n url,\r\n scores: {\r\n performance: Math.round((categories.performance?.score || 0) * 100),\r\n accessibility: Math.round((categories.accessibility?.score || 0) * 100),\r\n bestPractices: Math.round((categories['best-practices']?.score || 0) * 100),\r\n seo: Math.round((categories.seo?.score || 0) * 100),\r\n },\r\n metrics: {\r\n lcp: audits['largest-contentful-paint']?.numericValue,\r\n fid: audits['max-potential-fid']?.numericValue,\r\n cls: audits['cumulative-layout-shift']?.numericValue,\r\n },\r\n });\r\n } catch {\r\n reject(new Error('Lighthouse 输出解析失败'));\r\n }\r\n });\r\n });\r\n}\r\n\r\n/**\r\n * 获取中位数结果\r\n */\r\nfunction getMedianResult(results: LighthouseRunResult[]): LighthouseRunResult {\r\n if (results.length === 1) return results[0];\r\n\r\n // 按 performance score 排序取中位数\r\n const sorted = [...results].sort(\r\n (a, b) => a.scores.performance - b.scores.performance,\r\n );\r\n const mid = Math.floor(sorted.length / 2);\r\n return sorted[mid];\r\n}\r\n\r\n/**\r\n * 创建测试结果\r\n */\r\nfunction makeTestResult(\r\n name: string,\r\n value: number,\r\n threshold?: number,\r\n isLowerBetter = false,\r\n): TestCaseResult {\r\n let status: TestStatus = 'passed';\r\n let error: { message: string } | undefined;\r\n\r\n if (threshold !== undefined) {\r\n const passed = isLowerBetter ? value <= threshold : value >= threshold;\r\n if (!passed) {\r\n status = 'failed';\r\n error = {\r\n message: `${name}: ${isLowerBetter ? `${value}ms` : value} (${isLowerBetter ? '>' : '<'} threshold ${isLowerBetter ? `${threshold}ms` : threshold})`,\r\n };\r\n }\r\n }\r\n\r\n return {\r\n name,\r\n file: 'lighthouse',\r\n status,\r\n duration: 0,\r\n error,\r\n retries: 0,\r\n };\r\n}\r\n\r\n/**\r\n * 计算平均分数\r\n */\r\nfunction calculateAverageScores(results: LighthouseRunResult[]): PerformanceMetrics {\r\n const valid = results.filter((r) => !r.error);\r\n if (valid.length === 0) {\r\n return { performance: 0, accessibility: 0, bestPractices: 0, seo: 0 };\r\n }\r\n\r\n return {\r\n performance: Math.round(valid.reduce((s, r) => s + r.scores.performance, 0) / valid.length),\r\n accessibility: Math.round(valid.reduce((s, r) => s + r.scores.accessibility, 0) / valid.length),\r\n bestPractices: Math.round(valid.reduce((s, r) => s + r.scores.bestPractices, 0) / valid.length),\r\n seo: Math.round(valid.reduce((s, r) => s + r.scores.seo, 0) / valid.length),\r\n };\r\n}\r\n\r\n/**\r\n * 计算平均指标\r\n */\r\nfunction calculateAverageMetrics(results: LighthouseRunResult[]): PerformanceMetrics {\r\n const valid = results.filter((r) => !r.error);\r\n const scores = calculateAverageScores(results);\r\n\r\n const lcpValues = valid.map((r) => r.metrics.lcp).filter((v): v is number => v !== undefined);\r\n const fidValues = valid.map((r) => r.metrics.fid).filter((v): v is number => v !== undefined);\r\n const clsValues = valid.map((r) => r.metrics.cls).filter((v): v is number => v !== undefined);\r\n\r\n return {\r\n ...scores,\r\n lcp: lcpValues.length > 0 ? lcpValues.reduce((s, v) => s + v, 0) / lcpValues.length : undefined,\r\n fid: fidValues.length > 0 ? fidValues.reduce((s, v) => s + v, 0) / fidValues.length : undefined,\r\n cls: clsValues.length > 0 ? clsValues.reduce((s, v) => s + v, 0) / clsValues.length : undefined,\r\n };\r\n}\r\n","/**\r\n * mock 命令 - 启停Mock服务器,管理API路由\r\n */\r\n\r\nimport { Command } from 'commander';\r\nimport chalk from 'chalk';\r\nimport ora from 'ora';\r\nimport type { MockOptions } from '../types/index.js';\r\nimport { loadConfig } from '../services/config.js';\r\nimport {\r\n startMockServer,\r\n stopMockServer,\r\n getMockServerState,\r\n loadMockRoutes,\r\n} from '../services/mock-server.js';\r\n\r\nexport function registerMockCommand(program: Command): void {\r\n program\r\n .command('mock')\r\n .description('Mock服务管理 - 启动/停止Mock API服务器')\r\n .argument('<action>', '操作类型 (start|stop|status)')\r\n .option('-p, --port <port>', '指定端口号', '3456')\r\n .action(async (action: string, options: { port?: string; config?: string }) => {\r\n try {\r\n await executeMock({\r\n action: action as MockOptions['action'],\r\n port: options.port ? parseInt(options.port, 10) : undefined,\r\n config: options.config,\r\n });\r\n } catch (error) {\r\n console.error(chalk.red(`\\n ✗ ${error instanceof Error ? error.message : String(error)}\\n`));\r\n process.exit(1);\r\n }\r\n });\r\n}\r\n\r\nasync function executeMock(\r\n options: { action: MockOptions['action']; port?: number; config?: string },\r\n): Promise<void> {\r\n const config = await loadConfig(options.config);\r\n const port = options.port || config.mock.port;\r\n\r\n switch (options.action) {\r\n case 'start': {\r\n await startMock(port, config.mock.routesDir);\r\n break;\r\n }\r\n case 'stop': {\r\n await stopMock();\r\n break;\r\n }\r\n case 'status': {\r\n showStatus();\r\n break;\r\n }\r\n default: {\r\n console.error(chalk.red(`\\n 未知操作: ${options.action}`));\r\n console.log(chalk.gray(' 可用操作: start, stop, status\\n'));\r\n process.exit(1);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * 启动 Mock 服务器\r\n */\r\nasync function startMock(port: number, routesDir: string): Promise<void> {\r\n const state = getMockServerState();\r\n\r\n if (state.running) {\r\n console.log(chalk.yellow(`\\n Mock服务器已在运行 (端口: ${state.port})\\n`));\r\n return;\r\n }\r\n\r\n const spinner = ora(`正在启动Mock服务器 (端口: ${port})...`).start();\r\n\r\n try {\r\n // 加载路由\r\n const routes = await loadMockRoutes(routesDir);\r\n if (routes.length > 0) {\r\n spinner.text = `正在启动Mock服务器 (加载了 ${routes.length} 个路由)...`;\r\n }\r\n\r\n await startMockServer(port, routes);\r\n\r\n spinner.succeed(`Mock服务器已启动`);\r\n\r\n console.log();\r\n console.log(chalk.white(' 地址:'), chalk.cyan(`http://localhost:${port}`));\r\n console.log(chalk.white(' 路由:'), routes.length > 0 ? `${routes.length} 个` : chalk.gray('使用默认路由'));\r\n console.log(chalk.white(' 健康检查:'), chalk.cyan(`http://localhost:${port}/api/health`));\r\n\r\n if (routes.length > 0) {\r\n console.log();\r\n console.log(chalk.white(' 已注册路由:'));\r\n for (const route of routes.slice(0, 10)) {\r\n const method = chalk.gray(route.method.padEnd(6));\r\n console.log(` ${method} ${route.path}`);\r\n }\r\n if (routes.length > 10) {\r\n console.log(chalk.gray(` ... 还有 ${routes.length - 10} 个路由`));\r\n }\r\n }\r\n\r\n console.log();\r\n console.log(chalk.gray(' 按 Ctrl+C 停止服务器'));\r\n\r\n // 保持进程运行\r\n process.on('SIGINT', async () => {\r\n const stopSpinner = ora('正在停止Mock服务器...').start();\r\n await stopMockServer();\r\n stopSpinner.succeed('Mock服务器已停止');\r\n process.exit(0);\r\n });\r\n\r\n // 防止进程退出\r\n await new Promise(() => {});\r\n } catch (error) {\r\n spinner.fail('Mock服务器启动失败');\r\n throw error;\r\n }\r\n}\r\n\r\n/**\r\n * 停止 Mock 服务器\r\n */\r\nasync function stopMock(): Promise<void> {\r\n const state = getMockServerState();\r\n\r\n if (!state.running) {\r\n console.log(chalk.yellow('\\n Mock服务器未在运行\\n'));\r\n return;\r\n }\r\n\r\n const spinner = ora('正在停止Mock服务器...').start();\r\n\r\n try {\r\n await stopMockServer();\r\n spinner.succeed('Mock服务器已停止');\r\n console.log();\r\n } catch (error) {\r\n spinner.fail('Mock服务器停止失败');\r\n throw error;\r\n }\r\n}\r\n\r\n/**\r\n * 显示 Mock 服务器状态\r\n */\r\nfunction showStatus(): void {\r\n const state = getMockServerState();\r\n\r\n console.log();\r\n if (state.running) {\r\n console.log(chalk.green(' ● Mock服务器运行中'));\r\n console.log(chalk.white(' 端口:'), state.port);\r\n console.log(chalk.white(' 路由:'), state.routes.length);\r\n } else {\r\n console.log(chalk.gray(' ○ Mock服务器未运行'));\r\n console.log(chalk.gray(' 使用 qat mock start 启动'));\r\n }\r\n console.log();\r\n}\r\n","/**\r\n * report 命令 - 聚合测试结果,生成HTML报告,支持历史记录\r\n */\r\n\r\nimport { Command } from 'commander';\r\nimport chalk from 'chalk';\r\nimport ora from 'ora';\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport type { ReportOptions, TestRunResult } from '../types/index.js';\r\nimport { loadConfig } from '../services/config.js';\r\nimport { aggregateResults, generateHTMLReport, writeReportToDisk, type ReportData } from '../services/reporter.js';\r\n\r\n/** 测试结果存储目录 */\r\nconst RESULTS_DIR = '.qat-results';\r\n\r\nexport function registerReportCommand(program: Command): void {\r\n program\r\n .command('report')\r\n .description('生成测试报告 - 聚合所有测试结果并输出HTML')\r\n .option('-o, --output <dir>', '报告输出目录')\r\n .option('--open', '生成后自动打开报告', false)\r\n .action(async (options: ReportOptions) => {\r\n try {\r\n await executeReport(options);\r\n } catch (error) {\r\n console.error(chalk.red(`\\n ✗ ${error instanceof Error ? error.message : String(error)}\\n`));\r\n process.exit(1);\r\n }\r\n });\r\n}\r\n\r\nasync function executeReport(options: ReportOptions): Promise<void> {\r\n const config = await loadConfig(options.config);\r\n const outputDir = options.output || config.report.outputDir;\r\n\r\n const spinner = ora('正在收集测试结果...').start();\r\n\r\n // 1. 收集测试结果\r\n const results = collectResults();\r\n\r\n if (results.length === 0) {\r\n spinner.info('没有找到测试结果');\r\n console.log(chalk.gray('\\n 提示: 先运行 qat run 生成测试结果\\n'));\r\n return;\r\n }\r\n\r\n spinner.text = '正在生成HTML报告...';\r\n\r\n // 2. 聚合结果\r\n const reportData = aggregateResults(results);\r\n\r\n // 3. 写入报告\r\n const reportPath = writeReportToDisk(reportData, outputDir);\r\n\r\n // 4. 保存本次结果到历史记录\r\n saveResultToHistory(reportData);\r\n\r\n spinner.succeed('测试报告已生成');\r\n\r\n // 5. 显示结果\r\n displayReportResult(reportPath, reportData);\r\n\r\n // 6. 自动打开浏览器\r\n if (options.open || config.report.open) {\r\n await openReport(reportPath);\r\n }\r\n}\r\n\r\n/**\r\n * 收集测试结果\r\n */\r\nfunction collectResults(): TestRunResult[] {\r\n const results: TestRunResult[] = [];\r\n const resultsPath = path.join(process.cwd(), RESULTS_DIR);\r\n\r\n if (!fs.existsSync(resultsPath)) {\r\n return results;\r\n }\r\n\r\n const files = fs.readdirSync(resultsPath)\r\n .filter((f) => f.endsWith('.json'))\r\n .sort()\r\n .reverse(); // 最新的在前\r\n\r\n // 取最新一次运行的结果\r\n if (files.length > 0) {\r\n const latestFile = path.join(resultsPath, files[0]);\r\n try {\r\n const data = JSON.parse(fs.readFileSync(latestFile, 'utf-8'));\r\n if (Array.isArray(data)) {\r\n results.push(...data);\r\n } else if (data.results) {\r\n results.push(...data.results);\r\n }\r\n } catch {\r\n // 解析失败跳过\r\n }\r\n }\r\n\r\n return results;\r\n}\r\n\r\n/**\r\n * 保存结果到历史记录\r\n */\r\nfunction saveResultToHistory(reportData: ReportData): void {\r\n const resultsPath = path.join(process.cwd(), RESULTS_DIR);\r\n\r\n if (!fs.existsSync(resultsPath)) {\r\n fs.mkdirSync(resultsPath, { recursive: true });\r\n }\r\n\r\n const timestamp = new Date(reportData.timestamp).toISOString().replace(/[:.]/g, '-');\r\n const fileName = `result-${timestamp}.json`;\r\n const filePath = path.join(resultsPath, fileName);\r\n\r\n fs.writeFileSync(filePath, JSON.stringify(reportData, null, 2), 'utf-8');\r\n\r\n // 清理旧结果(保留最近 20 次)\r\n const files = fs.readdirSync(resultsPath)\r\n .filter((f) => f.startsWith('result-') && f.endsWith('.json'))\r\n .sort();\r\n\r\n while (files.length > 20) {\r\n const oldest = files.shift()!;\r\n fs.unlinkSync(path.join(resultsPath, oldest));\r\n }\r\n}\r\n\r\n/**\r\n * 显示报告生成结果\r\n */\r\nfunction displayReportResult(reportPath: string, data: ReportData): void {\r\n const relativePath = path.relative(process.cwd(), reportPath);\r\n\r\n console.log();\r\n console.log(chalk.green(' ✓ 测试报告已生成'));\r\n console.log();\r\n console.log(chalk.white(' 报告路径:'), chalk.cyan(relativePath));\r\n console.log(chalk.white(' 测试用例:'), `${data.summary.total} total`);\r\n console.log(chalk.white(' 通过:'), chalk.green(String(data.summary.passed)));\r\n if (data.summary.failed > 0) {\r\n console.log(chalk.white(' 失败:'), chalk.red(String(data.summary.failed)));\r\n }\r\n if (data.summary.skipped > 0) {\r\n console.log(chalk.white(' 跳过:'), chalk.yellow(String(data.summary.skipped)));\r\n }\r\n\r\n // 按类型统计\r\n if (Object.keys(data.byType).length > 0) {\r\n console.log();\r\n console.log(chalk.white(' 按类型:'));\r\n for (const [type, stats] of Object.entries(data.byType)) {\r\n const rate = stats.total > 0 ? ((stats.passed / stats.total) * 100).toFixed(0) : '0';\r\n const icon = stats.failed > 0 ? chalk.red('✗') : chalk.green('✓');\r\n console.log(` ${icon} ${type}: ${stats.passed}/${stats.total} (${rate}%)`);\r\n }\r\n }\r\n\r\n console.log();\r\n}\r\n\r\n/**\r\n * 自动打开报告\r\n */\r\nasync function openReport(reportPath: string): Promise<void> {\r\n const { exec } = await import('node:child_process');\r\n const platform = process.platform;\r\n\r\n let command: string;\r\n if (platform === 'win32') {\r\n command = `start \"\" \"${reportPath}\"`;\r\n } else if (platform === 'darwin') {\r\n command = `open \"${reportPath}\"`;\r\n } else {\r\n command = `xdg-open \"${reportPath}\"`;\r\n }\r\n\r\n exec(command, { shell: true }, (error) => {\r\n if (error) {\r\n console.log(chalk.gray(' 提示: 手动打开报告查看'));\r\n }\r\n });\r\n}\r\n","/**\r\n * 报告聚合服务 - 收集各运行器结果,生成完整HTML报告\r\n */\r\n\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport type { TestRunResult, TestSuiteResult, TestCaseResult } from '../types/index.js';\r\n\r\n/** 报告数据 */\r\nexport interface ReportData {\r\n timestamp: number;\r\n duration: number;\r\n results: TestRunResult[];\r\n summary: {\r\n total: number;\r\n passed: number;\r\n failed: number;\r\n skipped: number;\r\n pending: number;\r\n };\r\n /** 按测试类型分组统计 */\r\n byType: Record<string, { total: number; passed: number; failed: number; skipped: number }>;\r\n}\r\n\r\n/**\r\n * 聚合多个测试运行结果\r\n */\r\nexport function aggregateResults(results: TestRunResult[]): ReportData {\r\n const summary = {\r\n total: 0,\r\n passed: 0,\r\n failed: 0,\r\n skipped: 0,\r\n pending: 0,\r\n };\r\n\r\n const byType: Record<string, { total: number; passed: number; failed: number; skipped: number }> = {};\r\n\r\n for (const result of results) {\r\n const typeKey = result.type;\r\n if (!byType[typeKey]) {\r\n byType[typeKey] = { total: 0, passed: 0, failed: 0, skipped: 0 };\r\n }\r\n\r\n for (const suite of result.suites) {\r\n for (const test of suite.tests) {\r\n summary.total++;\r\n byType[typeKey].total++;\r\n\r\n if (test.status === 'passed') {\r\n summary.passed++;\r\n byType[typeKey].passed++;\r\n } else if (test.status === 'failed') {\r\n summary.failed++;\r\n byType[typeKey].failed++;\r\n } else if (test.status === 'skipped') {\r\n summary.skipped++;\r\n byType[typeKey].skipped++;\r\n } else {\r\n summary.pending++;\r\n }\r\n }\r\n }\r\n }\r\n\r\n const totalDuration = results.reduce((sum, r) => sum + r.duration, 0);\r\n\r\n return {\r\n timestamp: Date.now(),\r\n duration: totalDuration,\r\n results,\r\n summary,\r\n byType,\r\n };\r\n}\r\n\r\n/**\r\n * 格式化耗时\r\n */\r\nfunction formatDuration(ms: number): string {\r\n if (ms < 1000) return `${ms}ms`;\r\n if (ms < 60000) return `${(ms / 1000).toFixed(2)}s`;\r\n const minutes = Math.floor(ms / 60000);\r\n const seconds = ((ms % 60000) / 1000).toFixed(0);\r\n return `${minutes}m ${seconds}s`;\r\n}\r\n\r\n/**\r\n * 格式化时间戳\r\n */\r\nfunction formatTimestamp(ts: number): string {\r\n return new Date(ts).toLocaleString('zh-CN', {\r\n year: 'numeric',\r\n month: '2-digit',\r\n day: '2-digit',\r\n hour: '2-digit',\r\n minute: '2-digit',\r\n second: '2-digit',\r\n });\r\n}\r\n\r\n/**\r\n * 生成测试套件 HTML\r\n */\r\nfunction renderSuiteHTML(suite: TestSuiteResult): string {\r\n const statusClass = suite.status === 'passed' ? 'passed' : suite.status === 'failed' ? 'failed' : 'skipped';\r\n const testsHTML = suite.tests.map((test: TestCaseResult) => {\r\n const testStatusClass = test.status;\r\n const errorHTML = test.error\r\n ? `<div class=\"error-message\"><strong>${escapeHTML(test.error.message)}</strong>${\r\n test.error.stack ? `\\n${escapeHTML(test.error.stack)}` : ''\r\n }${\r\n test.error.expected && test.error.actual\r\n ? `\\n\\nExpected: ${escapeHTML(test.error.expected)}\\nActual: ${escapeHTML(test.error.actual)}`\r\n : ''\r\n }</div>`\r\n : '';\r\n return `<div class=\"test-item\">\r\n <span class=\"status-dot ${testStatusClass}\"></span>\r\n <span class=\"test-name\">${escapeHTML(test.name)}</span>\r\n <span class=\"duration\">${formatDuration(test.duration)}</span>\r\n ${test.retries > 0 ? `<span class=\"retries\">重试 ${test.retries} 次</span>` : ''}\r\n </div>\r\n ${errorHTML}`;\r\n }).join('');\r\n\r\n return `<div class=\"suite\">\r\n <div class=\"suite-header\" onclick=\"this.parentElement.classList.toggle('collapsed')\">\r\n <div>\r\n <span class=\"status-dot ${statusClass}\"></span>\r\n <strong>${escapeHTML(suite.name)}</strong>\r\n <span class=\"suite-file\">${escapeHTML(suite.file)}</span>\r\n </div>\r\n <div>\r\n <span class=\"duration\">${formatDuration(suite.duration)}</span>\r\n <span class=\"toggle-icon\">▼</span>\r\n </div>\r\n </div>\r\n <div class=\"suite-body\">${testsHTML}</div>\r\n </div>`;\r\n}\r\n\r\n/**\r\n * 转义 HTML 特殊字符\r\n */\r\nfunction escapeHTML(str: string): string {\r\n return str\r\n .replace(/&/g, '&amp;')\r\n .replace(/</g, '&lt;')\r\n .replace(/>/g, '&gt;')\r\n .replace(/\"/g, '&quot;')\r\n .replace(/'/g, '&#039;');\r\n}\r\n\r\n/**\r\n * 生成完整 HTML 报告\r\n */\r\nexport function generateHTMLReport(data: ReportData): string {\r\n const passRate = data.summary.total > 0\r\n ? ((data.summary.passed / data.summary.total) * 100).toFixed(1)\r\n : '0';\r\n\r\n const suitesHTML = data.results\r\n .flatMap((r) => r.suites)\r\n .map(renderSuiteHTML)\r\n .join('\\n');\r\n\r\n const byTypeHTML = Object.entries(data.byType)\r\n .map(([type, stats]) => {\r\n const rate = stats.total > 0 ? ((stats.passed / stats.total) * 100).toFixed(0) : '0';\r\n return `<div class=\"type-card\">\r\n <div class=\"type-name\">${type}</div>\r\n <div class=\"type-stats\">\r\n <span class=\"passed\">${stats.passed} 通过</span>\r\n <span class=\"failed\">${stats.failed} 失败</span>\r\n <span class=\"skipped\">${stats.skipped} 跳过</span>\r\n </div>\r\n <div class=\"type-rate\">${rate}%</div>\r\n </div>`;\r\n })\r\n .join('\\n');\r\n\r\n return `<!DOCTYPE html>\r\n<html lang=\"zh-CN\">\r\n<head>\r\n <meta charset=\"UTF-8\">\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n <title>QAT 测试报告 - ${formatTimestamp(data.timestamp)}</title>\r\n <style>\r\n :root {\r\n --color-passed: #22c55e;\r\n --color-failed: #ef4444;\r\n --color-skipped: #f59e0b;\r\n --color-info: #3b82f6;\r\n --bg-primary: #ffffff;\r\n --bg-secondary: #f8fafc;\r\n --text-primary: #1e293b;\r\n --text-secondary: #64748b;\r\n --border-color: #e2e8f0;\r\n }\r\n * { margin: 0; padding: 0; box-sizing: border-box; }\r\n body {\r\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\r\n background: var(--bg-secondary);\r\n color: var(--text-primary);\r\n line-height: 1.6;\r\n }\r\n .container { max-width: 1200px; margin: 0 auto; padding: 20px; }\r\n header {\r\n background: white;\r\n border-bottom: 1px solid var(--border-color);\r\n padding: 20px;\r\n margin-bottom: 20px;\r\n border-radius: 8px;\r\n }\r\n header h1 { font-size: 24px; font-weight: 600; }\r\n header .meta { color: var(--text-secondary); font-size: 14px; margin-top: 4px; }\r\n .summary { display: flex; gap: 16px; margin: 20px 0; flex-wrap: wrap; }\r\n .card {\r\n padding: 16px 24px; border-radius: 8px; color: white;\r\n font-weight: 600; min-width: 120px; text-align: center;\r\n }\r\n .card.passed { background: var(--color-passed); }\r\n .card.failed { background: var(--color-failed); }\r\n .card.skipped { background: var(--color-skipped); }\r\n .card.total { background: var(--color-info); }\r\n .card .card-value { font-size: 28px; }\r\n .card .card-label { font-size: 13px; opacity: 0.9; }\r\n .pass-rate {\r\n font-size: 48px; font-weight: 700; text-align: center; margin: 20px 0;\r\n color: ${parseFloat(passRate) >= 80 ? 'var(--color-passed)' : parseFloat(passRate) >= 50 ? 'var(--color-skipped)' : 'var(--color-failed)'};\r\n }\r\n .pass-rate-label { text-align: center; color: var(--text-secondary); margin-bottom: 20px; }\r\n .by-type { display: flex; gap: 12px; flex-wrap: wrap; margin: 20px 0; }\r\n .type-card {\r\n background: white; border: 1px solid var(--border-color); border-radius: 8px;\r\n padding: 12px 16px; min-width: 180px;\r\n }\r\n .type-name { font-weight: 600; text-transform: capitalize; margin-bottom: 4px; }\r\n .type-stats { font-size: 13px; }\r\n .type-stats span { margin-right: 8px; }\r\n .type-stats .passed { color: var(--color-passed); }\r\n .type-stats .failed { color: var(--color-failed); }\r\n .type-stats .skipped { color: var(--color-skipped); }\r\n .type-rate { font-size: 20px; font-weight: 700; margin-top: 4px; }\r\n .suite {\r\n background: white; border: 1px solid var(--border-color);\r\n border-radius: 8px; margin-bottom: 12px; overflow: hidden;\r\n }\r\n .suite-header {\r\n padding: 12px 16px; display: flex; justify-content: space-between;\r\n align-items: center; cursor: pointer; user-select: none;\r\n }\r\n .suite-header:hover { background: var(--bg-secondary); }\r\n .suite-header div { display: flex; align-items: center; gap: 8px; }\r\n .suite.collapsed .suite-body { display: none; }\r\n .suite-file { color: var(--text-secondary); font-size: 12px; }\r\n .toggle-icon { font-size: 12px; color: var(--text-secondary); transition: transform 0.2s; }\r\n .suite.collapsed .toggle-icon { transform: rotate(-90deg); }\r\n .suite-body { border-top: 1px solid var(--border-color); padding: 8px 16px; }\r\n .test-item {\r\n padding: 8px 0; display: flex; align-items: center; gap: 8px;\r\n }\r\n .test-item + .test-item { border-top: 1px solid var(--border-color); }\r\n .status-dot {\r\n width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;\r\n }\r\n .status-dot.passed { background: var(--color-passed); }\r\n .status-dot.failed { background: var(--color-failed); }\r\n .status-dot.skipped { background: var(--color-skipped); }\r\n .status-dot.pending { background: var(--text-secondary); }\r\n .test-name { flex: 1; }\r\n .duration { color: var(--text-secondary); font-size: 13px; }\r\n .retries { color: var(--color-skipped); font-size: 12px; }\r\n .error-message {\r\n background: #fef2f2; color: #991b1b; padding: 12px;\r\n border-radius: 4px; font-family: 'Fira Code', monospace;\r\n font-size: 13px; margin: 8px 0 8px 16px; white-space: pre-wrap;\r\n word-break: break-all;\r\n }\r\n footer {\r\n text-align: center; padding: 20px; color: var(--text-secondary);\r\n font-size: 13px; margin-top: 40px;\r\n }\r\n </style>\r\n</head>\r\n<body>\r\n <div class=\"container\">\r\n <header>\r\n <h1>QAT 测试报告</h1>\r\n <div class=\"meta\">生成时间: ${formatTimestamp(data.timestamp)} | 总耗时: ${formatDuration(data.duration)}</div>\r\n </header>\r\n\r\n <div class=\"pass-rate\">${passRate}%</div>\r\n <div class=\"pass-rate-label\">测试通过率</div>\r\n\r\n <div class=\"summary\">\r\n <div class=\"card total\">\r\n <div class=\"card-value\">${data.summary.total}</div>\r\n <div class=\"card-label\">总计</div>\r\n </div>\r\n <div class=\"card passed\">\r\n <div class=\"card-value\">${data.summary.passed}</div>\r\n <div class=\"card-label\">通过</div>\r\n </div>\r\n <div class=\"card failed\">\r\n <div class=\"card-value\">${data.summary.failed}</div>\r\n <div class=\"card-label\">失败</div>\r\n </div>\r\n <div class=\"card skipped\">\r\n <div class=\"card-value\">${data.summary.skipped}</div>\r\n <div class=\"card-label\">跳过</div>\r\n </div>\r\n </div>\r\n\r\n ${Object.keys(data.byType).length > 0 ? `<h2 style=\"margin-top:30px;margin-bottom:10px;\">按类型统计</h2><div class=\"by-type\">${byTypeHTML}</div>` : ''}\r\n\r\n <h2 style=\"margin-top:30px;margin-bottom:10px;\">测试详情</h2>\r\n ${suitesHTML || '<p style=\"color:var(--text-secondary)\">暂无测试结果</p>'}\r\n\r\n <footer>由 QAT 自动化测试工具生成</footer>\r\n </div>\r\n</body>\r\n</html>`;\r\n}\r\n\r\n/**\r\n * 将报告写入磁盘\r\n * @returns 报告文件路径\r\n */\r\nexport function writeReportToDisk(data: ReportData, outputDir: string): string {\r\n const html = generateHTMLReport(data);\r\n const dir = path.resolve(outputDir);\r\n\r\n if (!fs.existsSync(dir)) {\r\n fs.mkdirSync(dir, { recursive: true });\r\n }\r\n\r\n const indexPath = path.join(dir, 'index.html');\r\n fs.writeFileSync(indexPath, html, 'utf-8');\r\n\r\n return indexPath;\r\n}\r\n","/**\r\n * visual 命令 - 截图比对、基线管理、差异分析\r\n */\r\n\r\nimport { Command } from 'commander';\r\nimport chalk from 'chalk';\r\nimport ora from 'ora';\r\nimport type { VisualOptions } from '../types/index.js';\r\nimport { loadConfig } from '../services/config.js';\r\nimport {\r\n compareDirectories,\r\n updateAllBaselines,\r\n cleanBaselines,\r\n cleanDiffs,\r\n listBaselines,\r\n listDiffs,\r\n} from '../services/visual.js';\r\nimport { runPlaywright } from '../runners/playwright-runner.js';\r\n\r\nexport function registerVisualCommand(program: Command): void {\r\n program\r\n .command('visual')\r\n .description('视觉回归测试 - 截图比对与基线管理')\r\n .argument('<action>', '操作类型 (test|approve|clean)')\r\n .option('--threshold <number>', '像素差异阈值 (0-1)', '0.1')\r\n .action(async (action: string, options: { threshold?: string; config?: string }) => {\r\n try {\r\n await executeVisual({\r\n action: action as VisualOptions['action'],\r\n threshold: options.threshold ? parseFloat(options.threshold) : undefined,\r\n config: options.config,\r\n });\r\n } catch (error) {\r\n console.error(chalk.red(`\\n ✗ ${error instanceof Error ? error.message : String(error)}\\n`));\r\n process.exit(1);\r\n }\r\n });\r\n}\r\n\r\nasync function executeVisual(\r\n options: { action: VisualOptions['action']; threshold?: number; config?: string },\r\n): Promise<void> {\r\n const config = await loadConfig(options.config);\r\n const threshold = options.threshold ?? config.visual.threshold;\r\n const baselineDir = config.visual.baselineDir;\r\n const diffDir = config.visual.diffDir;\r\n\r\n switch (options.action) {\r\n case 'test': {\r\n await runVisualTest(threshold, baselineDir, diffDir, config);\r\n break;\r\n }\r\n case 'approve': {\r\n await approveBaselines(baselineDir, diffDir);\r\n break;\r\n }\r\n case 'clean': {\r\n await cleanAll(baselineDir, diffDir);\r\n break;\r\n }\r\n default: {\r\n console.error(chalk.red(`\\n 未知操作: ${options.action}`));\r\n console.log(chalk.gray(' 可用操作: test, approve, clean\\n'));\r\n process.exit(1);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * 执行视觉回归测试\r\n */\r\nasync function runVisualTest(\r\n threshold: number,\r\n baselineDir: string,\r\n diffDir: string,\r\n config: import('../types/index.js').QATConfig,\r\n): Promise<void> {\r\n // 1. 先运行 Playwright 视觉测试生成截图\r\n const spinner = ora('正在运行视觉测试截图...').start();\r\n\r\n try {\r\n const result = await runPlaywright({\r\n type: 'visual',\r\n browsers: config.playwright.browsers,\r\n });\r\n\r\n if (result.status === 'failed') {\r\n spinner.warn('部分视觉测试截图失败');\r\n } else {\r\n spinner.succeed('视觉测试截图完成');\r\n }\r\n } catch (error) {\r\n // Playwright 未安装等情况,继续尝试比对已有的截图\r\n spinner.warn('Playwright 运行跳过,尝试比对已有截图');\r\n }\r\n\r\n // 2. 比对截图\r\n const compareSpinner = ora('正在比对截图...').start();\r\n\r\n // 当前截图目录(Playwright 输出)\r\n const currentDir = findCurrentScreenshotsDir(baselineDir);\r\n\r\n if (!currentDir) {\r\n compareSpinner.info('没有找到截图文件');\r\n console.log(chalk.gray('\\n 提示: 先运行 qat run -t visual 生成截图\\n'));\r\n return;\r\n }\r\n\r\n const results = compareDirectories(baselineDir, currentDir, diffDir, threshold);\r\n\r\n compareSpinner.succeed('截图比对完成');\r\n\r\n // 3. 显示结果\r\n displayVisualResults(results, threshold);\r\n\r\n // 设置退出码\r\n const hasFailures = results.some((r) => !r.passed);\r\n if (hasFailures) {\r\n process.exitCode = 1;\r\n }\r\n}\r\n\r\n/**\r\n * 查找当前截图目录\r\n */\r\nfunction findCurrentScreenshotsDir(baselineDir: string): string | null {\r\n // Playwright 默认截图输出位置\r\n const possibleDirs = [\r\n path.join(process.cwd(), 'test-results'),\r\n path.join(process.cwd(), 'tests', 'visual', 'current'),\r\n path.join(process.cwd(), baselineDir, '..', 'current'),\r\n ];\r\n\r\n for (const dir of possibleDirs) {\r\n if (fs.existsSync(dir)) {\r\n const pngs = findPngFiles(dir);\r\n if (pngs.length > 0) return dir;\r\n }\r\n }\r\n\r\n return null;\r\n}\r\n\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\n\r\n/**\r\n * 递归查找 PNG 文件\r\n */\r\nfunction findPngFiles(dir: string): string[] {\r\n const files: string[] = [];\r\n\r\n function walk(d: string): void {\r\n if (!fs.existsSync(d)) return;\r\n const entries = fs.readdirSync(d, { withFileTypes: true });\r\n for (const entry of entries) {\r\n const fullPath = path.join(d, entry.name);\r\n if (entry.isDirectory() && entry.name !== 'node_modules') {\r\n walk(fullPath);\r\n } else if (entry.name.endsWith('.png')) {\r\n files.push(fullPath);\r\n }\r\n }\r\n }\r\n\r\n walk(dir);\r\n return files;\r\n}\r\n\r\n/**\r\n * 显示视觉回归测试结果\r\n */\r\nfunction displayVisualResults(\r\n results: import('../services/visual.js').DiffResult[],\r\n threshold: number,\r\n): void {\r\n const passed = results.filter((r) => r.passed);\r\n const failed = results.filter((r) => !r.passed);\r\n\r\n console.log();\r\n console.log(chalk.cyan(' ─────────────────────────────'));\r\n\r\n if (failed.length === 0) {\r\n console.log(chalk.green(` ✓ 全部通过 (${results.length} 个截图比对)`));\r\n } else {\r\n console.log(chalk.red(` ✗ ${failed.length} 个截图存在差异`));\r\n }\r\n\r\n console.log();\r\n console.log(` ${chalk.white('阈值:')} ${(threshold * 100).toFixed(0)}%`);\r\n console.log(` ${chalk.green('通过:')} ${passed.length}`);\r\n console.log(` ${chalk.red('失败:')} ${failed.length}`);\r\n\r\n if (passed.length > 0) {\r\n console.log();\r\n console.log(chalk.green(' 通过的截图:'));\r\n for (const result of passed) {\r\n const name = path.basename(result.baselinePath);\r\n if (result.totalPixels > 0) {\r\n const diffPct = (result.diffRatio * 100).toFixed(2);\r\n console.log(chalk.green(` ✓ ${name} (差异: ${diffPct}%)`));\r\n } else {\r\n console.log(chalk.green(` ✓ ${name} (新建基线)`));\r\n }\r\n }\r\n }\r\n\r\n if (failed.length > 0) {\r\n console.log();\r\n console.log(chalk.red(' 失败的截图:'));\r\n for (const result of failed) {\r\n const name = path.basename(result.baselinePath);\r\n if (result.diffPixels === -1) {\r\n console.log(chalk.red(` ✗ ${name} (尺寸不匹配)`));\r\n } else {\r\n const diffPct = (result.diffRatio * 100).toFixed(2);\r\n console.log(chalk.red(` ✗ ${name} (差异: ${diffPct}%)`));\r\n }\r\n if (result.diffPath) {\r\n console.log(chalk.gray(` 差异图: ${path.relative(process.cwd(), result.diffPath)}`));\r\n }\r\n }\r\n\r\n console.log();\r\n console.log(chalk.yellow(' 提示: 运行 qat visual approve 更新基线'));\r\n }\r\n\r\n console.log(chalk.cyan(' ─────────────────────────────'));\r\n console.log();\r\n}\r\n\r\n/**\r\n * 批量批准基线更新\r\n */\r\nasync function approveBaselines(baselineDir: string, diffDir: string): Promise<void> {\r\n const spinner = ora('正在更新基线快照...').start();\r\n\r\n const currentDir = findCurrentScreenshotsDir(baselineDir);\r\n\r\n if (!currentDir) {\r\n spinner.info('没有找到当前截图');\r\n return;\r\n }\r\n\r\n const updated = updateAllBaselines(currentDir, baselineDir);\r\n\r\n if (updated.length === 0) {\r\n spinner.info('没有需要更新的基线');\r\n return;\r\n }\r\n\r\n // 同时清理差异图片\r\n cleanDiffs(diffDir);\r\n\r\n spinner.succeed(`已更新 ${updated.length} 个基线快照`);\r\n\r\n console.log();\r\n for (const file of updated) {\r\n console.log(chalk.green(` ✓ ${file}`));\r\n }\r\n console.log();\r\n}\r\n\r\n/**\r\n * 清理所有基线和差异图片\r\n */\r\nasync function cleanAll(baselineDir: string, diffDir: string): Promise<void> {\r\n const spinner = ora('正在清理基线快照...').start();\r\n\r\n const baselines = cleanBaselines(baselineDir);\r\n const diffs = cleanDiffs(diffDir);\r\n\r\n spinner.succeed('清理完成');\r\n\r\n console.log();\r\n console.log(chalk.white(' 已删除:'));\r\n console.log(` 基线: ${baselines} 个`);\r\n console.log(` 差异: ${diffs} 个`);\r\n console.log();\r\n}\r\n","/**\r\n * 视觉回归服务 - 截图比对、基线管理、差异图片生成\r\n */\r\n\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport pixelmatch from 'pixelmatch';\r\nimport { PNG } from 'pngjs';\r\n\r\n/** 比对结果 */\r\nexport interface DiffResult {\r\n /** 是否通过(差异在阈值内) */\r\n passed: boolean;\r\n /** 差异像素数 */\r\n diffPixels: number;\r\n /** 总像素数 */\r\n totalPixels: number;\r\n /** 差异比例 (0-1) */\r\n diffRatio: number;\r\n /** 基线图片路径 */\r\n baselinePath: string;\r\n /** 当前图片路径 */\r\n currentPath: string;\r\n /** 差异图片路径(仅在有差异时生成) */\r\n diffPath?: string;\r\n}\r\n\r\n/**\r\n * 比对两张图片\r\n */\r\nexport function compareImages(\r\n baselinePath: string,\r\n currentPath: string,\r\n diffOutputPath: string,\r\n threshold: number = 0.1,\r\n): DiffResult {\r\n // 读取基线图片\r\n if (!fs.existsSync(baselinePath)) {\r\n throw new Error(`基线图片不存在: ${baselinePath}`);\r\n }\r\n\r\n if (!fs.existsSync(currentPath)) {\r\n throw new Error(`当前图片不存在: ${currentPath}`);\r\n }\r\n\r\n const baseline = PNG.sync.read(fs.readFileSync(baselinePath));\r\n const current = PNG.sync.read(fs.readFileSync(currentPath));\r\n\r\n // 尺寸必须一致\r\n if (baseline.width !== current.width || baseline.height !== current.height) {\r\n return {\r\n passed: false,\r\n diffPixels: -1,\r\n totalPixels: baseline.width * baseline.height,\r\n diffRatio: 1,\r\n baselinePath,\r\n currentPath,\r\n diffPath: undefined,\r\n };\r\n }\r\n\r\n const { width, height } = baseline;\r\n const totalPixels = width * height;\r\n\r\n // 创建差异图片\r\n const diff = new PNG({ width, height });\r\n const diffPixels = pixelmatch(\r\n baseline.data,\r\n current.data,\r\n diff.data,\r\n width,\r\n height,\r\n { threshold: 0.1 }, // pixelmatch 自身阈值(颜色差异灵敏度)\r\n );\r\n\r\n const diffRatio = diffPixels / totalPixels;\r\n const passed = diffRatio <= threshold;\r\n\r\n // 生成差异图片(仅在有差异时)\r\n let diffPath: string | undefined;\r\n if (diffPixels > 0) {\r\n // 确保输出目录存在\r\n const diffDir = path.dirname(diffOutputPath);\r\n if (!fs.existsSync(diffDir)) {\r\n fs.mkdirSync(diffDir, { recursive: true });\r\n }\r\n fs.writeFileSync(diffOutputPath, PNG.sync.write(diff));\r\n diffPath = diffOutputPath;\r\n }\r\n\r\n return {\r\n passed,\r\n diffPixels,\r\n totalPixels,\r\n diffRatio,\r\n baselinePath,\r\n currentPath,\r\n diffPath,\r\n };\r\n}\r\n\r\n/**\r\n * 创建基线快照(如果不存在则从当前截图复制)\r\n */\r\nexport function createBaseline(\r\n currentPath: string,\r\n baselinePath: string,\r\n): string {\r\n if (!fs.existsSync(currentPath)) {\r\n throw new Error(`当前截图不存在: ${currentPath}`);\r\n }\r\n\r\n const baselineDir = path.dirname(baselinePath);\r\n if (!fs.existsSync(baselineDir)) {\r\n fs.mkdirSync(baselineDir, { recursive: true });\r\n }\r\n\r\n fs.copyFileSync(currentPath, baselinePath);\r\n return baselinePath;\r\n}\r\n\r\n/**\r\n * 更新基线(将当前截图替换为基线)\r\n */\r\nexport function updateBaseline(\r\n currentPath: string,\r\n baselinePath: string,\r\n): string {\r\n return createBaseline(currentPath, baselinePath);\r\n}\r\n\r\n/**\r\n * 批量更新基线(将 diff 目录下的所有当前截图更新为基线)\r\n */\r\nexport function updateAllBaselines(\r\n currentDir: string,\r\n baselineDir: string,\r\n): string[] {\r\n const updated: string[] = [];\r\n\r\n if (!fs.existsSync(currentDir)) {\r\n return updated;\r\n }\r\n\r\n const files = fs.readdirSync(currentDir).filter((f) => f.endsWith('.png'));\r\n\r\n for (const file of files) {\r\n const currentPath = path.join(currentDir, file);\r\n const baselinePath = path.join(baselineDir, file);\r\n\r\n const baselineDirAbs = path.dirname(baselinePath);\r\n if (!fs.existsSync(baselineDirAbs)) {\r\n fs.mkdirSync(baselineDirAbs, { recursive: true });\r\n }\r\n\r\n fs.copyFileSync(currentPath, baselinePath);\r\n updated.push(file);\r\n }\r\n\r\n return updated;\r\n}\r\n\r\n/**\r\n * 清理所有基线快照\r\n */\r\nexport function cleanBaselines(baselineDir: string): number {\r\n if (!fs.existsSync(baselineDir)) {\r\n return 0;\r\n }\r\n\r\n const files = fs.readdirSync(baselineDir).filter((f) => f.endsWith('.png'));\r\n let count = 0;\r\n\r\n for (const file of files) {\r\n fs.unlinkSync(path.join(baselineDir, file));\r\n count++;\r\n }\r\n\r\n return count;\r\n}\r\n\r\n/**\r\n * 清理差异图片\r\n */\r\nexport function cleanDiffs(diffDir: string): number {\r\n if (!fs.existsSync(diffDir)) {\r\n return 0;\r\n }\r\n\r\n const files = fs.readdirSync(diffDir).filter((f) => f.endsWith('.png'));\r\n let count = 0;\r\n\r\n for (const file of files) {\r\n fs.unlinkSync(path.join(diffDir, file));\r\n count++;\r\n }\r\n\r\n return count;\r\n}\r\n\r\n/**\r\n * 获取基线快照列表\r\n */\r\nexport function listBaselines(baselineDir: string): string[] {\r\n if (!fs.existsSync(baselineDir)) {\r\n return [];\r\n }\r\n\r\n return fs\r\n .readdirSync(baselineDir)\r\n .filter((f) => f.endsWith('.png'))\r\n .sort();\r\n}\r\n\r\n/**\r\n * 获取差异图片列表\r\n */\r\nexport function listDiffs(diffDir: string): string[] {\r\n if (!fs.existsSync(diffDir)) {\r\n return [];\r\n }\r\n\r\n return fs\r\n .readdirSync(diffDir)\r\n .filter((f) => f.endsWith('.png'))\r\n .sort();\r\n}\r\n\r\n/**\r\n * 批量比对目录中的所有截图\r\n */\r\nexport function compareDirectories(\r\n baselineDir: string,\r\n currentDir: string,\r\n diffDir: string,\r\n threshold: number = 0.1,\r\n): DiffResult[] {\r\n const results: DiffResult[] = [];\r\n\r\n if (!fs.existsSync(currentDir)) {\r\n return results;\r\n }\r\n\r\n const currentFiles = fs.readdirSync(currentDir).filter((f) => f.endsWith('.png'));\r\n\r\n for (const file of currentFiles) {\r\n const currentPath = path.join(currentDir, file);\r\n const baselinePath = path.join(baselineDir, file);\r\n const diffPath = path.join(diffDir, file);\r\n\r\n if (!fs.existsSync(baselinePath)) {\r\n // 无基线,自动创建\r\n createBaseline(currentPath, baselinePath);\r\n results.push({\r\n passed: true,\r\n diffPixels: 0,\r\n totalPixels: 0,\r\n diffRatio: 0,\r\n baselinePath,\r\n currentPath,\r\n diffPath: undefined,\r\n });\r\n continue;\r\n }\r\n\r\n try {\r\n const result = compareImages(baselinePath, currentPath, diffPath, threshold);\r\n results.push(result);\r\n } catch (error) {\r\n results.push({\r\n passed: false,\r\n diffPixels: -1,\r\n totalPixels: 0,\r\n diffRatio: 1,\r\n baselinePath,\r\n currentPath,\r\n diffPath: undefined,\r\n });\r\n }\r\n }\r\n\r\n return results;\r\n}\r\n","/**\r\n * setup 命令 - 一键安装测试所需的外部依赖\r\n */\r\n\r\nimport { Command } from 'commander';\r\nimport chalk from 'chalk';\r\nimport inquirer from 'inquirer';\r\nimport ora from 'ora';\r\nimport { execFile } from 'node:child_process';\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport type { SetupOptions, QATConfig } from '../types/index.js';\r\nimport { loadConfig } from '../services/config.js';\r\nimport { detectProject } from '../services/detector.js';\r\n\r\n/** 依赖分组定义 */\r\ninterface DependencyGroup {\r\n /** 分组名称 */\r\n name: string;\r\n /** 对应的配置项启用路径 */\r\n configKey: string;\r\n /** 需要安装的包列表 */\r\n packages: string[];\r\n /** 额外的安装后步骤 */\r\n postInstall?: string[];\r\n}\r\n\r\n/** 所有可选依赖分组 */\r\nconst DEPENDENCY_GROUPS: DependencyGroup[] = [\r\n {\r\n name: 'Vitest (单元/组件/API测试)',\r\n configKey: 'vitest',\r\n packages: ['vitest', '@vue/test-utils', 'happy-dom', '@vitest/coverage-v8'],\r\n },\r\n {\r\n name: 'Playwright (E2E/视觉回归测试)',\r\n configKey: 'playwright',\r\n packages: ['@playwright/test'],\r\n postInstall: ['npx playwright install --with-deps'],\r\n },\r\n {\r\n name: 'Lighthouse (性能测试)',\r\n configKey: 'lighthouse',\r\n packages: ['lighthouse'],\r\n },\r\n];\r\n\r\nexport function registerSetupCommand(program: Command): void {\r\n program\r\n .command('setup')\r\n .description('一键安装测试所需的依赖包')\r\n .option('-f, --force', '强制重新安装已有依赖')\r\n .option('--dry-run', '仅显示需要安装的依赖,不实际执行')\r\n .action(async (options: SetupOptions) => {\r\n try {\r\n await executeSetup(options);\r\n } catch (error) {\r\n console.error(chalk.red(`\\n ✗ ${error instanceof Error ? error.message : String(error)}\\n`));\r\n process.exit(1);\r\n }\r\n });\r\n}\r\n\r\nasync function executeSetup(options: SetupOptions): Promise<void> {\r\n console.log(chalk.cyan('\\n QAT 依赖安装器\\n'));\r\n\r\n // 1. 检测项目\r\n const projectInfo = detectProject();\r\n\r\n if (!fs.existsSync(path.join(process.cwd(), 'package.json'))) {\r\n throw new Error('未找到 package.json,请在项目根目录执行此命令');\r\n }\r\n\r\n // 2. 显示框架信息\r\n if (projectInfo.frameworkConfidence > 0) {\r\n console.log(chalk.white(` 检测到框架: ${chalk.cyan(projectInfo.frameworkDisplayName)}`));\r\n if (projectInfo.uiLibrary !== 'none') {\r\n console.log(chalk.white(` UI 组件库: ${chalk.cyan(projectInfo.uiLibrary)}`));\r\n }\r\n if (projectInfo.monorepo !== 'none') {\r\n console.log(chalk.white(` Monorepo: ${chalk.cyan(projectInfo.monorepo)}`));\r\n if (projectInfo.appDirs.length > 0) {\r\n console.log(chalk.white(` 子项目: ${chalk.gray(projectInfo.appDirs.join(', '))}`));\r\n }\r\n }\r\n console.log();\r\n }\r\n\r\n // 3. Monorepo 提示\r\n let installDir = process.cwd();\r\n if (projectInfo.monorepo !== 'none' && projectInfo.appDirs.length > 0) {\r\n const { chooseDir } = await inquirer.prompt([\r\n {\r\n type: 'list',\r\n name: 'chooseDir',\r\n message: '检测到 Monorepo 结构,请选择依赖安装位置:',\r\n choices: [\r\n { name: `项目根目录 (${projectInfo.packageManager} workspace 自动提升)`, value: 'root' },\r\n ...projectInfo.appDirs.map((d) => ({\r\n name: `${d}/`,\r\n value: d,\r\n })),\r\n ],\r\n default: 'root',\r\n },\r\n ]);\r\n if (chooseDir !== 'root') {\r\n installDir = path.join(process.cwd(), chooseDir);\r\n if (!fs.existsSync(path.join(installDir, 'package.json'))) {\r\n throw new Error(`${chooseDir} 下没有 package.json`);\r\n }\r\n }\r\n }\r\n\r\n // 4. 读取配置(如果存在)\r\n let config: Partial<QATConfig> | null = null;\r\n try {\r\n config = await loadConfig(options.config);\r\n } catch {\r\n // 配置文件不存在,根据项目检测结果推断\r\n }\r\n\r\n // 5. 根据框架调整依赖分组\r\n const groupsToInstall = determineGroups(config, projectInfo, options.force);\r\n\r\n if (groupsToInstall.length === 0) {\r\n console.log(chalk.green(' ✓ 所有依赖已安装,无需额外操作\\n'));\r\n return;\r\n }\r\n\r\n // 6. 交互选择\r\n const selectedGroups = await selectGroups(groupsToInstall, options.dryRun);\r\n\r\n if (selectedGroups.length === 0) {\r\n console.log(chalk.gray('\\n 已取消安装\\n'));\r\n return;\r\n }\r\n\r\n // 7. 确认安装\r\n const allPackages = selectedGroups.flatMap((g) => g.packages);\r\n const { confirmed } = await inquirer.prompt([\r\n {\r\n type: 'confirm',\r\n name: 'confirmed',\r\n message: `确认安装以下 ${allPackages.length} 个依赖?\\n${allPackages.map((p) => ` - ${p}`).join('\\n')}`,\r\n default: true,\r\n },\r\n ]);\r\n\r\n if (!confirmed) {\r\n console.log(chalk.gray('\\n 已取消安装\\n'));\r\n return;\r\n }\r\n\r\n // 8. Dry run 模式\r\n if (options.dryRun) {\r\n console.log(chalk.yellow('\\n [Dry Run] 以下命令将被执行:\\n'));\r\n const pm = getPackageManager(projectInfo.packageManager);\r\n const installCmd = pm === 'npm' ? 'npm install -D' : pm === 'yarn' ? 'yarn add -D' : pm === 'pnpm' ? 'pnpm add -D' : 'bun add -D';\r\n for (const group of selectedGroups) {\r\n const dirHint = installDir !== process.cwd() ? ` (在 ${path.relative(process.cwd(), installDir) || installDir})` : '';\r\n console.log(chalk.white(` ${installCmd} ${group.packages.join(' ')}${dirHint}`));\r\n if (group.postInstall) {\r\n for (const cmd of group.postInstall) {\r\n console.log(chalk.white(` ${cmd}`));\r\n }\r\n }\r\n }\r\n console.log();\r\n return;\r\n }\r\n\r\n // 9. 执行安装\r\n for (const group of selectedGroups) {\r\n await installGroup(group, projectInfo.packageManager, installDir);\r\n }\r\n\r\n // 10. 显示结果\r\n displaySetupResult(selectedGroups);\r\n}\r\n\r\n/**\r\n * 确定需要安装的依赖分组\r\n */\r\nfunction determineGroups(\r\n config: Partial<QATConfig> | null,\r\n projectInfo: ReturnType<typeof detectProject>,\r\n force: boolean,\r\n): DependencyGroup[] {\r\n const groups: DependencyGroup[] = [];\r\n\r\n for (const group of DEPENDENCY_GROUPS) {\r\n // 判断该功能是否启用\r\n const isConfigEnabled = isGroupEnabled(group.configKey, config);\r\n // 判断是否已安装\r\n const isInstalled = group.packages.every((pkg) =>\r\n projectInfo.dependencies.includes(pkg) || projectInfo.dependencies.includes(pkg.replace(/^@/, '')),\r\n );\r\n\r\n if (isConfigEnabled && (!isInstalled || force)) {\r\n groups.push(group);\r\n }\r\n }\r\n\r\n // 如果没有配置文件,所有分组都作为候选\r\n if (!config) {\r\n return DEPENDENCY_GROUPS;\r\n }\r\n\r\n return groups;\r\n}\r\n\r\n/**\r\n * 判断配置中某功能是否启用\r\n */\r\nfunction isGroupEnabled(configKey: string, config: Partial<QATConfig> | null): boolean {\r\n if (!config) return true; // 无配置时默认启用\r\n const section = config[configKey as keyof QATConfig];\r\n if (section && typeof section === 'object' && 'enabled' in section) {\r\n return (section as { enabled: boolean }).enabled;\r\n }\r\n return true;\r\n}\r\n\r\n/**\r\n * 交互选择要安装的依赖分组\r\n */\r\nasync function selectGroups(\r\n groups: DependencyGroup[],\r\n dryRun?: boolean,\r\n): Promise<DependencyGroup[]> {\r\n const { selected } = await inquirer.prompt([\r\n {\r\n type: 'checkbox',\r\n name: 'selected',\r\n message: '选择要安装的测试工具:',\r\n choices: groups.map((g) => ({\r\n name: g.name,\r\n value: g,\r\n checked: true,\r\n })),\r\n },\r\n ]);\r\n\r\n return selected;\r\n}\r\n\r\n/**\r\n * 安装一个依赖分组\r\n */\r\nasync function installGroup(\r\n group: DependencyGroup,\r\n packageManager: string,\r\n installDir: string,\r\n): Promise<void> {\r\n const pm = getPackageManager(packageManager);\r\n const spinner = ora(`正在安装 ${group.name}...`).start();\r\n\r\n try {\r\n // 安装 npm 包\r\n const installArgs = buildInstallArgs(pm, group.packages);\r\n await execCommand(pm, installArgs, installDir);\r\n\r\n // 执行安装后步骤(如 playwright install)\r\n if (group.postInstall) {\r\n for (const cmd of group.postInstall) {\r\n spinner.text = `正在执行 ${cmd}...`;\r\n const [command, ...args] = cmd.split(' ');\r\n await execCommand(command, args, installDir);\r\n }\r\n }\r\n\r\n spinner.succeed(`${group.name} 安装完成`);\r\n } catch (error) {\r\n spinner.fail(`${group.name} 安装失败`);\r\n throw new Error(\r\n `安装 ${group.name} 失败: ${error instanceof Error ? error.message : String(error)}`,\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * 构建安装命令参数\r\n */\r\nfunction buildInstallArgs(pm: string, packages: string[]): string[] {\r\n switch (pm) {\r\n case 'npm':\r\n return ['install', '-D', ...packages];\r\n case 'yarn':\r\n return ['add', '-D', ...packages];\r\n case 'pnpm':\r\n return ['add', '-D', ...packages];\r\n case 'bun':\r\n return ['add', '-D', ...packages];\r\n default:\r\n return ['install', '-D', ...packages];\r\n }\r\n}\r\n\r\n/**\r\n * 获取包管理器可执行文件名\r\n */\r\nfunction getPackageManager(pm: string): string {\r\n // Windows 上需要 .cmd 后缀\r\n if (process.platform === 'win32') {\r\n if (pm === 'npm') return 'npm.cmd';\r\n if (pm === 'yarn') return 'yarn.cmd';\r\n if (pm === 'pnpm') return 'pnpm.cmd';\r\n if (pm === 'bun') return 'bun.cmd';\r\n }\r\n return pm;\r\n}\r\n\r\n/**\r\n * 执行命令\r\n */\r\nfunction execCommand(command: string, args: string[], cwd?: string): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n execFile(command, args, {\r\n cwd: cwd || process.cwd(),\r\n env: { ...process.env },\r\n maxBuffer: 50 * 1024 * 1024,\r\n shell: true,\r\n }, (error, stdout, stderr) => {\r\n if (error) {\r\n reject(new Error(error.message + (stderr ? `\\n${stderr}` : '')));\r\n return;\r\n }\r\n resolve();\r\n });\r\n });\r\n}\r\n\r\n/**\r\n * 显示安装结果\r\n */\r\nfunction displaySetupResult(groups: DependencyGroup[]): void {\r\n console.log(chalk.green('\\n ✓ 依赖安装完成!\\n'));\r\n console.log(chalk.white(' 已安装:'));\r\n\r\n for (const group of groups) {\r\n console.log(chalk.cyan(`\\n ${group.name}`));\r\n for (const pkg of group.packages) {\r\n console.log(chalk.gray(` ✓ ${pkg}`));\r\n }\r\n if (group.postInstall) {\r\n for (const cmd of group.postInstall) {\r\n console.log(chalk.gray(` ✓ ${cmd}`));\r\n }\r\n }\r\n }\r\n\r\n console.log();\r\n console.log(chalk.cyan(' 下一步:'));\r\n console.log(chalk.gray(' 1. 运行 qat create 创建测试用例'));\r\n console.log(chalk.gray(' 2. 运行 qat run 执行测试'));\r\n console.log();\r\n}\r\n","/**\r\n * status 命令 - 显示 QAT 当前状态,包括 AI 模型信息\r\n */\r\n\r\nimport { Command } from 'commander';\r\nimport chalk from 'chalk';\r\nimport ora from 'ora';\r\nimport type { GlobalOptions } from '../types/index.js';\r\nimport {\r\n loadGlobalAIConfig,\r\n isGlobalAIConfigured,\r\n maskApiKey,\r\n getAIConfigPath,\r\n toAIConfig,\r\n} from '../services/global-config.js';\r\nimport { testAIConnection } from '../ai/provider.js';\r\nimport { loadConfig } from '../services/config.js';\r\nimport { detectProject } from '../services/detector.js';\r\n\r\nexport function registerStatusCommand(program: Command): void {\r\n program\r\n .command('status')\r\n .description('查看 QAT 状态 - AI 模型信息、项目配置')\r\n .action(async (options: GlobalOptions) => {\r\n try {\r\n await executeStatus(options);\r\n } catch (error) {\r\n console.error(chalk.red(`\\n ✗ ${error instanceof Error ? error.message : String(error)}\\n`));\r\n process.exit(1);\r\n }\r\n });\r\n}\r\n\r\nasync function executeStatus(_options: GlobalOptions): Promise<void> {\r\n // ─── AI 模型状态 ────────────────────────────────────\r\n console.log(chalk.cyan('\\n AI 模型状态'));\r\n console.log(chalk.gray(' ─────────────────────────────'));\r\n\r\n const globalAI = loadGlobalAIConfig();\r\n\r\n if (!globalAI) {\r\n console.log(chalk.yellow(' ✗ 未配置 AI 模型'));\r\n console.log(chalk.gray(' 运行 qat change 配置 AI 模型'));\r\n } else {\r\n console.log(` ${chalk.white('模型:')} ${chalk.green(globalAI.model)}`);\r\n console.log(` ${chalk.white('API URL:')} ${chalk.gray(globalAI.baseUrl)}`);\r\n console.log(` ${chalk.white('API Key:')} ${chalk.gray(maskApiKey(globalAI.apiKey))}`);\r\n console.log(` ${chalk.white('Provider:')} ${chalk.gray(globalAI.provider)}`);\r\n console.log(` ${chalk.white('配置文件:')} ${chalk.gray(getAIConfigPath())}`);\r\n\r\n // 连通性测试\r\n const testSpinner = ora(' 正在测试连通性...').start();\r\n try {\r\n const aiConfig = toAIConfig(globalAI);\r\n const result = await testAIConnection(aiConfig);\r\n if (result.ok) {\r\n testSpinner.succeed(` 连通正常 ${chalk.gray(`(${result.latencyMs}ms)`)}`);\r\n } else {\r\n testSpinner.fail(` 连通异常: ${result.message}`);\r\n }\r\n } catch (error) {\r\n testSpinner.fail(` 连通测试失败: ${error instanceof Error ? error.message : String(error)}`);\r\n }\r\n }\r\n\r\n // ─── 项目配置状态 ───────────────────────────────────\r\n console.log();\r\n console.log(chalk.cyan(' 项目配置'));\r\n console.log(chalk.gray(' ─────────────────────────────'));\r\n\r\n try {\r\n const config = await loadConfig();\r\n const projectInfo = detectProject();\r\n\r\n console.log(` ${chalk.white('框架:')} ${projectInfo.frameworkDisplayName}`);\r\n console.log(` ${chalk.white('源码目录:')} ${config.project.srcDir}`);\r\n console.log(` ${chalk.white('Vitest:')} ${config.vitest.enabled ? chalk.green('✓') : chalk.red('✗')} (${config.vitest.environment})`);\r\n console.log(` ${chalk.white('Playwright:')} ${config.playwright.enabled ? chalk.green('✓') : chalk.red('✗')} (${config.playwright.browsers.join(', ')})`);\r\n console.log(` ${chalk.white('Mock:')} ${config.mock.enabled ? chalk.green('✓') : chalk.red('✗')} (port ${config.mock.port})`);\r\n console.log(` ${chalk.white('Visual:')} ${config.visual.enabled ? chalk.green('✓') : chalk.red('✗')}`);\r\n console.log(` ${chalk.white('Lighthouse:')} ${config.lighthouse.enabled ? chalk.green('✓') : chalk.red('✗')}`);\r\n } catch {\r\n console.log(chalk.yellow(' ✗ 未找到项目配置 (运行 qat init 初始化)'));\r\n }\r\n\r\n console.log();\r\n}\r\n","/**\r\n * change 命令 - 更改 AI 模型配置\r\n * 修改全局 ~/.qat/ai.json,影响所有项目\r\n */\r\n\r\nimport { Command } from 'commander';\r\nimport chalk from 'chalk';\r\nimport inquirer from 'inquirer';\r\nimport ora from 'ora';\r\nimport type { GlobalOptions } from '../types/index.js';\r\nimport {\r\n loadGlobalAIConfig,\r\n saveGlobalAIConfig,\r\n isGlobalAIConfigured,\r\n maskApiKey,\r\n getAIConfigPath,\r\n toAIConfig,\r\n} from '../services/global-config.js';\r\nimport { testAIConnection } from '../ai/provider.js';\r\n\r\nexport function registerChangeCommand(program: Command): void {\r\n program\r\n .command('change')\r\n .description('更改 AI 模型配置 - 修改全局 AI 模型、API Key 等')\r\n .action(async (options: GlobalOptions) => {\r\n try {\r\n await executeChange(options);\r\n } catch (error) {\r\n console.error(chalk.red(`\\n ✗ ${error instanceof Error ? error.message : String(error)}\\n`));\r\n process.exit(1);\r\n }\r\n });\r\n}\r\n\r\nasync function executeChange(_options: GlobalOptions): Promise<void> {\r\n const current = loadGlobalAIConfig();\r\n\r\n if (current) {\r\n console.log(chalk.cyan('\\n 当前 AI 配置:'));\r\n console.log(` ${chalk.white('模型:')} ${chalk.green(current.model)}`);\r\n console.log(` ${chalk.white('API URL:')} ${chalk.gray(current.baseUrl)}`);\r\n console.log(` ${chalk.white('API Key:')} ${chalk.gray(maskApiKey(current.apiKey))}`);\r\n console.log();\r\n } else {\r\n console.log(chalk.yellow('\\n 当前未配置 AI 模型,请配置:\\n'));\r\n }\r\n\r\n // 引导用户配置\r\n const answers = await inquirer.prompt([\r\n {\r\n type: 'input',\r\n name: 'apiKey',\r\n message: 'API Key (Ollama 本地可留空):',\r\n default: current?.apiKey || '',\r\n },\r\n {\r\n type: 'input',\r\n name: 'baseUrl',\r\n message: 'API Base URL:',\r\n default: current?.baseUrl || 'https://api.deepseek.com/v1',\r\n validate: (input: string) => {\r\n if (!input.trim()) return 'Base URL 不能为空';\r\n if (!input.trim().startsWith('http')) return 'URL 必须以 http(s):// 开头';\r\n return true;\r\n },\r\n },\r\n {\r\n type: 'input',\r\n name: 'model',\r\n message: '模型名称:',\r\n default: current?.model || 'deepseek-chat',\r\n validate: (input: string) => {\r\n if (!input.trim()) return '模型名称不能为空';\r\n return true;\r\n },\r\n },\r\n ]);\r\n\r\n const newConfig = {\r\n provider: 'openai',\r\n apiKey: answers.apiKey?.trim() || '',\r\n baseUrl: answers.baseUrl?.trim() || 'https://api.deepseek.com/v1',\r\n model: answers.model?.trim() || 'deepseek-chat',\r\n };\r\n\r\n // 保存\r\n saveGlobalAIConfig(newConfig);\r\n console.log(chalk.green(`\\n ✓ AI 配置已保存`));\r\n console.log(chalk.gray(` ${getAIConfigPath()}`));\r\n console.log(` ${chalk.white('模型:')} ${chalk.green(newConfig.model)} @ ${chalk.gray(newConfig.baseUrl)}`);\r\n\r\n // 连通性测试\r\n const testSpinner = ora(' 正在测试连通性...').start();\r\n try {\r\n const aiConfig = toAIConfig(newConfig);\r\n const result = await testAIConnection(aiConfig);\r\n if (result.ok) {\r\n testSpinner.succeed(` AI 连通正常 ${chalk.gray(`(${newConfig.model}, ${result.latencyMs}ms)`)}`);\r\n } else {\r\n testSpinner.fail(` AI 连通异常: ${result.message}`);\r\n }\r\n } catch (error) {\r\n testSpinner.fail(` 连通测试失败: ${error instanceof Error ? error.message : String(error)}`);\r\n }\r\n\r\n console.log();\r\n}\r\n"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IA+EN,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAmIX,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAuBL,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":["chalk","chalk","ora","fs","path","fs","path","path","fs","fs","path","fs","path","path","fs","chalk","chalk","fs","path","path","fs","fs","path","path","fs","fs","path","path","program","chalk","ora","fs","chalk","inquirer","ora","fs","path","program","chalk","inquirer","ora","path","fs","chalk","inquirer","ora","fs","path","path","execFile","path","execFile","program","chalk","inquirer","ora","fs","path","chalk","ora","program","chalk","ora","chalk","ora","fs","path","fs","path","formatDuration","RESULTS_DIR","program","chalk","ora","path","fs","chalk","ora","fs","path","fs","path","fs","path","fs","path","program","chalk","ora","chalk","inquirer","ora","execFile","fs","path","program","chalk","fs","path","inquirer","ora","execFile","chalk","ora","program","chalk","ora","chalk","inquirer","ora","program","chalk","inquirer","ora","chalk"]}