From 1f27a46f7b2686af884206f17cf4f3d09b841b7c Mon Sep 17 00:00:00 2001 From: Jeffery Date: Fri, 26 Jun 2026 10:42:53 +0800 Subject: [PATCH] =?UTF-8?q?docs(README):=20=E5=BB=BA=E7=AB=8B=E5=B0=88?= =?UTF-8?q?=E6=A1=88=20README=20=E8=88=87=E5=8A=9F=E8=83=BD/=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E7=AF=84=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增專案列表、公開功能列表(連結至 develop 上的原始碼行)與使用範例; 連結指向 develop 分支以確保合併後仍有效。 Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 289 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..00a76ed --- /dev/null +++ b/README.md @@ -0,0 +1,289 @@ +# release-cleanup + +release-cleanup 是一個 Gitea Docker Action,用於自動清理儲存庫中的成品(release)與標籤(tag):刪除超出保留數量的舊版本 release,並移除未被任何 release 指定的孤立 tag。原本以 bash 實作,現已改寫為模組化的 Node.js 程式,進入點仍為 `entrypoint.sh`。 + +> 更新時間:2026/06/26 10:40:15 + +## 專案列表 + +### 專案描述 + +| 專案名稱 | 專案描述 | +| --- | --- | +| [release-cleanup](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app) | Gitea Docker Action 的 Node.js 實作。提供主控台輸出、參數驗證、設定載入、Gitea API 客戶端,以及清理舊版本 release 與孤立 tag 的功能。 | + +### 參考專案 + +| 專案名稱 | 參考專案列表 | +| --- | --- | +| [release-cleanup](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app) | 無 | + +### 相依套件(npm) + +| 專案名稱 | 套件列表 | +| --- | --- | +| [release-cleanup](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app) | 無(僅使用 Node.js 內建模組與 `node:test`) | + +## 功能列表 + +### release-cleanup + +| 功能名稱 | 功能描述 | +| --- | --- | +| [separator](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/logger.js#L9) | [輸出一條等號分隔線至 stdout。](#separator) | +| [section](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/logger.js#L17) | [輸出區段標題(分隔線 + 標題 + 虛線)。](#section) | +| [info](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/logger.js#L27) | [以 `[INFO]` 前綴輸出一般資訊。](#info) | +| [success](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/logger.js#L35) | [以 `[OK]` 前綴輸出成功訊息。](#success) | +| [warn](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/logger.js#L43) | [以 `[WARN]` 前綴輸出警告訊息。](#warn) | +| [fail](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/logger.js#L51) | [以 `[ERR]` 前綴輸出錯誤訊息至 stderr。](#fail) | +| [isEmptyOrNull](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/validate.js#L9) | [判斷值是否為空(空字串、null、undefined 或字串 "null")。](#isemptyornull) | +| [requireValue](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/validate.js#L19) | [要求欄位有值,空值時丟出 Error。](#requirevalue) | +| [requireInteger](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/validate.js#L32) | [要求欄位為非負整數,否則丟出 Error。](#requireinteger) | +| [loadConfig](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/config.js#L20) | [從環境變數載入並驗證設定,回傳設定物件。](#loadconfig) | +| [GiteaClient](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/gitea-client.js#L8) | [建立帶認證標頭的 Gitea API 客戶端。](#giteaclient) | +| [GiteaClient.fetchAllPages](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/gitea-client.js#L31) | [逐頁讀取分頁式清單 API 並合併成單一陣列。](#giteaclientfetchallpages) | +| [GiteaClient.deleteResource](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/gitea-client.js#L60) | [對指定資源發出 DELETE 請求並回傳狀態碼。](#giteaclientdeleteresource) | +| [selectReleasesToDelete](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/releases.js#L14) | [依時間排序後回傳超出保留數量的舊 release。](#selectreleasestodelete) | +| [cleanupReleases](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/releases.js#L32) | [讀取並刪除超出保留數量的舊版本 release。](#cleanupreleases) | +| [categorizeTags](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/tags.js#L17) | [將 tag 分類為保留、刪除或略過。](#categorizetags) | +| [cleanupOrphanTags](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/tags.js#L42) | [刪除未被任何 release 指定的孤立 tag。](#cleanuporphantags) | + +## 使用範例 + + +### separator + +輸出一條前後換行的等號分隔線至 stdout,用於視覺上區隔不同階段的輸出。 + +```js +import { separator } from './logger.js' + +separator() +// 輸出: +// +// ================================================== +``` + + +### section + +先印分隔線,再印標題與一條虛線,用於標示流程進入新階段。 + +```js +import { section } from './logger.js' + +section('參數檢查') +// ================================================== +// 參數檢查 +// -------------------------------------------------- +``` + + +### info + +以 `[INFO]` 前綴輸出一般資訊訊息至 stdout。 + +```js +import { info } from './logger.js' + +info('GET https://gitea.example.com/api/v1/repos/owner/repo/releases') +// [INFO] GET https://gitea.example.com/api/v1/repos/owner/repo/releases +``` + + +### success + +以 `[OK]` 前綴輸出成功訊息(前綴補空白以對齊其他標籤)。 + +```js +import { success } from './logger.js' + +success('成功刪除: v1.0.0 (Release 1.0.0)') +// [OK] 成功刪除: v1.0.0 (Release 1.0.0) +``` + + +### warn + +以 `[WARN]` 前綴輸出警告訊息;為與一般輸出同流,仍寫入 stdout。 + +```js +import { warn } from './logger.js' + +warn('GITEA_TOKEN is empty; release API calls will be anonymous') +// [WARN] GITEA_TOKEN is empty; release API calls will be anonymous +``` + + +### fail + +以 `[ERR]` 前綴輸出錯誤訊息至 stderr(唯一寫入 stderr 的輸出函式)。 + +```js +import { fail } from './logger.js' + +fail('刪除失敗: v0.9.0 (舊版), HTTP 500') +// (stderr) [ERR] 刪除失敗: v0.9.0 (舊版), HTTP 500 +``` + + +### isEmptyOrNull + +判斷值是否視為「空」。使用嚴格相等,因此 `0`、`false`、字串 `"0"` 都不算空;`undefined`、`null`、空字串與字串 `"null"` 才回傳 `true`。 + +```js +import { isEmptyOrNull } from './validate.js' + +isEmptyOrNull('') // true +isEmptyOrNull('null') // true +isEmptyOrNull(0) // false +isEmptyOrNull('v1.0') // false +``` + + +### requireValue + +要求指定欄位有值,空值時丟出 Error 以中止流程。空值判定委派給 `isEmptyOrNull`。 + +```js +import { requireValue } from './validate.js' + +requireValue('GITEA_SERVER_URL', 'https://gitea.example.com') // 通過 +requireValue('GITEA_SERVER_URL', '') // 丟出 Error: GITEA_SERVER_URL is required +``` + + +### requireInteger + +要求欄位為非負整數(可為數字或純數字字串),拒絕負數、小數、空值與非數字內容。 + +```js +import { requireInteger } from './validate.js' + +requireInteger('KEEP_COUNT', '2') // 通過 +requireInteger('KEEP_COUNT', 0) // 通過 +requireInteger('KEEP_COUNT', '-1') // 丟出 Error: KEEP_COUNT must be a non-negative integer +``` + + +### loadConfig + +從環境變數載入並驗證設定,回傳供清理流程使用的設定物件;必填項缺漏或 `KEEP_COUNT` 非整數時丟出 Error。 + +```js +import { loadConfig } from './config.js' + +// 由 process.env 讀取(GITEA_SERVER_URL / GITEA_REPOSITORY / KEEP_COUNT / GITEA_TOKEN) +const config = loadConfig() + +// 測試時可注入假環境變數 +const cfg = loadConfig({ + GITEA_SERVER_URL: 'https://gitea.example.com', + GITEA_REPOSITORY: 'owner/repo', + KEEP_COUNT: '2', +}) +// cfg.releaseApiUrl === 'https://gitea.example.com/api/v1/repos/owner/repo/releases' +``` + + +### GiteaClient + +建立與 Gitea API 溝通的客戶端;傳入 token 時於後續請求帶上 `Authorization` 標頭,否則以匿名方式呼叫。 + +```js +import { GiteaClient } from './gitea-client.js' + +const client = new GiteaClient({ token: process.env.GITEA_TOKEN }) +const anonymous = new GiteaClient() // 無 token,匿名呼叫 +``` + + +### GiteaClient.fetchAllPages + +逐頁讀取分頁式清單 API(每頁以 `?page=N` 遞增),直到回傳空陣列為止,合併成單一陣列;任一頁 HTTP 非 2xx 時丟出 Error。 + +```js +const client = new GiteaClient({ token: process.env.GITEA_TOKEN }) +const releases = await client.fetchAllPages( + 'https://gitea.example.com/api/v1/repos/owner/repo/releases', +) +console.log(`共取得 ${releases.length} 筆 release`) +``` + + +### GiteaClient.deleteResource + +對指定資源發出 DELETE 請求,回傳 HTTP 狀態碼供呼叫端判斷成敗(成功刪除通常為 204)。 + +```js +const code = await client.deleteResource( + 'https://gitea.example.com/api/v1/repos/owner/repo/releases/123', +) +if (code === 204) { + console.log('刪除成功') +} +``` + + +### selectReleasesToDelete + +依 `created_at` 由新到舊排序,保留最新的 `keepCount` 筆,回傳其餘(較舊)待刪除的 release;為純函式,不改動原陣列。 + +```js +import { selectReleasesToDelete } from './releases.js' + +const releases = [ + { id: 1, tag_name: 'v1.0.0', created_at: '2024-01-01T00:00:00Z' }, + { id: 2, tag_name: 'v2.0.0', created_at: '2024-02-01T00:00:00Z' }, + { id: 3, tag_name: 'v3.0.0', created_at: '2024-03-01T00:00:00Z' }, +] +selectReleasesToDelete(releases, 2) +// [{ id: 1, tag_name: 'v1.0.0', ... }] // 僅保留最新兩筆,回傳最舊的一筆 +``` + + +### cleanupReleases + +讀取全部 release,刪除超出保留數量的舊版本;總數不超過 `keepCount` 時不做任何刪除。 + +```js +import { loadConfig } from './config.js' +import { GiteaClient } from './gitea-client.js' +import { cleanupReleases } from './releases.js' + +const config = loadConfig() +const client = new GiteaClient({ token: config.token }) +await cleanupReleases(client, config) +``` + + +### categorizeTags + +將每個 tag 分類為 `keep`(仍被 release 指定)、`delete`(孤立 tag)或 `skip`(無名稱);為純函式,方便測試。 + +```js +import { categorizeTags } from './tags.js' + +const tags = [{ name: 'v2.0.0' }, { name: 'v1.0.0' }, { name: '' }] +categorizeTags(tags, ['v2.0.0']) +// [ +// { tag: { name: 'v2.0.0' }, action: 'keep' }, +// { tag: { name: 'v1.0.0' }, action: 'delete' }, +// { tag: { name: '' }, action: 'skip' }, +// ] +``` + + +### cleanupOrphanTags + +重新讀取 release 清單取得仍被指定的 tag,再刪除未被任何 release 指定的孤立 tag。 + +```js +import { loadConfig } from './config.js' +import { GiteaClient } from './gitea-client.js' +import { cleanupOrphanTags } from './tags.js' + +const config = loadConfig() +const client = new GiteaClient({ token: config.token }) +await cleanupOrphanTags(client, config) +```