ai-git-tools 2.0.65 → 2.0.66
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/package.json
CHANGED
|
@@ -108,13 +108,20 @@ export async function writeAndTestCommand(options = {}) {
|
|
|
108
108
|
|
|
109
109
|
// 自動從 state 檔讀取 file / issue(指令列將覆蓋)
|
|
110
110
|
const state = loadDevState();
|
|
111
|
-
|
|
111
|
+
let filePath = options.file || state?.filePath;
|
|
112
112
|
const issueNumber = options.issue || state?.issueNumber;
|
|
113
|
-
const fromState = !options.file && state?.filePath;
|
|
113
|
+
const fromState = !options.file && Boolean(state?.filePath);
|
|
114
114
|
const maxFixes = parseInt(options.maxFixes || '2', 10);
|
|
115
115
|
|
|
116
|
+
// 若仍無 filePath,改用 git 偵測最近異動的原始碼檔案
|
|
117
|
+
let fromGit = false;
|
|
116
118
|
if (!filePath) {
|
|
117
|
-
|
|
119
|
+
filePath = await detectChangedSourceFile(logger);
|
|
120
|
+
fromGit = Boolean(filePath);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!filePath) {
|
|
124
|
+
logger.error('偵測不到異動的原始碼,請用 --file 指定目標檔案。');
|
|
118
125
|
throw new Error('缺少原始碼路徑');
|
|
119
126
|
}
|
|
120
127
|
|
|
@@ -124,8 +131,8 @@ export async function writeAndTestCommand(options = {}) {
|
|
|
124
131
|
throw new Error(`找不到原始碼:${absoluteSourcePath}`);
|
|
125
132
|
}
|
|
126
133
|
|
|
127
|
-
// 自動從 state 讀取時,測試類型預設為 both
|
|
128
|
-
const resolvedTestTypes = resolveRequestedTestTypes(absoluteSourcePath, options.testType || (fromState ? 'both' : 'auto'));
|
|
134
|
+
// 自動從 state 或 git 讀取時,測試類型預設為 both
|
|
135
|
+
const resolvedTestTypes = resolveRequestedTestTypes(absoluteSourcePath, options.testType || (fromState || fromGit ? 'both' : 'auto'));
|
|
129
136
|
const testTargets = resolvedTestTypes.map((testType) => ({
|
|
130
137
|
testType,
|
|
131
138
|
testFilePath: deriveTestFilePath(absoluteSourcePath, testType),
|
|
@@ -134,6 +141,8 @@ export async function writeAndTestCommand(options = {}) {
|
|
|
134
141
|
logger.header('write-and-test');
|
|
135
142
|
if (fromState) {
|
|
136
143
|
logger.info('從上一步 dev-from-issue 自動讀取');
|
|
144
|
+
} else if (fromGit) {
|
|
145
|
+
logger.info('從 git 異動記錄自動偵測');
|
|
137
146
|
}
|
|
138
147
|
console.log(`原始碼 :${absoluteSourcePath}`);
|
|
139
148
|
console.log(`測試類型:${formatTestTypes(resolvedTestTypes)}`);
|
|
@@ -355,3 +364,62 @@ export function deriveTestFilePath(sourceFilePath, testType = 'unit') {
|
|
|
355
364
|
const suffix = testType === 'component' ? 'component' : 'unit';
|
|
356
365
|
return join(dir, '__tests__', `${name}.${suffix}.test${ext}`);
|
|
357
366
|
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* 透過 git 偵測最近異動的原始碼檔案
|
|
370
|
+
* 優先順序:git staged → git unstaged → git 最新一筆 commit
|
|
371
|
+
* 排除測試檔、設定檔、lock 檔
|
|
372
|
+
*/
|
|
373
|
+
async function detectChangedSourceFile(logger) {
|
|
374
|
+
const SOURCE_EXTENSIONS = /\.(js|jsx|ts|tsx|mjs|cjs)$/;
|
|
375
|
+
const EXCLUDED = /(__tests__|\.test\.|\.spec\.|\.config\.|node_modules|dist\/|\.lock$|package\.json)/;
|
|
376
|
+
|
|
377
|
+
function filterSourceFiles(files) {
|
|
378
|
+
return files
|
|
379
|
+
.split('\n')
|
|
380
|
+
.map((f) => f.trim().replace(/^[MADRCU?!\s]+/, ''))
|
|
381
|
+
.filter((f) => f && SOURCE_EXTENSIONS.test(f) && !EXCLUDED.test(f) && existsSync(resolve(process.cwd(), f)));
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
try {
|
|
385
|
+
// 1. staged 檔案
|
|
386
|
+
const staged = filterSourceFiles(
|
|
387
|
+
execSync('git diff --cached --name-only', { encoding: 'utf-8', stdio: 'pipe', cwd: process.cwd() })
|
|
388
|
+
);
|
|
389
|
+
if (staged.length > 0) {
|
|
390
|
+
logger.info(`偵測到 staged 異動:${staged[0]}`);
|
|
391
|
+
return staged[0];
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// 2. unstaged 工作區異動
|
|
395
|
+
const unstaged = filterSourceFiles(
|
|
396
|
+
execSync('git diff --name-only', { encoding: 'utf-8', stdio: 'pipe', cwd: process.cwd() })
|
|
397
|
+
);
|
|
398
|
+
if (unstaged.length > 0) {
|
|
399
|
+
logger.info(`偵測到工作區異動:${unstaged[0]}`);
|
|
400
|
+
return unstaged[0];
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// 3. untracked 新增檔案
|
|
404
|
+
const untracked = filterSourceFiles(
|
|
405
|
+
execSync('git ls-files --others --exclude-standard', { encoding: 'utf-8', stdio: 'pipe', cwd: process.cwd() })
|
|
406
|
+
);
|
|
407
|
+
if (untracked.length > 0) {
|
|
408
|
+
logger.info(`偵測到新增檔案:${untracked[0]}`);
|
|
409
|
+
return untracked[0];
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// 4. 最新 commit 的異動
|
|
413
|
+
const lastCommit = filterSourceFiles(
|
|
414
|
+
execSync('git diff-tree --no-commit-id -r --name-only HEAD', { encoding: 'utf-8', stdio: 'pipe', cwd: process.cwd() })
|
|
415
|
+
);
|
|
416
|
+
if (lastCommit.length > 0) {
|
|
417
|
+
logger.info(`偵測到最後 commit 異動:${lastCommit[0]}`);
|
|
418
|
+
return lastCommit[0];
|
|
419
|
+
}
|
|
420
|
+
} catch {
|
|
421
|
+
// git 取得失敗(非 git 專案等),直接回傳 null
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return null;
|
|
425
|
+
}
|