fix(cleanup-release): 資料異常改判定失敗、分頁上限可設定並收斂錯誤輸出
- release id 非正整數、tag 缺少名稱時改視為失敗(fail + return false),壞資料不再被靜默當成功 - 分頁上限改為可用環境變數 MAX_PAGES 覆寫(預設 1000),超限錯誤訊息附調高方式 - 未捕捉錯誤預設只輸出 error.message,RUNNER_DEBUG=1 時才輸出完整 stack Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
c4a83a8969
commit
4aecd48b5a
+23
-13
@@ -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<string, string>} headers request 標頭。
|
||||
* @param {number} [maxPages=DEFAULT_MAX_PAGES] 分頁上限,可由環境變數 `MAX_PAGES` 覆寫。
|
||||
* @returns {Promise<any[]>} 合併後的陣列資料。
|
||||
*/
|
||||
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(() => {
|
||||
|
||||
Reference in New Issue
Block a user