From c37b33f397223e674640c468241afcac39d5e2ce Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 1 Jul 2026 09:07:05 +0800 Subject: [PATCH] =?UTF-8?q?fix(release-cleanup):=20=E6=97=A5=E8=AA=8C?= =?UTF-8?q?=E6=94=B9=E7=82=BA=20[=E9=9A=8E=E6=AE=B5][=E7=AD=89=E7=B4=9A][?= =?UTF-8?q?=E6=99=82=E9=96=93]=20=E6=A0=BC=E5=BC=8F=E4=B8=A6=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E5=8D=80=E6=AE=B5=E5=88=86=E9=9A=94=E7=B7=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 stage() 設定目前階段、formatLog() 共用組裝,log 統一為 [階段][等級][時間]: 訊息 - 階段對應 參數檢查/取得舊版本/刪除舊版本;移除 separator()/section() 的 ==== / ---- 分隔區塊 - 行為、控制流、exit code 不變;fail 仍走 stderr、其餘走 stdout Co-Authored-By: Claude Opus 4.8 (1M context) --- app/index.js | 69 +++++++++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/app/index.js b/app/index.js index 83dc448..eca265b 100644 --- a/app/index.js +++ b/app/index.js @@ -2,26 +2,21 @@ 'use strict'; /** - * 在 stdout 輸出一條視覺分隔線(前後各一個換行 + 50 個等號), - * 用於在 log 中切割不同區段。 - * - * @returns {void} + * 目前流程階段名稱,作為每行 log 的 `[階段]` 前綴使用; + * 為空字串時省略整個 `[階段]` 區塊。 + * @type {string} */ -function separator() { - process.stdout.write('\n==================================================\n'); -} +let currentStage = ''; /** - * 輸出一個區段標題區塊:上緣等號分隔線、標題文字、下緣虛線, - * 用於在 log 中標示流程進入新階段。 + * 設定目前流程階段名稱,之後輸出的 log 會以 `[階段]` 前綴標示所屬階段。 + * 傳入空字串可清除階段前綴。 * - * @param {string} title 區段標題文字。 + * @param {string} name 階段名稱(顯示於每行 log 開頭的 `[階段]` 區塊)。 * @returns {void} */ -function section(title) { - separator(); - process.stdout.write(`${title}\n`); - process.stdout.write('--------------------------------------------------\n'); +function stage(name) { + currentStage = name; } /** @@ -38,45 +33,59 @@ function timestamp() { } /** - * 以 `[INF][時間]:` 前綴將一般資訊訊息輸出到 stdout(時間為 Asia/Taipei)。 + * 依統一格式組出一行 log:`[階段?][等級][時間]: 訊息`。 + * 階段取自目前 currentStage(為空字串時省略整個 `[階段]` 區塊); + * 時間為 Asia/Taipei 的 `yyyy/MM/dd HH:mm:ss`。 + * + * @param {string} level 等級碼(INF/WRN/ERR)。 + * @param {string} msg 訊息內容。 + * @returns {string} 組裝後的 log 字串。 + */ +function formatLog(level, msg) { + const stagePart = currentStage ? `[${currentStage}]` : ''; + return `${stagePart}[${level}][${timestamp()}]: ${msg}`; +} + +/** + * 以 `[階段?][INF][時間]:` 前綴將一般資訊訊息輸出到 stdout(時間為 Asia/Taipei)。 * * @param {string} msg 要輸出的訊息內容。 * @returns {void} */ function info(msg) { - console.log(`[INF][${timestamp()}]: ${msg}`); + console.log(formatLog('INF', msg)); } /** - * 以 `[INF][時間]:` 前綴將成功訊息輸出到 stdout(時間為 Asia/Taipei)。 + * 以 `[階段?][INF][時間]:` 前綴將成功訊息輸出到 stdout(時間為 Asia/Taipei)。 * 規範等級碼無「成功」,故等級採 INF,成功語意保留於訊息文字。 * * @param {string} msg 要輸出的訊息內容。 * @returns {void} */ function success(msg) { - console.log(`[INF][${timestamp()}]: ${msg}`); + console.log(formatLog('INF', msg)); } /** - * 以 `[WRN][時間]:` 前綴將警告訊息輸出到 stdout(時間為 Asia/Taipei)。 + * 以 `[階段?][WRN][時間]:` 前綴將警告訊息輸出到 stdout(時間為 Asia/Taipei)。 * * @param {string} msg 要輸出的訊息內容。 * @returns {void} */ function warn(msg) { - console.log(`[WRN][${timestamp()}]: ${msg}`); + console.log(formatLog('WRN', msg)); } /** - * 以 `[ERR][時間]:` 前綴將錯誤訊息輸出到 stderr(時間為 Asia/Taipei)。 + * 以 `[階段?][ERR][時間]:` 前綴將錯誤訊息輸出到 stderr(時間為 Asia/Taipei)。 * 僅負責輸出,不會結束程序(是否離開由呼叫端決定)。 * * @param {string} msg 要輸出的錯誤訊息內容。 * @returns {void} */ function fail(msg) { - console.error(`[ERR][${timestamp()}]: ${msg}`); + console.error(formatLog('ERR', msg)); } /** @@ -92,8 +101,8 @@ function isEmptyOrNull(value) { } /** - * 檢查必填值。先以 [INFO] 印出 name=value, - * 若值被判定為空(見 isEmptyOrNull),印出 [ERR] 並以 exit code 1 結束程序。 + * 檢查必填值。先以 info([INF])印出 name=value, + * 若值被判定為空(見 isEmptyOrNull),以 fail([ERR])輸出並以 exit code 1 結束程序。 * * @param {string} name 參數名稱(用於 log 與錯誤訊息)。 * @param {*} value 要驗證的參數值。 @@ -157,7 +166,9 @@ async function main() { const GITEA_TOKEN = process.env.GITEA_TOKEN; const KEEP_COUNT_RAW = process.env.KEEP_COUNT; - section('參數檢查'); + // 於容器啟動橫幅與後續 log 之間留一空行,方便在 CI log 中閱讀 + process.stdout.write('\n'); + stage('參數檢查'); requireValue('GITEA_SERVER_URL', GITEA_SERVER_URL); requireValue('GITEA_REPOSITORY', GITEA_REPOSITORY); requireValue('KEEP_COUNT', KEEP_COUNT_RAW); @@ -225,7 +236,7 @@ async function main() { const releaseApiUrl = `${GITEA_SERVER_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases`; - section('取得成品資訊'); + stage('取得舊版本'); info(`GET ${releaseApiUrl}`); let releases = await fetchAllPages(releaseApiUrl); @@ -251,7 +262,7 @@ async function main() { if (releasesToDelete.length === 0) { success('沒有需要清理的舊版本成品'); } else { - section('刪除舊版本成品'); + stage('刪除舊版本'); info(`DELETE_RELEASE_COUNT=${releasesToDelete.length}`); for (const release of releasesToDelete) { @@ -276,7 +287,7 @@ async function main() { } } - section('刪除未指定 release 的 tag'); + stage('刪除舊版本'); // 重新取得 release 清單,得到刪除舊版本後仍指定 tag 的成品 const currentReleases = await fetchAllPages(releaseApiUrl); @@ -311,8 +322,6 @@ async function main() { fail(`刪除 tag 失敗: ${tagName}, HTTP ${code}`); } } - - separator(); } main().catch((err) => {