diff --git a/.gitea/workflows/cd.yaml b/.gitea/workflows/cd.yaml index 30b1952..f6b4b47 100644 --- a/.gitea/workflows/cd.yaml +++ b/.gitea/workflows/cd.yaml @@ -1,12 +1,32 @@ +# 用途:本檔為 Gitea CD(持續部署)工作流程設定。 +# 當 master 分支有 push 時自動觸發,使用公司 Gitea 上的 +# composite action「release-tag-version」對建置成品進行釋出與版本標註(tag)。 +# 更新日期(台灣時區 Asia/Taipei):2026/06/30 16:44:09 + +# 工作流程名稱,顯示於 Gitea Actions 介面,用以辨識此 CD 流程 name: CD +# 定義觸發此工作流程的事件 on: + # 當有程式碼 push(推送)至指定分支時觸發 push: + # 限定僅特定分支的 push 才觸發 branches: + # 僅 master 分支(正式發布主線)push 時才執行此 CD 流程 - master +# 定義此工作流程包含的所有 job(工作) jobs: + # job 識別代號:release-tag-version(釋出並標註版本) release-tag-version: + # job 的顯示名稱,呈現於 Gitea Actions 執行畫面 name: Release Tag Version + # 指定此 job 執行所使用的 runner 標籤(ubuntu runner) runs-on: ubuntu + # 定義此 job 依序執行的步驟清單 steps: + # 步驟名稱:釋出並標註成品版本 - name: 釋出並標註成品版本 + # 引用公司 Gitea 上的 composite action「release-tag-version」執行釋出與打 tag。 + # 版本(@ 之後)由 Gitea 變數 vars.ACTION_RELEASE_TAG_VERSION 動態決定, + # 便於集中管理所引用 action 的版本,不需修改本檔即可切換版本。 + # 需人工確認:此 action 之具體釋出/標註行為與所需 secrets/權限,須參閱該 composite action 定義。 uses: https://gitea.jsc.idv.tw/composite-actions/release-tag-version@${{ vars.ACTION_RELEASE_TAG_VERSION }} diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index b9d4eed..a577c47 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -1,19 +1,57 @@ +# =================================================================== +# 用途:Gitea CI workflow,於 Pull Request 開啟(opened)或同步更新(synchronize)時觸發(master 分支除外)。 +# 流程先呼叫 composite action `release-tag-version`(is_beta: true)標註一個 beta 成品版本並輸出版本號, +# 再以該 beta 版本(@v${version})執行本專案的 `release-cleanup` action,對自身成品做清理(dogfooding 自我清理驗證)。 +# 更新日期(台灣時區 Asia/Taipei):2026/06/30 18:19:59 +# =================================================================== + +# workflow 名稱,顯示於 Gitea Actions 介面 name: CI +# 觸發事件設定 on: + # 針對 Pull Request 事件觸發 pull_request: + # 目標分支過濾:忽略以下分支(即 master 不觸發本 workflow) branches-ignore: + # 排除 master 分支,避免對正式主線的 PR 執行本流程 - master + # 僅在 PR 被「開啟(opened)」或「推送新 commit 同步(synchronize)」時觸發 types: [opened, synchronize] +# 定義所有 jobs jobs: - ai-code-review: - name: AI Code Review + # 第一個 job: 釋出並標註成品版本,並將版本號往外拋給後續 job 使用。 + release-tag-version: + # job 顯示名稱。 + name: Release Tag Version + # 指定執行環境 (runner) 標籤為 ubuntu。 runs-on: ubuntu - permissions: - contents: write - pull-requests: write - issues: write + # 宣告此 job 的輸出,供後續 needs 此 job 的其他 job 取用。 + outputs: + # 將下方 id 為 release-tag-version 的 step 所輸出的 version,設為此 job 的對外 version 輸出 (關鍵點: 透過 outputs 把標註出的版本往外拋)。 + version: ${{ steps.release-tag-version.outputs.version }} + # 此 job 依序執行的步驟。 steps: - - name: AI 程式碼審查 by OpenCode - uses: https://gitea.jsc.idv.tw/composite-actions/opencode-code-review@${{ vars.ACTION_OPENCODE_CODE_REVIEW_VERSION }} + # 步驟: 呼叫 composite action 進行釋出並標註成品版本。 + - name: 釋出並標註成品版本 + # 設定此 step 的 id,讓上方 outputs 可用 steps.release-tag-version.outputs.version 取得其輸出。 + id: release-tag-version + # 引用 release-tag-version composite action,版本以變數 vars.ACTION_RELEASE_TAG_VERSION 釘選 (集中於 repo/org 變數管理,便於統一升版)。 + uses: https://gitea.jsc.idv.tw/composite-actions/release-tag-version@${{ vars.ACTION_RELEASE_TAG_VERSION }} + # 傳遞給該 action 的輸入參數。 with: - comment_token: ${{ secrets.COMMENT_TOKEN }} + # is_beta 設為 true 表示走 beta 標版流程 (標註的是 beta 成品版本,而非正式版)。 + is_beta: true + # 第二個 job: 以前一個 job 標註出的 beta 版本執行清理成品 action (dogfooding 自我清理)。 + release-cleanup: + # job 顯示名稱。 + name: Release Cleanup + # 指定執行環境 (runner) 標籤為 ubuntu。 + runs-on: ubuntu + # 宣告相依於 release-tag-version job,確保其先完成且可取用其 outputs.version。 + needs: release-tag-version + # 此 job 依序執行的步驟。 + steps: + # 步驟: 呼叫 release-cleanup action 清理舊成品。 + - name: 清理舊成品 + # 引用本專案 release-cleanup action,版本釘選為前一個 job 輸出的 beta 版本 v${version} (以剛標出的 beta 版自我驗證清理流程)。 + uses: https://gitea.jsc.idv.tw/docker-actions/release-cleanup@v${{ needs.release-tag-version.outputs.version }} diff --git a/README.md b/README.md index c229e66..e1b77a6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Release Cleanup -> 更新時間:2026/06/30 12:02:55(台灣時區 Asia/Taipei) +> 更新時間:2026/07/01 09:00:53(台灣時區 Asia/Taipei) 清理舊成品的 **Gitea / GitHub docker container action**。依保留數量清理 repo 的舊 release,並刪除沒有對應 release 的 tag;正式版與 beta 版各自獨立保留指定數量。 @@ -10,12 +10,31 @@ ## 行為總覽 1. **參數檢查**:驗證 `GITEA_SERVER_URL`、`GITEA_REPOSITORY`、`KEEP_COUNT`(缺值或 `KEEP_COUNT` 非整數時以 exit code 1 結束)。 -2. **取得成品資訊**:取得所有 release,依建立時間由新到舊排序,並以 tag 名稱含 `-beta.` 與否分為正式版與 beta 版。 -3. **刪除舊版本成品**:正式版與 beta 版**各自**保留最新 `KEEP_COUNT` 筆,刪除其餘 release(單筆刪除失敗只記錄錯誤,不中斷)。 -4. **刪除未指定 release 的 tag**:重新取得 release 後,刪除所有未被任何 release 指定的 tag。 +2. **取得舊版本**:取得所有 release,依建立時間由新到舊排序,並以 tag 名稱含 `-beta.` 與否分為正式版與 beta 版。 +3. **刪除舊版本**:正式版與 beta 版**各自**保留最新 `KEEP_COUNT` 筆,刪除其餘 release(單筆刪除失敗只記錄錯誤,不中斷);接著重新取得 release 後,刪除所有未被任何 release 指定的 tag。 `GITEA_TOKEN` 為空時以匿名身分呼叫 API。取得分頁失敗或參數不合法則直接以 exit code 1 結束程序。 +## 日誌輸出格式 + +所有等級式輸出統一為 `[{階段}][{等級}][{時間}]: {訊息}`: + +- `階段`:目前流程階段(`參數檢查` / `取得舊版本` / `刪除舊版本`),由 `stage()` 設定;未設定時省略整個 `[階段]` 區塊。 +- `等級`:`INF`(含成功訊息)/ `WRN` / `ERR`。 +- `時間`:Asia/Taipei 的 `yyyy/MM/dd HH:mm:ss`。 +- `fail` 走 stderr,其餘走 stdout。 + +範例: + +```text +[參數檢查][INF][2026/07/01 09:00:53]: GITEA_SERVER_URL=https://gitea.jsc.idv.tw +[取得舊版本][INF][2026/07/01 09:00:53]: RELEASE_COUNT=3 +[取得舊版本][INF][2026/07/01 09:00:53]: 沒有需要清理的舊版本成品 +[刪除舊版本][ERR][2026/07/01 09:00:53]: 刪除 tag 失敗: v0.0.1, HTTP 500 +``` + +> 容器啟動橫幅(名稱/用途/更新時間)由 `entrypoint.sh` 輸出,不帶等級與階段前綴。 + ## Action 輸入與環境變數 | 名稱 | 來源 | 必填 | 預設 | 說明 | @@ -44,7 +63,7 @@ jobs: | 專案名稱 | 專案描述 | | --- | --- | -| [release-cleanup](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app) | 清理 Gitea repo 舊 release 與孤兒 tag 的 docker container action;正式版與 beta 版各自保留指定數量 | +| [release-cleanup](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app) | 清理 Gitea repo 舊 release 與孤兒 tag 的 docker container action;正式版與 beta 版各自保留指定數量,並以統一的 `[階段][等級][時間]` 日誌輸出處理過程 | ### 參考專案 @@ -64,84 +83,103 @@ jobs: ### release-cleanup +> 說明:本專案為單一 Node 模組(`app/index.js`),下列為模組內可文件化的頂層函式(不含 `main` 內部的巢狀私有函式 `fetchAllPages` / `deleteUrl`)。 + | 功能名稱 | 功能描述 | | --- | --- | -| [separator](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L10) | [在 stdout 輸出視覺分隔線](#separator) | -| [section](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L21) | [輸出區段標題區塊](#section) | -| [info](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L33) | [以 \[INFO\] 前綴輸出資訊訊息](#info) | -| [success](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L43) | [以 \[OK\] 前綴輸出成功訊息](#success) | -| [warn](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L53) | [以 \[WARN\] 前綴輸出警告訊息](#warn) | -| [fail](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L64) | [以 \[ERR\] 前綴輸出錯誤訊息到 stderr](#fail) | -| [isEmptyOrNull](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L76) | [判斷值是否視為空 / 未設定](#isemptyornull) | -| [requireValue](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L89) | [檢查必填值,空值時結束程序](#requirevalue) | -| [requireInteger](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L106) | [驗證非負整數,不符時結束程序](#requireinteger) | -| [isBeta](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L120) | [判斷名稱是否為 beta 版本](#isbeta) | -| [main](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L140) | [Action 主流程:清理舊 release 與孤兒 tag](#main) | +| [stage](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L18) | [設定目前流程階段名稱(log 的 \[階段\] 前綴)](#stage) | +| [timestamp](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L29) | [取得 Asia/Taipei 時間字串](#timestamp) | +| [formatLog](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L44) | [組出統一格式的 log 字串](#formatlog) | +| [info](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L55) | [以 \[INF\] 等級輸出資訊訊息](#info) | +| [success](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L66) | [以 \[INF\] 等級輸出成功訊息](#success) | +| [warn](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L76) | [以 \[WRN\] 等級輸出警告訊息](#warn) | +| [fail](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L87) | [以 \[ERR\] 等級輸出錯誤訊息到 stderr](#fail) | +| [isEmptyOrNull](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L99) | [判斷值是否視為空 / 未設定](#isemptyornull) | +| [requireValue](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L112) | [檢查必填值,空值時結束程序](#requirevalue) | +| [requireInteger](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L129) | [驗證非負整數,不符時結束程序](#requireinteger) | +| [isBeta](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L143) | [判斷名稱是否為 beta 版本](#isbeta) | +| [main](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L163) | [Action 主流程:清理舊 release 與孤兒 tag](#main) | ## 使用範例 - -### separator + +### stage -在 stdout 輸出一條前後帶換行、由 50 個等號組成的視覺分隔線,用於在 log 中切割區段。無參數、無回傳值。 +設定目前流程階段名稱,之後 `info`/`success`/`warn`/`fail` 輸出的每行 log 會以 `[階段]` 前綴標示所屬階段;傳入空字串可清除前綴。 ```js -separator(); -// 輸出: -// -// ================================================== +stage('取得舊版本'); +info('RELEASE_COUNT=3'); +// 輸出:[取得舊版本][INF][2026/07/01 09:00:53]: RELEASE_COUNT=3 ``` - -### section +> 前置條件:無。結果:更新模組層級的目前階段字串;本身不輸出任何內容。 -輸出一個區段標題區塊:上緣等號分隔線、標題文字、下緣虛線,用於標示流程進入新階段。 + +### timestamp + +取得目前 **Asia/Taipei** 時區的時間字串,格式為 `yyyy/MM/dd HH:mm:ss`。使用 Node 內建 `Intl`(`toLocaleString('sv-SE')` 輸出 24 小時制再把 `-` 換成 `/`),不需額外相依。供 `formatLog` 組裝統一格式前綴使用。 ```js -section('參數檢查'); -// 輸出: -// ================================================== -// 參數檢查 -// -------------------------------------------------- +timestamp(); +// 例如回傳: '2026/07/01 09:00:53' +``` + +> 前置條件:執行環境須支援 `Intl`(Node 18+ 內建)。結果:回傳當下台灣時間字串,無副作用。 + + +### formatLog + +依統一格式組出一行 log 字串:`[階段?][等級][時間]: 訊息`。階段取自目前 `currentStage`(為空字串時省略整個 `[階段]` 區塊),時間為 Asia/Taipei。為 `info`/`success`/`warn`/`fail` 共用的內部格式化函式,本身不輸出。 + +```js +stage('刪除舊版本'); +formatLog('ERR', '刪除 tag 失敗: v0.0.1, HTTP 500'); +// 回傳: '[刪除舊版本][ERR][2026/07/01 09:00:53]: 刪除 tag 失敗: v0.0.1, HTTP 500' ``` ### info -以 `[INFO]` 前綴將一般資訊訊息輸出到 stdout(`console.log`)。 +以 `[階段?][INF][時間]:` 前綴將一般資訊訊息輸出到 stdout(`console.log`)。 ```js +stage('取得舊版本'); info('RELEASE_COUNT=10'); -// 輸出:[INFO] RELEASE_COUNT=10 +// 輸出:[取得舊版本][INF][2026/07/01 09:00:53]: RELEASE_COUNT=10 ``` ### success -以 `[OK]` 前綴將成功訊息輸出到 stdout。 +以 `[階段?][INF][時間]:` 前綴將成功訊息輸出到 stdout。規範等級碼無「成功」一項,故等級採 `INF`,成功語意保留於訊息文字。 ```js -success('成功刪除: v1.0.0 (Release 1.0.0)'); -// 輸出:[OK] 成功刪除: v1.0.0 (Release 1.0.0) +stage('取得舊版本'); +success('沒有需要清理的舊版本成品'); +// 輸出:[取得舊版本][INF][2026/07/01 09:00:53]: 沒有需要清理的舊版本成品 ``` ### warn -以 `[WARN]` 前綴將警告訊息輸出到 stdout(非致命情況,程式會繼續執行)。 +以 `[階段?][WRN][時間]:` 前綴將警告訊息輸出到 stdout(非致命情況,程式會繼續執行)。 ```js +stage('參數檢查'); warn('GITEA_TOKEN is empty; release API calls will be anonymous'); -// 輸出:[WARN] GITEA_TOKEN is empty; release API calls will be anonymous +// 輸出:[參數檢查][WRN][2026/07/01 09:00:53]: GITEA_TOKEN is empty; release API calls will be anonymous ``` ### fail -以 `[ERR]` 前綴將錯誤訊息輸出到 **stderr**(`console.error`)。僅負責輸出,不會結束程序;是否離開由呼叫端決定。 +以 `[階段?][ERR][時間]:` 前綴將錯誤訊息輸出到 **stderr**(`console.error`)。僅負責輸出,不會結束程序;是否離開由呼叫端決定。 ```js -fail('刪除失敗: v0.1.0 (Release 0.1.0), HTTP 500'); +stage('刪除舊版本'); +fail('刪除 tag 失敗: v0.0.1, HTTP 500'); +// 輸出(stderr):[刪除舊版本][ERR][2026/07/01 09:00:53]: 刪除 tag 失敗: v0.0.1, HTTP 500 process.exit(1); // 終止由呼叫端負責 ``` @@ -159,11 +197,12 @@ isEmptyOrNull('abc'); // false ### requireValue -檢查必填值。先以 `[INFO]` 印出 `name=value`,若值被判定為空則印出 `[ERR]` 並以 exit code 1 結束程序。 +檢查必填值。先以 `info`(`[INF]`)印出 `name=value`,若值被判定為空則以 `fail`(`[ERR]`)輸出並以 exit code 1 結束程序。 ```js +stage('參數檢查'); requireValue('GITEA_SERVER_URL', process.env.GITEA_SERVER_URL); -// 值為空 → 輸出 [ERR] GITEA_SERVER_URL is required,並 process.exit(1) +// 值為空 → 輸出 [參數檢查][ERR][時間]: GITEA_SERVER_URL is required,並 process.exit(1) ``` > 前置條件:應在程式啟動早期呼叫。注意此函式會將值原樣印到 stdout,故不用於機密值(程式對 `GITEA_TOKEN` 改以 redacted 方式輸出)。 @@ -171,11 +210,11 @@ requireValue('GITEA_SERVER_URL', process.env.GITEA_SERVER_URL); ### requireInteger -驗證值是否為非負整數字串(正則 `/^[0-9]+$/`,允許前導零、不允許負號或小數),不符合時印出 `[ERR]` 並以 exit code 1 結束程序。 +驗證值是否為非負整數字串(正則 `/^[0-9]+$/`,允許前導零、不允許負號或小數),不符合時以 `[ERR]` 輸出並以 exit code 1 結束程序。 ```js requireInteger('KEEP_COUNT', '2'); // 通過 -requireInteger('KEEP_COUNT', '-1'); // 輸出 [ERR] ... 並 process.exit(1) +requireInteger('KEEP_COUNT', '-1'); // 輸出 [ERR] 訊息並 process.exit(1) ``` @@ -192,7 +231,7 @@ isBeta('v1.2.3-beta'); // false(無結尾的點,不符合 -beta.) ### main -Action 主流程(`async`)。讀取環境變數後依序執行參數檢查、取得 release、刪除舊版本(正式版與 beta 版各自保留 `KEEP_COUNT` 筆)、刪除未指定 release 的 tag。由檔尾 `main().catch(...)` 立即執行。 +Action 主流程(`async`)。讀取環境變數後依序執行參數檢查、取得 release、刪除舊版本(正式版與 beta 版各自保留 `KEEP_COUNT` 筆)、刪除未指定 release 的 tag;各階段以 `stage()` 標示,log 皆帶 `[階段]` 前綴。由檔尾 `main().catch(...)` 立即執行。 ```js // 容器啟動時由 entrypoint.sh 執行:exec node /app/index.js @@ -204,4 +243,4 @@ main().catch((err) => { }); ``` -> 前置條件:執行環境須提供必要環境變數,且可連線到 Gitea API。結果:清理符合條件的舊 release 與孤兒 tag,並輸出處理過程 log。 +> 前置條件:執行環境須提供必要環境變數,且可連線到 Gitea API。結果:清理符合條件的舊 release 與孤兒 tag,並以統一的 `[階段][等級][時間]` 日誌輸出處理過程。 diff --git a/app/index.js b/app/index.js index ab96512..eca265b 100644 --- a/app/index.js +++ b/app/index.js @@ -2,67 +2,90 @@ 'use strict'; /** - * 在 stdout 輸出一條視覺分隔線(前後各一個換行 + 50 個等號), - * 用於在 log 中切割不同區段。 + * 目前流程階段名稱,作為每行 log 的 `[階段]` 前綴使用; + * 為空字串時省略整個 `[階段]` 區塊。 + * @type {string} + */ +let currentStage = ''; + +/** + * 設定目前流程階段名稱,之後輸出的 log 會以 `[階段]` 前綴標示所屬階段。 + * 傳入空字串可清除階段前綴。 * + * @param {string} name 階段名稱(顯示於每行 log 開頭的 `[階段]` 區塊)。 * @returns {void} */ -function separator() { - process.stdout.write('\n==================================================\n'); +function stage(name) { + currentStage = name; } /** - * 輸出一個區段標題區塊:上緣等號分隔線、標題文字、下緣虛線, - * 用於在 log 中標示流程進入新階段。 + * 取得目前 Asia/Taipei 時區的時間字串,格式為 `yyyy/MM/dd HH:mm:ss`。 + * 使用 Node 內建 Intl(`toLocaleString('sv-SE')` 天生輸出 24 小時制 + * `YYYY-MM-DD HH:mm:ss`,再把 `-` 換成 `/`),不需額外相依。 * - * @param {string} title 區段標題文字。 - * @returns {void} + * @returns {string} 例如 `2026/06/30 16:53:06`。 */ -function section(title) { - separator(); - process.stdout.write(`${title}\n`); - process.stdout.write('--------------------------------------------------\n'); +function timestamp() { + return new Date() + .toLocaleString('sv-SE', { timeZone: 'Asia/Taipei' }) + .replace(/-/g, '/'); } /** - * 以 [INFO] 前綴將一般資訊訊息輸出到 stdout。 + * 依統一格式組出一行 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(`[INFO] ${msg}`); + console.log(formatLog('INF', msg)); } /** - * 以 [OK] 前綴將成功訊息輸出到 stdout。 + * 以 `[階段?][INF][時間]:` 前綴將成功訊息輸出到 stdout(時間為 Asia/Taipei)。 + * 規範等級碼無「成功」,故等級採 INF,成功語意保留於訊息文字。 * * @param {string} msg 要輸出的訊息內容。 * @returns {void} */ function success(msg) { - console.log(`[OK] ${msg}`); + console.log(formatLog('INF', msg)); } /** - * 以 [WARN] 前綴將警告訊息輸出到 stdout。 + * 以 `[階段?][WRN][時間]:` 前綴將警告訊息輸出到 stdout(時間為 Asia/Taipei)。 * * @param {string} msg 要輸出的訊息內容。 * @returns {void} */ function warn(msg) { - console.log(`[WARN] ${msg}`); + console.log(formatLog('WRN', msg)); } /** - * 以 [ERR] 前綴將錯誤訊息輸出到 stderr。 + * 以 `[階段?][ERR][時間]:` 前綴將錯誤訊息輸出到 stderr(時間為 Asia/Taipei)。 * 僅負責輸出,不會結束程序(是否離開由呼叫端決定)。 * * @param {string} msg 要輸出的錯誤訊息內容。 * @returns {void} */ function fail(msg) { - console.error(`[ERR] ${msg}`); + console.error(formatLog('ERR', msg)); } /** @@ -78,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 要驗證的參數值。 @@ -143,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); @@ -211,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); @@ -237,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) { @@ -262,7 +287,7 @@ async function main() { } } - section('刪除未指定 release 的 tag'); + stage('刪除舊版本'); // 重新取得 release 清單,得到刪除舊版本後仍指定 tag 的成品 const currentReleases = await fetchAllPages(releaseApiUrl); @@ -297,8 +322,6 @@ async function main() { fail(`刪除 tag 失敗: ${tagName}, HTTP ${code}`); } } - - separator(); } main().catch((err) => { diff --git a/entrypoint.sh b/entrypoint.sh index 0dd682b..a6046a8 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -7,7 +7,7 @@ # 中辨識正在執行哪一個 action 及其版本;接著以 exec 啟動 node 主程式 # /app/index.js,並把所有外部傳入的參數原樣交給它。 # -# 更新日期(台灣時區):2026/06/30 12:02:55 +# 更新日期(台灣時區):2026/06/30 18:19:59 # ============================================================================= # 啟用「遇到任何指令失敗(非 0 結束碼)即立刻中止腳本」的模式。 @@ -19,7 +19,7 @@ ACTION_NAME='Release Cleanup' # action 用途說明,供下方橫幅輸出使用。 ACTION_DESC='清理舊成品' # action 更新時間(固定字串,台灣時區),供下方橫幅輸出使用。 -ACTION_UPDATED='2026/06/30 12:02:55' +ACTION_UPDATED='2026/06/30 18:19:59' # 輸出一個空行 + 分隔線,作為橫幅的上邊框,讓 log 中的資訊區塊更易閱讀。 printf '\n%s\n' '=================================================='