docs(release-cleanup): 補齊 config/gitea-client/releases/tags 的 JSDoc

為 loadConfig、GiteaClient(含 fetchAllPages/deleteResource)、
selectReleasesToDelete/cleanupReleases、categorizeTags/cleanupOrphanTags
補上完整 JSDoc(參數、回傳、例外、行為說明)。僅註解變更。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jeffery
2026-06-26 10:46:20 +08:00
co-authored by Claude Opus 4.8
parent 7082fd7311
commit 7e6f9ef3f6
4 changed files with 59 additions and 26 deletions
+11 -3
View File
@@ -5,9 +5,17 @@ import { isEmptyOrNull, requireValue, requireInteger } from './validate.js'
import { section, info, warn } from './logger.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) { export function loadConfig(env = process.env) {
section('參數檢查') section('參數檢查')
+17 -6
View File
@@ -1,9 +1,15 @@
// 與 Gitea API 溝通的 HTTP 客戶端,封裝認證標頭、分頁讀取與刪除請求。 // 與 Gitea API 溝通的 HTTP 客戶端,封裝認證標頭、分頁讀取與刪除請求。
// 對應原本 entrypoint.sh 的 fetch_all_pages 與 curl DELETE 呼叫。 // 對應原本 entrypoint.sh 的 fetch_all_pages 與 curl DELETE 呼叫。
/**
* 與 Gitea REST API 溝通的輕量 HTTP 客戶端,負責帶上認證標頭、分頁讀取清單與發出刪除請求。
* 取代原 bash 版本以 `curl`/`jq` 進行的 API 操作。
*/
export class GiteaClient { export class GiteaClient {
/** /**
* @param {{ token?: string | null }} options 認證設定;有 token 時帶上 Authorization 標頭 * 建立客戶端;有 token 時於後續所有請求帶上 `Authorization: token <token>` 標頭,
* 無 token(或為 `null`)時以匿名方式呼叫。
* @param {{ token?: string | null }} [options] 認證設定
*/ */
constructor({ token = null } = {}) { constructor({ token = null } = {}) {
this.headers = {} this.headers = {}
@@ -13,9 +19,14 @@ export class GiteaClient {
} }
/** /**
* 逐頁讀取分頁式清單 API,直到回傳空陣列為止,合併成單一陣列。 * 逐頁讀取分頁式清單 API(每頁以 `?page=N` 由 1 遞增),直到某頁回傳空陣列(或非陣列)為止,
* @param {string} baseUrl 不含 query string 的 API 位址 * 將所有頁面項目合併成單一陣列回傳。
* @returns {Promise<any[]>} 所有頁面合併後的項目 *
* 注意:終止條件依賴「空陣列代表最後一頁」的假設;若 API 不以空陣列結尾則迴圈不會自然停止。
*
* @param {string} baseUrl 不含 query string 的 API 位址(本方法會自行附加 `?page=N`)
* @returns {Promise<any[]>} 所有頁面合併後的項目陣列;無資料時為空陣列
* @throws {Error} 任一頁回應 HTTP 非 2xx(`!res.ok`)時拋出,訊息含 URL 與狀態碼
*/ */
async fetchAllPages(baseUrl) { async fetchAllPages(baseUrl) {
const all = [] const all = []
@@ -42,9 +53,9 @@ export class GiteaClient {
} }
/** /**
* 對指定資源發出 DELETE 請求。 * 對指定資源發出 DELETE 請求,僅回傳 HTTP 狀態碼供呼叫端判斷成敗(不檢查 ok、不解析回應內容)
* @param {string} url 目標資源位址 * @param {string} url 目標資源位址
* @returns {Promise<number>} HTTP 狀態碼 * @returns {Promise<number>} 回應的 HTTP 狀態碼(成功刪除通常為 204)
*/ */
async deleteResource(url) { async deleteResource(url) {
const res = await fetch(url, { method: 'DELETE', headers: this.headers }) const res = await fetch(url, { method: 'DELETE', headers: this.headers })
+14 -8
View File
@@ -5,11 +5,11 @@ import { section, info, success, fail, warn } from './logger.js'
import { isEmptyOrNull } from './validate.js' import { isEmptyOrNull } from './validate.js'
/** /**
* 依建立時間由新到舊排序,保留最新的 keepCount 筆,回傳其餘待刪除的成品。 * 依 `created_at` 建立時間由新到舊排序,保留最新的 `keepCount` 筆,回傳其餘(較舊)待刪除的成品。
* 純函式,方便單元測試。 * 以展開運算子複製陣列後再排序,不會改動傳入的原陣列。為純函式,方便單元測試。
* @param {any[]} releases 成品清單 * @param {any[]} releases 成品清單,每個元素預期含有 `created_at` 欄位
* @param {number} keepCount 要保留的筆數 * @param {number} keepCount 要保留的最新筆數;為 0 時代表全部刪除
* @returns {any[]} 需要刪除的成品(較舊者) * @returns {any[]} 需要刪除的成品陣列(較舊者),依新到舊排序
*/ */
export function selectReleasesToDelete(releases, keepCount) { export function selectReleasesToDelete(releases, keepCount) {
const sorted = [...releases].sort( const sorted = [...releases].sort(
@@ -19,9 +19,15 @@ export function selectReleasesToDelete(releases, keepCount) {
} }
/** /**
* 讀取成品清單,刪除超出保留數量的舊版本成品。 * 讀取全部成品清單,刪除超出保留數量的舊版本成品。
* @param {import('./gitea-client.js').GiteaClient} client *
* @param {ReturnType<import('./config.js').loadConfig>} config * 流程:取得所有 release → 若總數不超過 `keepCount` 則直接結束(無需清理)→
* 否則以 [[selectReleasesToDelete]] 取出待刪除清單,逐筆刪除(略過沒有 `id` 的項目),
* 依回應狀態碼 204 判定成功與否並輸出結果。
*
* @param {import('./gitea-client.js').GiteaClient} client 用於讀取與刪除的 Gitea 客戶端
* @param {ReturnType<import('./config.js').loadConfig>} config 設定物件,使用其 `releaseApiUrl` 與 `keepCount`
* @returns {Promise<void>}
*/ */
export async function cleanupReleases(client, config) { export async function cleanupReleases(client, config) {
section('取得成品資訊') section('取得成品資訊')
+17 -9
View File
@@ -5,12 +5,14 @@ import { section, info, success, fail, warn } from './logger.js'
import { isEmptyOrNull } from './validate.js' import { isEmptyOrNull } from './validate.js'
/** /**
* 將 tag 分類為保留、刪除或略過(無名稱) * 將每個 tag 分類為保留、刪除或略過三類,判斷優先序為:無名稱 → skip、仍被指定 → keep、其餘 → delete
* 仍被任一 release 指定的 tag 予以保留,其餘視為孤立 tag 待刪除。 *
* 純函式,方便單元測試。 * 先把 `releaseTagNames` 收進 `Set` 以利 O(1) 比對;名稱經 [[isEmptyOrNull]] 判定為空者標為 `skip`,
* @param {any[]} tags tag 清單 * 仍被任一 release 指定者標為 `keep`,其餘視為孤立 tag 標為 `delete`。為純函式,無副作用,方便單元測試。
* @param {Iterable<string>} releaseTagNames 仍被 release 指定的 tag 名稱 *
* @returns {{ tag: any, action: 'keep' | 'delete' | 'skip' }[]} * @param {any[]} tags tag 清單,每個元素預期含有 `name` 欄位
* @param {Iterable<string>} releaseTagNames 仍被 release 指定的 tag 名稱集合
* @returns {{ tag: any, action: 'keep' | 'delete' | 'skip' }[]} 與輸入等長且保持原順序的分類結果
*/ */
export function categorizeTags(tags, releaseTagNames) { export function categorizeTags(tags, releaseTagNames) {
const keep = new Set(releaseTagNames) const keep = new Set(releaseTagNames)
@@ -27,9 +29,15 @@ export function categorizeTags(tags, releaseTagNames) {
} }
/** /**
* 重新讀取成品清單以取得仍被指定的 tag,再刪除未指定 release 的孤立 tag。 * 重新讀取成品清單以取得仍被指定的 tag,再刪除未被任何 release 指定的孤立 tag。
* @param {import('./gitea-client.js').GiteaClient} client *
* @param {ReturnType<import('./config.js').loadConfig>} config * 流程:重新抓取全部 release(反映刪除舊版本後的最新狀態)並取出其 `tag_name` 集合 →
* 抓取全部 tag → 以 [[categorizeTags]] 分類,對 `skip` 警告略過、`keep` 保留,
* 對 `delete` 發出刪除並依狀態碼 204 判定成功與否。
*
* @param {import('./gitea-client.js').GiteaClient} client 用於讀取與刪除的 Gitea 客戶端
* @param {ReturnType<import('./config.js').loadConfig>} config 設定物件,使用其 `releaseApiUrl` 與 `tagApiUrl`
* @returns {Promise<void>}
*/ */
export async function cleanupOrphanTags(client, config) { export async function cleanupOrphanTags(client, config) {
section('刪除未指定 release 的 tag') section('刪除未指定 release 的 tag')