- fetchAllPages 加入 MAX_PAGES(1000)安全斷點,避免 API 異常時無限迴圈 - 刪除 release/tag 時對 id 與 tag 名稱做 encodeURIComponent, 防止特殊字元造成路徑穿越(正常數值/版本字串編碼後不變) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
71 lines
2.8 KiB
JavaScript
71 lines
2.8 KiB
JavaScript
// 清理舊版本成品的功能模組。
|
|
// 對應原本 entrypoint.sh 的「取得成品資訊」與「刪除舊版本成品」區段。
|
|
|
|
import { section, info, success, fail, warn } from './logger.js'
|
|
import { isEmptyOrNull } from './validate.js'
|
|
|
|
/**
|
|
* 依 `created_at` 建立時間由新到舊排序,保留最新的 `keepCount` 筆,回傳其餘(較舊)待刪除的成品。
|
|
* 以展開運算子複製陣列後再排序,不會改動傳入的原陣列。為純函式,方便單元測試。
|
|
* @param {any[]} releases 成品清單,每個元素預期含有 `created_at` 欄位
|
|
* @param {number} keepCount 要保留的最新筆數;為 0 時代表全部刪除
|
|
* @returns {any[]} 需要刪除的成品陣列(較舊者),依新到舊排序
|
|
*/
|
|
export function selectReleasesToDelete(releases, keepCount) {
|
|
// 解析 created_at;格式無效或缺漏時視為最舊(0),避免 NaN 造成排序結果不可預期。
|
|
const createdTime = (release) => {
|
|
const time = Date.parse(release?.created_at)
|
|
return Number.isNaN(time) ? 0 : time
|
|
}
|
|
const sorted = [...releases].sort((a, b) => createdTime(b) - createdTime(a))
|
|
return sorted.slice(keepCount)
|
|
}
|
|
|
|
/**
|
|
* 讀取全部成品清單,刪除超出保留數量的舊版本成品。
|
|
*
|
|
* 流程:取得所有 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) {
|
|
section('取得成品資訊')
|
|
info(`GET ${config.releaseApiUrl}`)
|
|
|
|
const releases = await client.fetchAllPages(config.releaseApiUrl)
|
|
info(`RELEASE_COUNT=${releases.length}`)
|
|
info(`KEEP_COUNT=${config.keepCount}`)
|
|
|
|
if (releases.length <= config.keepCount) {
|
|
success('沒有需要清理的舊版本成品')
|
|
return
|
|
}
|
|
|
|
section('刪除舊版本成品')
|
|
const toDelete = selectReleasesToDelete(releases, config.keepCount)
|
|
|
|
for (const release of toDelete) {
|
|
const { id, tag_name: tag, name } = release
|
|
|
|
if (isEmptyOrNull(id)) {
|
|
warn(`略過沒有 id 的成品: ${tag} (${name})`)
|
|
continue
|
|
}
|
|
|
|
// 對 id 做編碼,避免非預期內容被拼接進 URL(防路徑穿越);正常數值 id 編碼後不變。
|
|
const url = `${config.releaseApiUrl}/${encodeURIComponent(id)}`
|
|
info(`DELETE ${tag} (${name})`)
|
|
|
|
const code = await client.deleteResource(url)
|
|
if (code === 204) {
|
|
success(`成功刪除: ${tag} (${name})`)
|
|
} else {
|
|
fail(`刪除失敗: ${tag} (${name}), HTTP ${code}`)
|
|
}
|
|
}
|
|
}
|