From 4aecd48b5a47eb62eaac81774746f5ee5e251b3e Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 15 Jul 2026 10:43:21 +0800 Subject: [PATCH] =?UTF-8?q?fix(cleanup-release):=20=E8=B3=87=E6=96=99?= =?UTF-8?q?=E7=95=B0=E5=B8=B8=E6=94=B9=E5=88=A4=E5=AE=9A=E5=A4=B1=E6=95=97?= =?UTF-8?q?=E3=80=81=E5=88=86=E9=A0=81=E4=B8=8A=E9=99=90=E5=8F=AF=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E4=B8=A6=E6=94=B6=E6=96=82=E9=8C=AF=E8=AA=A4=E8=BC=B8?= =?UTF-8?q?=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - release id 非正整數、tag 缺少名稱時改視為失敗(fail + return false),壞資料不再被靜默當成功 - 分頁上限改為可用環境變數 MAX_PAGES 覆寫(預設 1000),超限錯誤訊息附調高方式 - 未捕捉錯誤預設只輸出 error.message,RUNNER_DEBUG=1 時才輸出完整 stack Co-Authored-By: Claude Fable 5 --- src/index.js | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/index.js b/src/index.js index 8cc01bd..dfaafb1 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,7 @@ const https = require('https'); const DELETE_CONCURRENCY = 4; -const MAX_PAGES = 1000; +const DEFAULT_MAX_PAGES = 1000; const keepAliveAgent = new https.Agent({ keepAlive: true }); @@ -295,18 +295,19 @@ function request(url, { method = 'GET', headers = {}, collectBody = true } = {}) } /** - * 逐頁抓取 JSON 陣列資料,直到回傳空頁為止;超過 `MAX_PAGES` 即中止並回報異常。 + * 逐頁抓取 JSON 陣列資料,直到回傳空頁為止;超過 `maxPages` 即中止並回報異常。 * * @param {string} baseUrl 不含 page 參數的 API URL。 * @param {Record} headers request 標頭。 + * @param {number} [maxPages=DEFAULT_MAX_PAGES] 分頁上限,可由環境變數 `MAX_PAGES` 覆寫。 * @returns {Promise} 合併後的陣列資料。 */ -async function fetchAllPages(baseUrl, headers) { +async function fetchAllPages(baseUrl, headers, maxPages = DEFAULT_MAX_PAGES) { const all = []; for (let page = 1; ; page += 1) { - if (page > MAX_PAGES) { - throw new Error(`GET ${baseUrl} 分頁超過 ${MAX_PAGES} 頁上限,中止抓取以避免無限迴圈`); + if (page > maxPages) { + throw new Error(`GET ${baseUrl} 分頁超過 ${maxPages} 頁上限,中止抓取以避免無限迴圈;資料量更大時可用 MAX_PAGES 環境變數調高上限`); } const pageUrl = `${baseUrl}?page=${page}`; @@ -401,6 +402,7 @@ async function main() { const GITEA_REPOSITORY = normalizeEnvValue(process.env.GITEA_REPOSITORY); const RUNNER_TOKEN = normalizeEnvValue(process.env.RUNNER_TOKEN) ?? ''; const KEEP_COUNT = normalizeEnvValue(process.env.KEEP_COUNT) ?? ''; + const MAX_PAGES = normalizeEnvValue(process.env.MAX_PAGES) ?? ''; section('參數檢查'); requireValue('GITEA_SERVER_URL', GITEA_SERVER_URL, maskUrlForLog(GITEA_SERVER_URL)); @@ -410,6 +412,13 @@ async function main() { requireValue('KEEP_COUNT', KEEP_COUNT); requirePositiveInteger('KEEP_COUNT', KEEP_COUNT); + let maxPages = DEFAULT_MAX_PAGES; + if (!isEmptyOrNull(MAX_PAGES)) { + info(`MAX_PAGES=${MAX_PAGES}`); + requirePositiveInteger('MAX_PAGES', MAX_PAGES); + maxPages = Number(MAX_PAGES); + } + const keepCount = Number(KEEP_COUNT); const authHeaders = {}; if (isEmptyOrNull(RUNNER_TOKEN)) { @@ -432,7 +441,7 @@ async function main() { section('取得成品資訊'); info(`GET ${releaseApiUrl}`); - const releaseJson = await fetchAllPages(releaseApiUrl, authHeaders); + const releaseJson = await fetchAllPages(releaseApiUrl, authHeaders, maxPages); releaseJson.sort((left, right) => { if (left.created_at < right.created_at) { return 1; @@ -458,10 +467,10 @@ async function main() { const releaseResults = await processInBatches(releaseToDelete, DELETE_CONCURRENCY, async (releaseItem) => { const releaseId = releaseItem?.id; if (!Number.isSafeInteger(releaseId) || releaseId <= 0) { - warn( - `略過 id 不是正整數的成品: ${sanitizeLogText(releaseItem?.tag_name || '')} (${sanitizeLogText(releaseItem?.name || '')})`, + fail( + `成品 id 不是正整數,視為資料異常: ${sanitizeLogText(releaseItem?.tag_name || '')} (${sanitizeLogText(releaseItem?.name || '')})`, ); - return true; + return false; } const releaseTag = sanitizeLogText(releaseItem.tag_name || ''); @@ -496,14 +505,14 @@ async function main() { const tagApiUrl = `${serverBaseUrl}/api/v1/repos/${repositoryPath}/tags`; info(`GET ${tagApiUrl}`); - const tagJson = await fetchAllPages(tagApiUrl, authHeaders); + const tagJson = await fetchAllPages(tagApiUrl, authHeaders, maxPages); info(`TAG_COUNT=${tagJson.length}`); const tagResults = await processInBatches(tagJson, DELETE_CONCURRENCY, async (tagItem) => { const tagName = tagItem?.name; if (isEmptyOrNull(tagName)) { - warn('略過沒有名稱的 tag'); - return true; + fail('tag 缺少名稱,視為資料異常'); + return false; } const safeTagName = sanitizeLogText(tagName); @@ -532,7 +541,8 @@ async function main() { main() .catch((error) => { - fail(error instanceof Error ? error.stack || error.message : String(error)); + const showStack = process.env.RUNNER_DEBUG === '1'; + fail(error instanceof Error ? (showStack && error.stack) || error.message : String(error)); process.exitCode = 1; }) .finally(() => {