- 移除 separator/section、新增 stage/formatLog 條目與錨點 - 更新「日誌輸出格式」章節與各範例為 [階段][等級][時間],同步函式起始行號 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
11 KiB
Release Cleanup
更新時間:2026/07/01 09:00:53(台灣時區 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)
行為總覽
- 參數檢查:驗證
GITEA_SERVER_URL、GITEA_REPOSITORY、KEEP_COUNT(缺值或KEEP_COUNT非整數時以 exit code 1 結束)。 - 取得舊版本:取得所有 release,依建立時間由新到舊排序,並以 tag 名稱含
-beta.與否分為正式版與 beta 版。 - 刪除舊版本:正式版與 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。
範例:
[參數檢查][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 輸入與環境變數
| 名稱 | 來源 | 必填 | 預設 | 說明 |
|---|---|---|---|---|
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 使用範例
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Release Cleanup
uses: docker-actions/release-cleanup@develop
with:
keep_count: 3
專案列表
專案描述
| 專案名稱 | 專案描述 |
|---|---|
| release-cleanup | 清理 Gitea repo 舊 release 與孤兒 tag 的 docker container action;正式版與 beta 版各自保留指定數量,並以統一的 [階段][等級][時間] 日誌輸出處理過程 |
參考專案
| 專案名稱 | 參考專案列表 |
|---|---|
| release-cleanup | 無 |
NuGet 套件
| 專案名稱 | NuGet 套件列表 |
|---|---|
| release-cleanup | 無 |
註:本專案為 Node.js 專案,無 .NET ProjectReference 與 NuGet 套件;
app/package.json亦無第三方相依(僅使用 Node 內建 API)。
功能列表
release-cleanup
說明:本專案為單一 Node 模組(
app/index.js),下列為模組內可文件化的頂層函式(不含main內部的巢狀私有函式fetchAllPages/deleteUrl)。
使用範例
stage
設定目前流程階段名稱,之後 info/success/warn/fail 輸出的每行 log 會以 [階段] 前綴標示所屬階段;傳入空字串可清除前綴。
stage('取得舊版本');
info('RELEASE_COUNT=3');
// 輸出:[取得舊版本][INF][2026/07/01 09:00:53]: RELEASE_COUNT=3
前置條件:無。結果:更新模組層級的目前階段字串;本身不輸出任何內容。
timestamp
取得目前 Asia/Taipei 時區的時間字串,格式為 yyyy/MM/dd HH:mm:ss。使用 Node 內建 Intl(toLocaleString('sv-SE') 輸出 24 小時制再把 - 換成 /),不需額外相依。供 formatLog 組裝統一格式前綴使用。
timestamp();
// 例如回傳: '2026/07/01 09:00:53'
前置條件:執行環境須支援
Intl(Node 18+ 內建)。結果:回傳當下台灣時間字串,無副作用。
formatLog
依統一格式組出一行 log 字串:[階段?][等級][時間]: 訊息。階段取自目前 currentStage(為空字串時省略整個 [階段] 區塊),時間為 Asia/Taipei。為 info/success/warn/fail 共用的內部格式化函式,本身不輸出。
stage('刪除舊版本');
formatLog('ERR', '刪除 tag 失敗: v0.0.1, HTTP 500');
// 回傳: '[刪除舊版本][ERR][2026/07/01 09:00:53]: 刪除 tag 失敗: v0.0.1, HTTP 500'
info
以 [階段?][INF][時間]: 前綴將一般資訊訊息輸出到 stdout(console.log)。
stage('取得舊版本');
info('RELEASE_COUNT=10');
// 輸出:[取得舊版本][INF][2026/07/01 09:00:53]: RELEASE_COUNT=10
success
以 [階段?][INF][時間]: 前綴將成功訊息輸出到 stdout。規範等級碼無「成功」一項,故等級採 INF,成功語意保留於訊息文字。
stage('取得舊版本');
success('沒有需要清理的舊版本成品');
// 輸出:[取得舊版本][INF][2026/07/01 09:00:53]: 沒有需要清理的舊版本成品
warn
以 [階段?][WRN][時間]: 前綴將警告訊息輸出到 stdout(非致命情況,程式會繼續執行)。
stage('參數檢查');
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)。僅負責輸出,不會結束程序;是否離開由呼叫端決定。
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); // 終止由呼叫端負責
isEmptyOrNull
判斷值是否視為「空 / 未設定」,涵蓋 undefined、null、空字串,以及字面字串 'null'(用於處理 CI/環境變數把未設定值帶成字串 "null" 的情況)。
isEmptyOrNull(''); // true
isEmptyOrNull('null'); // true(字面字串)
isEmptyOrNull('abc'); // false
requireValue
檢查必填值。先以 info([INF])印出 name=value,若值被判定為空則以 fail([ERR])輸出並以 exit code 1 結束程序。
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 結束程序。
requireInteger('KEEP_COUNT', '2'); // 通過
requireInteger('KEEP_COUNT', '-1'); // 輸出 [ERR] 訊息並 process.exit(1)
isBeta
判斷 release / tag 名稱是否為 beta 版本,規則為名稱中含有子字串 -beta.。
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(...) 立即執行。
// 容器啟動時由 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,並以統一的
[階段][等級][時間]日誌輸出處理過程。