From 7e6f9ef3f66f223b22ca4e1018b9d3d31aed7187 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Fri, 26 Jun 2026 10:42:53 +0800 Subject: [PATCH] =?UTF-8?q?docs(release-cleanup):=20=E8=A3=9C=E9=BD=8A=20c?= =?UTF-8?q?onfig/gitea-client/releases/tags=20=E7=9A=84=20JSDoc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 為 loadConfig、GiteaClient(含 fetchAllPages/deleteResource)、 selectReleasesToDelete/cleanupReleases、categorizeTags/cleanupOrphanTags 補上完整 JSDoc(參數、回傳、例外、行為說明)。僅註解變更。 Co-Authored-By: Claude Opus 4.8 (1M context) --- app/config.js | 14 +++++++++++--- app/gitea-client.js | 23 +++++++++++++++++------ app/releases.js | 22 ++++++++++++++-------- app/tags.js | 26 +++++++++++++++++--------- 4 files changed, 59 insertions(+), 26 deletions(-) diff --git a/app/config.js b/app/config.js index eaf8de4..f0278ce 100644 --- a/app/config.js +++ b/app/config.js @@ -5,9 +5,17 @@ import { isEmptyOrNull, requireValue, requireInteger } from './validate.js' import { section, info, warn } from './logger.js' /** - * 從環境變數載入設定並完成驗證。 - * @param {NodeJS.ProcessEnv} env 環境變數來源,預設為 process.env - * @returns 包含 API 位址、token、保留數量等資訊的設定物件 + * 從環境變數載入設定並完成驗證,作為整個清理流程的設定來源。 + * + * 讀取 `GITEA_SERVER_URL`、`GITEA_REPOSITORY`、`KEEP_COUNT`、`GITEA_TOKEN`, + * 其中前三者為必填(`KEEP_COUNT` 另須為非負整數);`GITEA_TOKEN` 為選填, + * 為空時改以匿名方式呼叫 API。驗證過程會將參數印出(token 以 `[redacted]` 遮蔽)。 + * + * @param {NodeJS.ProcessEnv} env 環境變數來源,預設為 `process.env`,可於測試時注入假值 + * @returns {{serverUrl: string, repository: string, token: string|null, keepCount: number, releaseApiUrl: string, tagApiUrl: string}} + * 設定物件:`token` 在環境變數為空時為 `null`;`keepCount` 為數值; + * `releaseApiUrl`/`tagApiUrl` 為依 server 與 repository 組出的 API 位址。 + * @throws {Error} 任一必填項缺漏,或 `KEEP_COUNT` 非非負整數時拋出(由 [[validate]] 的檢查函式丟出) */ export function loadConfig(env = process.env) { section('參數檢查') diff --git a/app/gitea-client.js b/app/gitea-client.js index bb810f3..d5a7bf6 100644 --- a/app/gitea-client.js +++ b/app/gitea-client.js @@ -1,9 +1,15 @@ // 與 Gitea API 溝通的 HTTP 客戶端,封裝認證標頭、分頁讀取與刪除請求。 // 對應原本 entrypoint.sh 的 fetch_all_pages 與 curl DELETE 呼叫。 +/** + * 與 Gitea REST API 溝通的輕量 HTTP 客戶端,負責帶上認證標頭、分頁讀取清單與發出刪除請求。 + * 取代原 bash 版本以 `curl`/`jq` 進行的 API 操作。 + */ export class GiteaClient { /** - * @param {{ token?: string | null }} options 認證設定;有 token 時帶上 Authorization 標頭 + * 建立客戶端;有 token 時於後續所有請求帶上 `Authorization: token ` 標頭, + * 無 token(或為 `null`)時以匿名方式呼叫。 + * @param {{ token?: string | null }} [options] 認證設定 */ constructor({ token = null } = {}) { this.headers = {} @@ -13,9 +19,14 @@ export class GiteaClient { } /** - * 逐頁讀取分頁式清單 API,直到回傳空陣列為止,合併成單一陣列。 - * @param {string} baseUrl 不含 query string 的 API 位址 - * @returns {Promise} 所有頁面合併後的項目 + * 逐頁讀取分頁式清單 API(每頁以 `?page=N` 由 1 遞增),直到某頁回傳空陣列(或非陣列)為止, + * 將所有頁面項目合併成單一陣列回傳。 + * + * 注意:終止條件依賴「空陣列代表最後一頁」的假設;若 API 不以空陣列結尾則迴圈不會自然停止。 + * + * @param {string} baseUrl 不含 query string 的 API 位址(本方法會自行附加 `?page=N`) + * @returns {Promise} 所有頁面合併後的項目陣列;無資料時為空陣列 + * @throws {Error} 任一頁回應 HTTP 非 2xx(`!res.ok`)時拋出,訊息含 URL 與狀態碼 */ async fetchAllPages(baseUrl) { const all = [] @@ -42,9 +53,9 @@ export class GiteaClient { } /** - * 對指定資源發出 DELETE 請求。 + * 對指定資源發出 DELETE 請求,僅回傳 HTTP 狀態碼供呼叫端判斷成敗(不檢查 ok、不解析回應內容)。 * @param {string} url 目標資源位址 - * @returns {Promise} HTTP 狀態碼 + * @returns {Promise} 回應的 HTTP 狀態碼(成功刪除通常為 204) */ async deleteResource(url) { const res = await fetch(url, { method: 'DELETE', headers: this.headers }) diff --git a/app/releases.js b/app/releases.js index 418ecbd..9ec6881 100644 --- a/app/releases.js +++ b/app/releases.js @@ -5,11 +5,11 @@ import { section, info, success, fail, warn } from './logger.js' import { isEmptyOrNull } from './validate.js' /** - * 依建立時間由新到舊排序,保留最新的 keepCount 筆,回傳其餘待刪除的成品。 - * 純函式,方便單元測試。 - * @param {any[]} releases 成品清單 - * @param {number} keepCount 要保留的筆數 - * @returns {any[]} 需要刪除的成品(較舊者) + * 依 `created_at` 建立時間由新到舊排序,保留最新的 `keepCount` 筆,回傳其餘(較舊)待刪除的成品。 + * 以展開運算子複製陣列後再排序,不會改動傳入的原陣列。為純函式,方便單元測試。 + * @param {any[]} releases 成品清單,每個元素預期含有 `created_at` 欄位 + * @param {number} keepCount 要保留的最新筆數;為 0 時代表全部刪除 + * @returns {any[]} 需要刪除的成品陣列(較舊者),依新到舊排序 */ export function selectReleasesToDelete(releases, keepCount) { const sorted = [...releases].sort( @@ -19,9 +19,15 @@ export function selectReleasesToDelete(releases, keepCount) { } /** - * 讀取成品清單,刪除超出保留數量的舊版本成品。 - * @param {import('./gitea-client.js').GiteaClient} client - * @param {ReturnType} config + * 讀取全部成品清單,刪除超出保留數量的舊版本成品。 + * + * 流程:取得所有 release → 若總數不超過 `keepCount` 則直接結束(無需清理)→ + * 否則以 [[selectReleasesToDelete]] 取出待刪除清單,逐筆刪除(略過沒有 `id` 的項目), + * 依回應狀態碼 204 判定成功與否並輸出結果。 + * + * @param {import('./gitea-client.js').GiteaClient} client 用於讀取與刪除的 Gitea 客戶端 + * @param {ReturnType} config 設定物件,使用其 `releaseApiUrl` 與 `keepCount` + * @returns {Promise} */ export async function cleanupReleases(client, config) { section('取得成品資訊') diff --git a/app/tags.js b/app/tags.js index 230de91..c991da3 100644 --- a/app/tags.js +++ b/app/tags.js @@ -5,12 +5,14 @@ import { section, info, success, fail, warn } from './logger.js' import { isEmptyOrNull } from './validate.js' /** - * 將 tag 分類為保留、刪除或略過(無名稱)。 - * 仍被任一 release 指定的 tag 予以保留,其餘視為孤立 tag 待刪除。 - * 純函式,方便單元測試。 - * @param {any[]} tags tag 清單 - * @param {Iterable} releaseTagNames 仍被 release 指定的 tag 名稱 - * @returns {{ tag: any, action: 'keep' | 'delete' | 'skip' }[]} + * 將每個 tag 分類為保留、刪除或略過三類,判斷優先序為:無名稱 → skip、仍被指定 → keep、其餘 → delete。 + * + * 先把 `releaseTagNames` 收進 `Set` 以利 O(1) 比對;名稱經 [[isEmptyOrNull]] 判定為空者標為 `skip`, + * 仍被任一 release 指定者標為 `keep`,其餘視為孤立 tag 標為 `delete`。為純函式,無副作用,方便單元測試。 + * + * @param {any[]} tags tag 清單,每個元素預期含有 `name` 欄位 + * @param {Iterable} releaseTagNames 仍被 release 指定的 tag 名稱集合 + * @returns {{ tag: any, action: 'keep' | 'delete' | 'skip' }[]} 與輸入等長且保持原順序的分類結果 */ export function categorizeTags(tags, releaseTagNames) { const keep = new Set(releaseTagNames) @@ -27,9 +29,15 @@ export function categorizeTags(tags, releaseTagNames) { } /** - * 重新讀取成品清單以取得仍被指定的 tag,再刪除未指定 release 的孤立 tag。 - * @param {import('./gitea-client.js').GiteaClient} client - * @param {ReturnType} config + * 重新讀取成品清單以取得仍被指定的 tag,再刪除未被任何 release 指定的孤立 tag。 + * + * 流程:重新抓取全部 release(反映刪除舊版本後的最新狀態)並取出其 `tag_name` 集合 → + * 抓取全部 tag → 以 [[categorizeTags]] 分類,對 `skip` 警告略過、`keep` 保留, + * 對 `delete` 發出刪除並依狀態碼 204 判定成功與否。 + * + * @param {import('./gitea-client.js').GiteaClient} client 用於讀取與刪除的 Gitea 客戶端 + * @param {ReturnType} config 設定物件,使用其 `releaseApiUrl` 與 `tagApiUrl` + * @returns {Promise} */ export async function cleanupOrphanTags(client, config) { section('刪除未指定 release 的 tag')