ai-worktool 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,484 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.createJestConfigFile = createJestConfigFile;
40
+ exports.runJestTests = runJestTests;
41
+ exports.getFailedTests = getFailedTests;
42
+ exports.parseLcovForUncoveredLines = parseLcovForUncoveredLines;
43
+ exports.readCoverageSummary = readCoverageSummary;
44
+ exports.generateUncoveredLinesReport = generateUncoveredLinesReport;
45
+ exports.getFailedTestSummaries = getFailedTestSummaries;
46
+ exports.findTestErrorDetails = findTestErrorDetails;
47
+ exports.clearJsonReportCache = clearJsonReportCache;
48
+ exports.openLcovReport = openLcovReport;
49
+ const child_process_1 = require("child_process");
50
+ const fs = __importStar(require("fs/promises"));
51
+ const path = __importStar(require("path"));
52
+ const util_1 = __importDefault(require("util"));
53
+ // 转换exec为Promise形式,便于async/await使用
54
+ const execAsync = util_1.default.promisify(child_process_1.exec);
55
+ /**
56
+ * 创建 Jest 配置文件
57
+ *
58
+ * 该函数会在指定路径创建一个 Jest 配置文件(jest.config.js),
59
+ * 包含基本的测试配置选项。如果文件已存在,将不会覆盖原有文件。
60
+ *
61
+ * @param configPath 配置文件保存路径(包含文件名,如:./jest.config.js)
62
+ * @param options Jest 配置选项,用于自定义测试配置
63
+ * @returns 返回一个 Promise,当配置文件创建成功时解析为文件路径,若文件已存在则返回 null
64
+ * @throws 当写入文件时发生错误时抛出异常
65
+ */
66
+ async function createJestConfigFile(configPath, options = {}) {
67
+ // 检查文件是否已存在
68
+ try {
69
+ await fs.access(configPath);
70
+ // 文件已存在,返回 null 表示未创建新文件
71
+ return null;
72
+ }
73
+ catch {
74
+ // 文件不存在,继续创建
75
+ }
76
+ options = typeof options === 'string' ? JSON.parse(options) : options;
77
+ // 合并默认配置和用户配置
78
+ const defaultConfig = {
79
+ testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'],
80
+ moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json', 'node'],
81
+ transform: {
82
+ '^.+\\.(ts|tsx)$': 'ts-jest',
83
+ '^.+\\.(js|jsx)$': 'babel-jest'
84
+ },
85
+ collectCoverage: true,
86
+ // 指定需要统计覆盖率的源代码路径(关键配置)
87
+ collectCoverageFrom: [
88
+ // 匹配所有源代码文件(根据项目目录结构调整)
89
+ 'src/**/*.{js,jsx,ts,tsx}', // 例如:src 目录下所有 JS/TS 文件
90
+ '!src/**/*.d.ts', // 排除类型定义文件(可选)
91
+ '!src/mocks/**', // 排除 mock 文件(可选)
92
+ '!src/**/index.{js,ts}', // 排除入口文件(可选,根据需求调整)
93
+ '!**/node_modules/**', // 排除 node_modules
94
+ '!**/vendor/**' // 排除第三方依赖(如有)
95
+ ],
96
+ coverageDirectory: 'coverage',
97
+ coverageReporters: ['json-summary', 'lcov', 'text', 'clover'],
98
+ testPathIgnorePatterns: ['/node_modules/']
99
+ };
100
+ const mergedConfig = { ...defaultConfig, ...options };
101
+ // 生成配置文件内容
102
+ const configContent = `module.exports = ${JSON.stringify(mergedConfig, null, 2)};\n`;
103
+ // 确保目录存在
104
+ const dirPath = path.dirname(configPath);
105
+ await fs.mkdir(dirPath, { recursive: true });
106
+ // 写入配置文件
107
+ await fs.writeFile(configPath, configContent, 'utf-8');
108
+ return configPath;
109
+ }
110
+ async function getTestResultsState(projectRoot, coverageDir = 'coverage') {
111
+ coverageDir = coverageDir || 'coverage';
112
+ const testResultsPath = path.join(projectRoot, coverageDir, 'test-results.json');
113
+ if (await fileExists(testResultsPath)) {
114
+ const testResults = JSON.parse(await fs.readFile(testResultsPath, 'utf-8'));
115
+ if (!testResults.testResults) {
116
+ return null;
117
+ }
118
+ return fs.stat(testResultsPath);
119
+ }
120
+ else {
121
+ return null;
122
+ }
123
+ }
124
+ /**
125
+ * 执行 Jest 单元测试并生成覆盖率报告
126
+ * @param projectRoot 项目根目录
127
+ * @param jestConfigPath Jest 配置文件路径(可选)
128
+ */
129
+ async function runJestTests(projectRoot, coverageDir = 'coverage', jestConfigPath) {
130
+ coverageDir = coverageDir || 'coverage';
131
+ if (!(await fileExists(path.join(projectRoot, 'package.json')))) {
132
+ throw new Error('当前目录下不存在 package.json 文件,请确保当前目录为项目根目录。');
133
+ }
134
+ return new Promise((resolve, reject) => {
135
+ const now = Date.now();
136
+ const testResultsFile = path.join(projectRoot, coverageDir, 'test-results.json');
137
+ const jestCommand = jestConfigPath
138
+ ? `npx jest --coverage --coverageReporters="json-summary" --coverageReporters="lcov" --json --outputFile="${testResultsFile}" --config ${jestConfigPath}`
139
+ : `npx jest --coverage --coverageReporters="json-summary" --coverageReporters="lcov" --json --outputFile="${testResultsFile}"`;
140
+ const childProcess = (0, child_process_1.exec)(jestCommand, {
141
+ cwd: projectRoot,
142
+ encoding: 'utf-8'
143
+ });
144
+ let stdout = '';
145
+ let stderr = '';
146
+ childProcess.stdout?.on('data', (data) => {
147
+ stdout += data;
148
+ });
149
+ childProcess.stderr?.on('data', (data) => {
150
+ stderr += data;
151
+ });
152
+ childProcess.on('close', async (code) => {
153
+ // 单测用例失败返回成功
154
+ if (code !== 0) {
155
+ // const error = new Error(`Jest测试失败,退出码: ${code}\n${stderr}`);
156
+ // console.error(`执行 Jest 测试失败: ${error.message}`);
157
+ const state = await getTestResultsState(projectRoot, coverageDir);
158
+ // 即使测试失败,仍然返回部分结果
159
+ resolve({
160
+ testResultsFile,
161
+ success: state?.mtimeMs >= now,
162
+ noTestsFound: stderr.includes('No tests found, exiting with code 1'),
163
+ exitCode: code,
164
+ stdout,
165
+ stderr
166
+ });
167
+ }
168
+ else {
169
+ resolve({
170
+ testResultsFile,
171
+ success: true,
172
+ exitCode: code,
173
+ stdout,
174
+ stderr
175
+ });
176
+ }
177
+ });
178
+ childProcess.on('error', (err) => {
179
+ console.error(`执行 Jest 测试失败: ${err.message}`);
180
+ reject(err);
181
+ });
182
+ });
183
+ }
184
+ // 辅助函数:异步检查文件是否存在
185
+ async function fileExists(path) {
186
+ try {
187
+ await fs.access(path);
188
+ return true;
189
+ }
190
+ catch {
191
+ return false;
192
+ }
193
+ }
194
+ async function getFailedTests(projectRoot, coverageDir = 'coverage') {
195
+ coverageDir = coverageDir || 'coverage';
196
+ const testResultsPath = path.join(projectRoot, coverageDir, 'test-results.json');
197
+ let testResults = null;
198
+ if (!(await fileExists(testResultsPath))) {
199
+ throw new Error(`测试结果文件不存在: ${testResultsPath}`);
200
+ }
201
+ // 尝试读取测试结果文件
202
+ try {
203
+ testResults = JSON.parse(await fs.readFile(testResultsPath, 'utf-8'));
204
+ }
205
+ catch (err) {
206
+ console.error(`读取测试结果文件失败: ${err.message}`);
207
+ }
208
+ // 提取失败的测试用例信息
209
+ const failedTests = extractFailedTests(testResults);
210
+ return failedTests;
211
+ }
212
+ /**
213
+ * 解析 LCOV 格式的覆盖率报告,提取未覆盖的行号
214
+ * @param lcovContent LCOV 格式的覆盖率报告内容
215
+ */
216
+ function parseLcovForUncoveredLines(lcovContent) {
217
+ const uncoveredLines = {};
218
+ let currentFile = null;
219
+ lcovContent.split('\n').forEach((line) => {
220
+ if (line.startsWith('SF:')) {
221
+ // 记录当前文件路径
222
+ currentFile = line.substring(3).trim();
223
+ uncoveredLines[currentFile] = [];
224
+ }
225
+ else if (line.startsWith('DA:') && currentFile) {
226
+ // 解析行覆盖率数据 (格式: DA:行号,执行次数)
227
+ const [lineNumberStr, executionCountStr] = line.substring(3).split(',');
228
+ const lineNumber = parseInt(lineNumberStr, 10);
229
+ const executionCount = parseInt(executionCountStr, 10);
230
+ // 如果执行次数为0,表示该行未被覆盖
231
+ if (executionCount === 0) {
232
+ uncoveredLines[currentFile].push(lineNumber);
233
+ }
234
+ }
235
+ });
236
+ return uncoveredLines;
237
+ }
238
+ /**
239
+ * 读取覆盖率摘要信息
240
+ * @param summaryPath 覆盖率摘要文件路径
241
+ */
242
+ async function readCoverageSummary(summaryPath) {
243
+ try {
244
+ if (!fileExists(summaryPath)) {
245
+ throw new Error(`覆盖率摘要文件不存在: ${summaryPath}`);
246
+ }
247
+ return JSON.parse(await fs.readFile(summaryPath, 'utf-8'));
248
+ }
249
+ catch (error) {
250
+ console.error(`读取覆盖率摘要失败: ${error.message}`);
251
+ throw error;
252
+ }
253
+ }
254
+ /**
255
+ * 生成未覆盖代码行报告
256
+ * @param projectRoot 项目根目录
257
+ * @param coverage 报告文件夹,默认 'coverage'
258
+ */
259
+ async function generateUncoveredLinesReport(projectRoot, coverage = 'coverage') {
260
+ const coverageDir = path.join(projectRoot, coverage);
261
+ const lcovInfoPath = path.join(coverageDir, 'lcov.info');
262
+ const summaryPath = path.join(coverageDir, 'coverage-summary.json');
263
+ // 检查覆盖率文件是否存在
264
+ if (!fileExists(lcovInfoPath) || !fileExists(summaryPath)) {
265
+ throw new Error('未找到覆盖率报告文件,请先执行测试并生成覆盖率报告');
266
+ }
267
+ // 读取覆盖率数据
268
+ const lcovContent = await fs.readFile(lcovInfoPath, 'utf-8');
269
+ const summary = await readCoverageSummary(summaryPath);
270
+ // 解析未覆盖的行号
271
+ const uncoveredLines = parseLcovForUncoveredLines(lcovContent);
272
+ // 构建报告对象
273
+ const report = {
274
+ total: summary.total,
275
+ files: {}
276
+ };
277
+ // 为每个文件添加覆盖率和未覆盖行信息
278
+ Object.keys(summary).forEach((filePath) => {
279
+ if (filePath === 'total') {
280
+ return;
281
+ }
282
+ // 相对路径处理
283
+ const relativePath = path.relative(projectRoot, filePath);
284
+ report.files[filePath] = {
285
+ coverage: summary[filePath],
286
+ uncoveredLines: uncoveredLines[relativePath] || []
287
+ };
288
+ });
289
+ return report;
290
+ }
291
+ /**
292
+ * 从 Jest 测试结果中提取失败的测试信息
293
+ * @param testResults Jest 测试结果对象
294
+ * @param projectRoot 项目根目录
295
+ */
296
+ function extractFailedTests(testResults) {
297
+ if (!testResults || !testResults.testResults) {
298
+ return [];
299
+ }
300
+ const failedTests = [];
301
+ testResults.testResults.forEach((suite) => {
302
+ const testFilePath = suite.name;
303
+ // 单测断言失败
304
+ suite.assertionResults.forEach((test) => {
305
+ if (test.status === 'failed') {
306
+ const failureMessages = test.failureMessages || [];
307
+ const errorLocations = extractErrorLocations(failureMessages);
308
+ failedTests.push({
309
+ testFilePath,
310
+ testName: test.fullName,
311
+ errorMessages: failureMessages,
312
+ errorLocations
313
+ });
314
+ }
315
+ });
316
+ // 或者文件编译报错
317
+ if (suite.status === 'failed' && suite.message) {
318
+ const errorMessages = [suite.message];
319
+ const errorLocations = extractErrorLocations(errorMessages);
320
+ failedTests.push({
321
+ testFilePath,
322
+ testName: '',
323
+ errorMessages,
324
+ errorLocations
325
+ });
326
+ }
327
+ });
328
+ return failedTests;
329
+ }
330
+ /**
331
+ * 从错误消息中提取错误位置信息
332
+ * @param errorMessages 错误消息数组
333
+ * @param projectRoot 项目根目录
334
+ */
335
+ function extractErrorLocations(errorMessages) {
336
+ const locations = [];
337
+ const locationRegex = /\(([^)]+):(\d+):(\d+)\)/g;
338
+ errorMessages.forEach((message) => {
339
+ let match;
340
+ while ((match = locationRegex.exec(message)) !== null) {
341
+ const [, filePath, lineStr, columnStr] = match;
342
+ const line = parseInt(lineStr, 10);
343
+ const column = parseInt(columnStr, 10);
344
+ locations.push({
345
+ filePath,
346
+ line,
347
+ column
348
+ });
349
+ }
350
+ });
351
+ return locations;
352
+ }
353
+ /**
354
+ * 缓存已加载的测试输出内容,避免重复读取文件
355
+ */
356
+ const outputCache = new Map();
357
+ /**
358
+ * 加载并解析Jest JSON测试报告
359
+ * @param jsonFilePath Jest JSON输出文件的路径
360
+ * @returns 一个Promise,解析为JestJsonReport对象
361
+ * @throws 如果文件不存在、无法读取或JSON解析失败,则抛出错误
362
+ */
363
+ async function loadAndParseJsonReport(jsonFilePath) {
364
+ const resolvedPath = path.resolve(jsonFilePath);
365
+ // 检查缓存
366
+ if (outputCache.has(resolvedPath)) {
367
+ return outputCache.get(resolvedPath);
368
+ }
369
+ try {
370
+ // 读取文件内容
371
+ const fileContent = await fs.readFile(resolvedPath, 'utf8');
372
+ // 解析JSON
373
+ const report = JSON.parse(fileContent);
374
+ // 存入缓存
375
+ outputCache.set(resolvedPath, report);
376
+ return report;
377
+ }
378
+ catch (error) {
379
+ if (error.name === 'SyntaxError') {
380
+ throw new Error(`JSON解析失败: ${error.message}`);
381
+ }
382
+ else {
383
+ throw new Error(`无法读取测试输出文件: ${error.message}`);
384
+ }
385
+ }
386
+ }
387
+ /**
388
+ * 提取所有失败的测试用例概要信息
389
+ * @param jsonFilePath Jest JSON输出文件的路径
390
+ * @returns 一个Promise,解析为失败的测试用例概要数组
391
+ */
392
+ async function getFailedTestSummaries(jsonFilePath) {
393
+ const report = await loadAndParseJsonReport(jsonFilePath);
394
+ const summaries = [];
395
+ // 遍历所有测试套件
396
+ for (const testSuite of report.testResults) {
397
+ // 遍历套件中的所有测试用例
398
+ for (const testCase of testSuite.assertionResults) {
399
+ // 只处理失败的测试用例
400
+ if (testCase.status === 'failed' && testCase.failureDetails && testCase.failureDetails.length > 0) {
401
+ summaries.push({
402
+ testName: testCase.fullName,
403
+ errorMessage: testCase.failureMessages[0].split('\n')[0],
404
+ filePath: testSuite.name
405
+ });
406
+ }
407
+ }
408
+ }
409
+ return summaries;
410
+ }
411
+ /**
412
+ * 查找指定测试用例的详细错误信息
413
+ * @param jsonFilePath Jest JSON输出文件的路径
414
+ * @param testName 要查找的测试用例名称(可以是部分匹配)
415
+ * @returns 一个Promise,解析为匹配的测试用例详细信息数组
416
+ */
417
+ async function findTestErrorDetails(jsonFilePath, testName) {
418
+ const report = await loadAndParseJsonReport(jsonFilePath);
419
+ let details = '';
420
+ // 遍历所有测试套件
421
+ for (const testSuite of report.testResults) {
422
+ // 遍历套件中的所有测试用例
423
+ for (const testCase of testSuite.assertionResults) {
424
+ // 只处理失败的测试用例,并且名称包含搜索词
425
+ if (testCase.status === 'failed' &&
426
+ testCase.failureDetails &&
427
+ testCase.failureDetails.length > 0 &&
428
+ (testCase.fullName.includes(testName) || testCase.title.includes(testName))) {
429
+ return testCase.failureMessages[0];
430
+ }
431
+ }
432
+ }
433
+ return details;
434
+ }
435
+ /**
436
+ * 清除JSON报告缓存
437
+ * @param jsonFilePath 可选,指定要清除缓存的文件路径;如果不提供,则清除所有缓存
438
+ */
439
+ function clearJsonReportCache(jsonFilePath) {
440
+ if (jsonFilePath) {
441
+ const resolvedPath = path.resolve(jsonFilePath);
442
+ outputCache.delete(resolvedPath);
443
+ }
444
+ else {
445
+ outputCache.clear();
446
+ }
447
+ }
448
+ /**
449
+ * 打开浏览器访问本地的lcov-report HTML文件
450
+ * @param reportPath lcov-report目录的路径,默认为项目根目录下的coverage/lcov-report
451
+ * @returns Promise<void>
452
+ */
453
+ async function openLcovReport(projectRoot, coverageDir = 'coverage', reportDir = 'lcov-report') {
454
+ try {
455
+ // 构建index.html的完整路径
456
+ const indexPath = path.join(projectRoot, coverageDir, reportDir, 'index.html');
457
+ // 检查文件是否存在
458
+ if (!fileExists(indexPath)) {
459
+ throw new Error(`LCOV报告文件不存在: ${indexPath}\n请先运行测试生成覆盖率报告`);
460
+ }
461
+ // 获取文件的绝对路径,并转换为URL格式
462
+ const absolutePath = path.resolve(indexPath);
463
+ const fileUrl = `file://${absolutePath}`;
464
+ console.log(`正在打开覆盖率报告: ${fileUrl}`);
465
+ // 根据不同操作系统,使用相应的命令打开浏览器
466
+ switch (process.platform) {
467
+ case 'darwin': // macOS
468
+ await execAsync(`open "${fileUrl}"`);
469
+ break;
470
+ case 'win32': // Windows
471
+ await execAsync(`start "" "${fileUrl}"`);
472
+ break;
473
+ default: // Linux及其他类Unix系统
474
+ await execAsync(`xdg-open "${fileUrl}"`);
475
+ break;
476
+ }
477
+ console.log('覆盖率报告已在默认浏览器中打开');
478
+ }
479
+ catch (error) {
480
+ console.error('打开覆盖率报告时出错:', error instanceof Error ? error.message : String(error));
481
+ throw error; // 重新抛出错误,允许调用者处理
482
+ }
483
+ }
484
+ //# sourceMappingURL=jest.js.map
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.createMochaConfigFile = createMochaConfigFile;
37
+ exports.runMochaTests = runMochaTests;
38
+ const child_process_1 = require("child_process");
39
+ const fs = __importStar(require("fs/promises"));
40
+ const path = __importStar(require("path"));
41
+ /**
42
+ * 创建 Mocha 配置文件
43
+ */
44
+ async function createMochaConfigFile(configPath, options = {}) {
45
+ try {
46
+ await fs.access(configPath);
47
+ return null;
48
+ }
49
+ catch {
50
+ // 继续创建文件
51
+ }
52
+ options = typeof options === 'string' ? JSON.parse(options) : options;
53
+ const defaultConfig = {
54
+ spec: ['test/**/*.{test,spec}.{js,ts}'],
55
+ exclude: ['node_modules/**'],
56
+ extension: ['js', 'ts'],
57
+ require: ['ts-node/register'], // 支持 TypeScript
58
+ reporter: 'spec'
59
+ };
60
+ const mergedConfig = { ...defaultConfig, ...options };
61
+ const configContent = `module.exports = ${JSON.stringify(mergedConfig, null, 2)};\n`;
62
+ const dirPath = path.dirname(configPath);
63
+ await fs.mkdir(dirPath, { recursive: true });
64
+ await fs.writeFile(configPath, configContent, 'utf-8');
65
+ return configPath;
66
+ }
67
+ /**
68
+ * 执行 Mocha 测试(需要配合 nyc 生成覆盖率)
69
+ */
70
+ async function runMochaTests(projectRoot, coverageDir = 'coverage', configPath) {
71
+ coverageDir = coverageDir || 'coverage';
72
+ return new Promise((resolve, reject) => {
73
+ const testResultsFile = path.join(projectRoot, coverageDir, 'test-results.json');
74
+ const mochaArgs = [
75
+ configPath ? `--config ${configPath}` : '',
76
+ `--reporter json --reporter-options output="${testResultsFile}"`
77
+ ].filter(Boolean).join(' ');
78
+ // 使用 nyc 生成覆盖率
79
+ const mochaCommand = `npx nyc --reporter=lcov --reporter=text --report-dir=${coverageDir} mocha ${mochaArgs}`;
80
+ const childProcess = (0, child_process_1.exec)(mochaCommand, { cwd: projectRoot, encoding: 'utf-8' });
81
+ let stdout = '';
82
+ let stderr = '';
83
+ childProcess.stdout?.on('data', (data) => (stdout += data));
84
+ childProcess.stderr?.on('data', (data) => (stderr += data));
85
+ childProcess.on('close', (code) => {
86
+ resolve({
87
+ testResultsFile,
88
+ success: code === 0,
89
+ noTestsFound: stderr.includes('No test files found'),
90
+ exitCode: code,
91
+ stdout,
92
+ stderr
93
+ });
94
+ });
95
+ childProcess.on('error', (err) => reject(err));
96
+ });
97
+ }
98
+ async function fileExists(path) {
99
+ try {
100
+ await fs.access(path);
101
+ return true;
102
+ }
103
+ catch {
104
+ return false;
105
+ }
106
+ }
107
+ //# sourceMappingURL=mocha.js.map