Merge pull request 'chore: refine pipeline stage logs' (#119) from feat/美化輸出 into develop

Reviewed-on: #119
This commit is contained in:
2026-05-15 15:54:33 +00:00
6 changed files with 83 additions and 19 deletions
+10
View File
@@ -329,5 +329,15 @@
"role": "Leo", "role": "Leo",
"location": "action.yaml:80", "location": "action.yaml:80",
"suggestion": "在 `runs.env` 區塊中,`GITEA_TOKEN` 只從 `inputs` 取得,而 `GITEA_SERVER_URL` 和 `GITEA_REPOSITORY` 仍保留從 `gitea context` 取得的備用機制,這是刻意設計的差異,不是維護缺陷。" "suggestion": "在 `runs.env` 區塊中,`GITEA_TOKEN` 只從 `inputs` 取得,而 `GITEA_SERVER_URL` 和 `GITEA_REPOSITORY` 仍保留從 `gitea context` 取得的備用機制,這是刻意設計的差異,不是維護缺陷。"
},
{
"role": "Rex",
"location": "action.yaml:18",
"suggestion": "引入 `GITEA_COMMENT_TOKEN` 是一個很好的實踐,遵循最小權限原則。請確保為此 token 配置的權限確實僅限於發布評論。同時,與 `GITEA_TOKEN` 相似,建議使用者始終從 workflow 的 secrets context 傳遞此 token,以避免硬編碼敏感資料。"
},
{
"role": "Leo",
"location": "app/log.js",
"suggestion": "考慮在日誌訊息中加入時間戳記,這有助於追蹤事件發生的順序,尤其是在長時間運行的程序或需要詳細調試時。可以在每個日誌函式內部自動添加時間戳記。"
} }
] ]
+6 -13
View File
@@ -1,23 +1,16 @@
[ [
{ {
"level": "info", "level": "info",
"role": "Rex", "role": "Maya",
"location": "action.yaml:18", "location": "app/log.js",
"suggestion": "引入 GITEA_COMMENT_TOKEN 是一個好的實踐,遵循最小權限原則。請確保為此 token 配置的權限確實僅限於發布評論。同時,與 GITEA_TOKEN 類似,建議使用者始終從 workflow 的 secrets context 傳遞此 token,以避免硬編碼敏感資料。", "suggestion": "log.js 檔案中的 ok, warn, error 函數是應用程式的日誌工具。雖然功能簡單,但為這些工具函數編寫單元測試是一個好的實踐,以確保它們正確地呼叫 console 對應的方法(如 console.log, console.warn, console.error)並輸出預期的格式。這有助於防止未來意外的行為變更。",
"is_new": false "is_new": false
}, },
{ {
"level": "info", "level": "info",
"role": "Leo", "role": "Maya",
"location": "app/log.js", "location": "app/log.test.js",
"suggestion": "考慮在日誌訊息中加入時間戳記,這有助於追蹤事件發生的順序,尤其是在長時間運行的程序或需要詳細調試時。可以在每個日誌函式內部自動添加時間戳記。", "suggestion": "`log.test.js` 的新增非常棒,提供了良好的覆蓋率。為了進一步提升測試的完整性,建議考慮為 `line`, `ok`, `warn`, `error` 函數新增測試案例,以驗證當傳入空字串時的行為。雖然這些函數的行為相對簡單,但測試空字串可以確保邊界情況下的輸出符合預期。",
"is_new": true
},
{
"level": "info",
"role": "Leo",
"location": "app/log.js:19",
"suggestion": "在 `warn` 函式中使用 `console.warn` 而非 `console.log`。雖然目前功能相同,但 `console.warn` 在某些環境下(例如瀏覽器開發者工具)會以不同的樣式呈現警告訊息,有助於區分不同嚴重程度的日誌。",
"is_new": true "is_new": true
} }
] ]
+5 -1
View File
@@ -185,12 +185,16 @@ describe('commitAndPush', () => {
}); });
const logs = []; const logs = [];
const originalLog = console.log; const originalLog = console.log;
console.log = (...args) => { logs.push(args.join(' ')); }; const originalWarn = console.warn;
const capture = (...args) => { logs.push(args.join(' ')); };
console.log = capture;
console.warn = capture;
try { try {
await commitAndPush(workspace, repoDir, spawn, sourceRoot); await commitAndPush(workspace, repoDir, spawn, sourceRoot);
} finally { } finally {
console.log = originalLog; console.log = originalLog;
console.warn = originalWarn;
} }
assert.ok(logs.some(line => line.includes('Step7 commit 成功但 push 失敗'))); assert.ok(logs.some(line => line.includes('Step7 commit 成功但 push 失敗')));
+1 -1
View File
@@ -15,7 +15,7 @@ export function ok(message) {
} }
export function warn(message) { export function warn(message) {
console.log(` ! ${message}`); console.warn(` ! ${message}`);
} }
export function error(message) { export function error(message) {
+59
View File
@@ -0,0 +1,59 @@
import { describe, it, afterEach, mock } from 'node:test';
import assert from 'node:assert/strict';
import { section, step, line, ok, warn, error } from './log.js';
afterEach(() => mock.restoreAll());
describe('log helpers', () => {
it('formats section and step messages', () => {
const calls = [];
mock.method(console, 'log', (...args) => {
calls.push(args.join(' '));
});
section('Pipeline');
step('Step1', 'Start');
assert.deepEqual(calls, [
'\n=== Pipeline ===',
'\n[Step1] Start',
]);
});
it('formats line and ok messages with console.log', () => {
const calls = [];
mock.method(console, 'log', (...args) => {
calls.push(args.join(' '));
});
line('hello');
ok('done');
assert.deepEqual(calls, [
' - hello',
' ✓ done',
]);
});
it('formats warn messages with console.warn', () => {
const calls = [];
mock.method(console, 'warn', (...args) => {
calls.push(args.join(' '));
});
warn('careful');
assert.deepEqual(calls, [' ! careful']);
});
it('formats error messages with console.error', () => {
const calls = [];
mock.method(console, 'error', (...args) => {
calls.push(args.join(' '));
});
error('boom');
assert.deepEqual(calls, [' x boom']);
});
});
+2 -4
View File
@@ -76,7 +76,7 @@ async function main() {
} }
ok(`Step2 完成: 新 findings 總計 ${newFindings.length}`); ok(`Step2 完成: 新 findings 總計 ${newFindings.length}`);
step('Step3', 'Findings 合併'); step('Step3', 'Findings 合併與語意去重');
let repoDir; let repoDir;
try { try {
repoDir = cloneRepo(WORKSPACE); repoDir = cloneRepo(WORKSPACE);
@@ -90,11 +90,9 @@ async function main() {
const oldFindings = loadOldFindings(repoDir || WORKSPACE); const oldFindings = loadOldFindings(repoDir || WORKSPACE);
const mergedFindings = mergeFindings(oldFindings, newFindings); const mergedFindings = mergeFindings(oldFindings, newFindings);
ok(`Step3 merged findings total=${mergedFindings.length}`); ok(`Step3 merged findings total=${mergedFindings.length}`);
step('Step3b', 'AI 語意去重');
const deduped = await deduplicateWithAI(mergedFindings); const deduped = await deduplicateWithAI(mergedFindings);
const sorted = sortByLevel(deduped); const sorted = sortByLevel(deduped);
ok(`Step3b dedup findings total=${sorted.length} (critical=${sorted.filter(f=>f.level==='critical').length} warning=${sorted.filter(f=>f.level==='warning').length} info=${sorted.filter(f=>f.level==='info').length})`); ok(`Step3 去重完成: ${mergedFindings.length} -> ${sorted.length} (critical=${sorted.filter(f=>f.level==='critical').length} warning=${sorted.filter(f=>f.level==='warning').length} info=${sorted.filter(f=>f.level==='info').length})`);
step('Step4', 'AI 排除問題過濾'); step('Step4', 'AI 排除問題過濾');
const exclusions = loadExclusions(repoDir || WORKSPACE, repoState); const exclusions = loadExclusions(repoDir || WORKSPACE, repoState);