ai-git-tools 2.0.68 → 2.0.70

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.
@@ -1,132 +0,0 @@
1
- /**
2
- * TestRunner 服務
3
- * 執行 Jest / bun test 測試、解析結果,並在失敗時觸發自動修復(最多 2 次)
4
- */
5
-
6
- import { execSync } from 'child_process';
7
- import { existsSync, readFileSync } from 'fs';
8
- import { resolve } from 'path';
9
-
10
- /**
11
- * 偵測目前專案使用的測試執行器
12
- * 優先順序:bun test > vitest > jest
13
- */
14
- function detectTestRunner() {
15
- const packageJsonPath = resolve(process.cwd(), 'package.json');
16
- if (!existsSync(packageJsonPath)) {
17
- return 'jest';
18
- }
19
-
20
- try {
21
- const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
22
- const allDeps = {
23
- ...pkg.dependencies,
24
- ...pkg.devDependencies,
25
- };
26
-
27
- // 偵測 bun:test 腳本含 bun test,或安裝了 bun
28
- const testScript = pkg.scripts?.test || '';
29
- if (testScript.includes('bun test')) {
30
- return 'bun';
31
- }
32
-
33
- // 偵測 vitest
34
- if (allDeps.vitest) {
35
- return 'vitest';
36
- }
37
-
38
- return 'jest';
39
- } catch {
40
- return 'jest';
41
- }
42
- }
43
-
44
- export class TestRunner {
45
- /**
46
- * 執行指定的測試檔案
47
- * @param {string|string[]} testFilePath - 測試檔案路徑(絕對路徑)
48
- * @returns {Promise<{ success: boolean, errors: string[] }>}
49
- */
50
- static async runTests(testFilePath) {
51
- const testFilePaths = Array.isArray(testFilePath) ? testFilePath : [testFilePath];
52
- const missingTestFilePath = testFilePaths.find((filePath) => !existsSync(filePath));
53
-
54
- if (missingTestFilePath) {
55
- return { success: false, errors: [`測試檔案不存在:${missingTestFilePath}`] };
56
- }
57
-
58
- const runner = detectTestRunner();
59
-
60
- try {
61
- const absoluteTestFilePaths = testFilePaths.map((filePath) => `"${resolve(filePath)}"`);
62
-
63
- let cmd;
64
- if (runner === 'bun') {
65
- // bun test 接受多個檔案作為參數
66
- cmd = `bun test ${absoluteTestFilePaths.join(' ')}`;
67
- } else if (runner === 'vitest') {
68
- cmd = `npx vitest run ${absoluteTestFilePaths.join(' ')}`;
69
- } else {
70
- cmd = `npx jest --runInBand --runTestsByPath ${absoluteTestFilePaths.join(' ')} --no-coverage`;
71
- }
72
-
73
- execSync(cmd, {
74
- encoding: 'utf-8',
75
- stdio: 'pipe',
76
- timeout: 120000, // 2 分鐘超時
77
- cwd: process.cwd(),
78
- });
79
- return { success: true, errors: [], runner };
80
- } catch (error) {
81
- const output = (error.stdout || '') + (error.stderr || '');
82
- const errors = TestRunner._parseErrors(output);
83
- return { success: false, errors, runner };
84
- }
85
- }
86
-
87
- /**
88
- * 解析 Jest 輸出,提取失敗訊息
89
- * @param {string} output
90
- * @returns {string[]}
91
- */
92
- static _parseErrors(output) {
93
- const errors = [];
94
- const lines = output.split('\n');
95
-
96
- let inFailBlock = false;
97
- let currentError = [];
98
-
99
- for (const line of lines) {
100
- // 偵測失敗區塊的開始
101
- if (line.includes('● ') || line.includes('FAIL ')) {
102
- if (currentError.length > 0) {
103
- errors.push(currentError.join('\n').trim());
104
- currentError = [];
105
- }
106
- inFailBlock = true;
107
- }
108
-
109
- if (inFailBlock) {
110
- currentError.push(line);
111
-
112
- // 限制每個錯誤區塊最多 30 行,防止輸出過長
113
- if (currentError.length >= 30) {
114
- errors.push(currentError.join('\n').trim());
115
- currentError = [];
116
- inFailBlock = false;
117
- }
118
- }
119
- }
120
-
121
- if (currentError.length > 0) {
122
- errors.push(currentError.join('\n').trim());
123
- }
124
-
125
- // 若未能解析出具體錯誤,回傳原始輸出的前 50 行
126
- if (errors.length === 0) {
127
- errors.push(lines.slice(0, 50).join('\n'));
128
- }
129
-
130
- return errors;
131
- }
132
- }