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.
- package/bin/cli.js +14 -90
- package/package.json +1 -1
- package/src/commands/dev-from-issue.js +0 -4
- package/src/core/ai-client.js +1 -1
- package/src/pr-modules/ai/code-analyzer.js +42 -27
- package/src/pr-modules/core/workflow.js +54 -49
- package/src/commands/auto-dev.js +0 -256
- package/src/commands/generate-code.js +0 -115
- package/src/commands/plan-issue.js +0 -91
- package/src/commands/write-and-test.js +0 -469
- package/src/core/test-generator.js +0 -165
- package/src/core/test-runner.js +0 -132
package/src/core/test-runner.js
DELETED
|
@@ -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
|
-
}
|