# Release Cleanup > 更新時間:2026/07/01 09:20:52(台灣時區 Asia/Taipei) 清理舊成品的 **Gitea / GitHub docker container action**。依保留數量清理 repo 的舊 release,並刪除沒有對應 release 的 tag;正式版與 beta 版各自獨立保留指定數量。 - 主程式語言:JavaScript(Node.js,使用原生 `fetch`,需 Node 18+) - 執行方式:docker container action(`action.yaml` → `Dockerfile` → `entrypoint.sh` → `node /app/index.js`) ## 行為總覽 1. **參數檢查**:驗證 `GITEA_SERVER_URL`、`GITEA_REPOSITORY`、`KEEP_COUNT`(缺值或 `KEEP_COUNT` 非整數時以 exit code 1 結束)。 2. **取得舊版本**:取得所有 release,依建立時間由新到舊排序,並以 tag 名稱含 `-beta.` 與否分為正式版與 beta 版。 3. **刪除舊版本**:正式版與 beta 版**各自**保留最新 `KEEP_COUNT` 筆,刪除其餘 release(單筆刪除失敗只記錄錯誤,不中斷);接著重新取得 release 後,刪除所有未被任何 release 指定的 tag。 `GITEA_TOKEN` 為空時以匿名身分呼叫 API。取得分頁失敗或參數不合法則直接以 exit code 1 結束程序;底層 `fetch` 網路錯誤或回應非 JSON 造成的解析失敗會上拋,由檔尾 `main().catch` 統一捕捉後以 exit code 1 結束。 ## 日誌輸出格式 所有等級式輸出統一為 `[{階段}][{等級}][{時間}]: {訊息}`: - `階段`:目前流程階段(`參數檢查` / `取得舊版本` / `刪除舊版本`),由 `stage()` 設定;未設定時省略整個 `[階段]` 區塊。 - `等級`:`INF`(含成功訊息)/ `WRN` / `ERR`。 - `時間`:Asia/Taipei 的 `yyyy/MM/dd HH:mm:ss`。 - `fail` 走 stderr,其餘走 stdout。 範例: ```text [參數檢查][INF][2026/07/01 09:20:52]: GITEA_SERVER_URL=https://gitea.jsc.idv.tw [取得舊版本][INF][2026/07/01 09:20:52]: RELEASE_COUNT=3 [取得舊版本][INF][2026/07/01 09:20:52]: 沒有需要清理的舊版本成品 [刪除舊版本][ERR][2026/07/01 09:20:52]: 刪除 tag 失敗: v0.0.1, HTTP 500 ``` > 容器啟動橫幅(名稱/用途/更新時間)由 `entrypoint.sh` 輸出,不帶等級與階段前綴。 ## Action 輸入與環境變數 | 名稱 | 來源 | 必填 | 預設 | 說明 | | --- | --- | --- | --- | --- | | `keep_count` | `inputs` | 否 | `2` | 正式版與 beta 版各自要保留的成品數量 | | `GITEA_SERVER_URL` | env(`gitea.server_url`) | 是 | — | Gitea 站台 URL | | `GITEA_REPOSITORY` | env(`gitea.repository`) | 是 | — | `owner/repo` 形式的儲存庫 | | `GITEA_TOKEN` | env(`gitea.token`) | 否 | — | API token;為空時以匿名呼叫 | ### Workflow 使用範例 ```yaml jobs: cleanup: runs-on: ubuntu-latest steps: - name: Release Cleanup uses: docker-actions/release-cleanup@develop with: keep_count: 3 ``` ## 專案列表 ### 專案描述 | 專案名稱 | 專案描述 | | --- | --- | | [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) | 無 | ### NuGet 套件 | 專案名稱 | NuGet 套件列表 | | --- | --- | | [release-cleanup](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app) | 無 | > 註:本專案為 Node.js 專案,無 .NET ProjectReference 與 NuGet 套件;`app/package.json` 亦無第三方相依(僅使用 Node 內建 API)。 ## 功能列表 ### release-cleanup > 說明:本專案為單一 Node 模組(`app/index.js`),未 `module.exports` 任何函式,下列為模組內可文件化的頂層函式(不含 `main` 內部的巢狀私有函式 `fetchAllPages` / `deleteUrl`)。 | 功能名稱 | 功能描述 | | --- | --- | | [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) | ## 使用範例 ### stage 設定目前流程階段名稱,之後 `info`/`success`/`warn`/`fail` 輸出的每行 log 會以 `[階段]` 前綴標示所屬階段;傳入空字串可清除前綴。 ```js stage('取得舊版本'); info('RELEASE_COUNT=3'); // 輸出:[取得舊版本][INF][2026/07/01 09:20:52]: RELEASE_COUNT=3 ``` > 前置條件:無。結果:更新模組層級的目前階段字串;本身不輸出任何內容。 ### timestamp 取得目前 **Asia/Taipei** 時區的時間字串,格式為 `yyyy/MM/dd HH:mm:ss`。使用 Node 內建 `Intl`(`toLocaleString('sv-SE')` 輸出 24 小時制再把 `-` 換成 `/`),不需額外相依。供 `formatLog` 組裝統一格式前綴使用。 ```js timestamp(); // 例如回傳: '2026/07/01 09:20:52' ``` > 前置條件:執行環境須支援 `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:20:52]: 刪除 tag 失敗: v0.0.1, HTTP 500' ``` ### info 以 `[階段?][INF][時間]:` 前綴將一般資訊訊息輸出到 stdout(`console.log`)。 ```js stage('取得舊版本'); info('RELEASE_COUNT=10'); // 輸出:[取得舊版本][INF][2026/07/01 09:20:52]: RELEASE_COUNT=10 ``` ### success 以 `[階段?][INF][時間]:` 前綴將成功訊息輸出到 stdout。規範等級碼無「成功」一項,故等級採 `INF`,成功語意保留於訊息文字。 ```js stage('取得舊版本'); success('沒有需要清理的舊版本成品'); // 輸出:[取得舊版本][INF][2026/07/01 09:20:52]: 沒有需要清理的舊版本成品 ``` ### warn 以 `[階段?][WRN][時間]:` 前綴將警告訊息輸出到 stdout(非致命情況,程式會繼續執行)。 ```js stage('參數檢查'); warn('GITEA_TOKEN is empty; release API calls will be anonymous'); // 輸出:[參數檢查][WRN][2026/07/01 09:20:52]: GITEA_TOKEN is empty; release API calls will be anonymous ``` ### fail 以 `[階段?][ERR][時間]:` 前綴將錯誤訊息輸出到 **stderr**(`console.error`)。僅負責輸出,不會結束程序;是否離開由呼叫端決定。 ```js stage('刪除舊版本'); fail('刪除 tag 失敗: v0.0.1, HTTP 500'); // 輸出(stderr):[刪除舊版本][ERR][2026/07/01 09:20:52]: 刪除 tag 失敗: v0.0.1, HTTP 500 process.exit(1); // 終止由呼叫端負責 ``` ### isEmptyOrNull 判斷值是否視為「空 / 未設定」,涵蓋 `undefined`、`null`、空字串,以及字面字串 `'null'`(用於處理 CI/環境變數把未設定值帶成字串 `"null"` 的情況)。以嚴格相等比對,故 `0`、`false`、`'0'`、純空白字串**不**視為空。 ```js isEmptyOrNull(''); // true isEmptyOrNull('null'); // true(字面字串) isEmptyOrNull('abc'); // false isEmptyOrNull('0'); // false(非空字串) ``` ### requireValue 檢查必填值。先以 `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) ``` > 前置條件:應在程式啟動早期呼叫。注意此函式會將值原樣印到 stdout,故不用於機密值(程式對 `GITEA_TOKEN` 改以 redacted 方式輸出)。 ### requireInteger 驗證值是否為非負整數字串(正則 `/^[0-9]+$/`,允許前導零、不允許負號或小數),不符合時以 `[ERR]` 輸出並以 exit code 1 結束程序。本函式不處理空值語意,需搭配 `requireValue` 先攔缺值。 ```js requireInteger('KEEP_COUNT', '2'); // 通過 requireInteger('KEEP_COUNT', '-1'); // 輸出 [ERR] 訊息並 process.exit(1) ``` ### isBeta 判斷 release / tag 名稱是否為 beta 版本,規則為名稱中含有子字串 `-beta.`(區分大小寫)。 ```js isBeta('v1.2.3-beta.1'); // true isBeta('v1.2.3'); // false isBeta('v1.2.3-beta'); // false(無結尾的點,不符合 -beta.) ``` ### main Action 主流程(`async`)。讀取環境變數後依序執行參數檢查、取得 release、刪除舊版本(正式版與 beta 版各自保留 `KEEP_COUNT` 筆)、刪除未指定 release 的 tag;各階段以 `stage()` 標示,log 皆帶 `[階段]` 前綴。由檔尾 `main().catch(...)` 立即執行並收斂未攔截的例外。 ```js // 容器啟動時由 entrypoint.sh 執行:exec node /app/index.js // 需提供環境變數: // GITEA_SERVER_URL、GITEA_REPOSITORY、KEEP_COUNT(必要)、GITEA_TOKEN(選填) main().catch((err) => { fail(err && err.stack ? err.stack : String(err)); process.exit(1); }); ``` > 前置條件:執行環境須提供必要環境變數,且可連線到 Gitea API。結果:清理符合條件的舊 release 與孤兒 tag,並以統一的 `[階段][等級][時間]` 日誌輸出處理過程。