diff --git a/app/index.js b/app/index.js index ec5c3fb..19044ed 100644 --- a/app/index.js +++ b/app/index.js @@ -4,7 +4,7 @@ import { loadConfig } from './config.js' import { GiteaClient } from './gitea-client.js' import { cleanupReleases } from './releases.js' import { cleanupOrphanTags } from './tags.js' -import { separator, fail } from './logger.js' +import { separator, failError } from './logger.js' /** * Action 主流程:讀取並驗證環境設定、建立帶認證的 Gitea 客戶端, @@ -24,26 +24,10 @@ export async function main() { separator() } -/** - * 將錯誤輸出至 stderr,並盡量保留可供除錯的上下文(錯誤類型與堆疊), - * 以便區分設定驗證錯誤與網路/API 請求錯誤。 - * @param {unknown} error 捕捉到的錯誤 - */ -function reportFatal(error) { - if (error instanceof Error) { - fail(`${error.name}: ${error.message}`) - if (error.stack) { - process.stderr.write(`${error.stack}\n`) - } - } else { - fail(String(error)) - } -} - // 僅在直接以 `node index.js` 執行時啟動主流程;被測試 import 時不自動執行,方便撰寫整合測試。 if (import.meta.url === `file://${process.argv[1]}`) { main().catch((error) => { - reportFatal(error) + failError(error) process.exit(1) }) } diff --git a/app/logger.js b/app/logger.js index 4db97f1..c421c05 100644 --- a/app/logger.js +++ b/app/logger.js @@ -51,3 +51,20 @@ export function warn(message) { export function fail(message) { process.stderr.write(`[ERR] ${message}\n`) } + +/** + * 輸出錯誤至 stderr,並依錯誤型別保留可供除錯的上下文: + * 若為 Error 物件,輸出「名稱: 訊息」並附上堆疊;否則輸出其字串形式。 + * 將「如何輸出錯誤」的邏輯集中於此,呼叫端毋須自行操作 stderr 或判斷型別。 + * @param {unknown} error 待輸出的錯誤 + */ +export function failError(error) { + if (error instanceof Error) { + fail(`${error.name}: ${error.message}`) + if (error.stack) { + process.stderr.write(`${error.stack}\n`) + } + } else { + fail(String(error)) + } +} diff --git a/app/validate.js b/app/validate.js index 7c2ee95..dec84d3 100644 --- a/app/validate.js +++ b/app/validate.js @@ -1,6 +1,11 @@ // 參數驗證,對應原本 entrypoint.sh 的 is_empty_or_null/require_value/require_integer。 // 驗證失敗時丟出 Error,由進入點統一捕捉後以非零狀態結束。 +// 允許的 URL 協定;集中為常數,方便檢視系統允許的協定與日後擴充。 +const ALLOWED_URL_PROTOCOLS = ['http:', 'https:'] +// 合法 repository 形式:owner/repo,僅允許英數字與 . _ -,且恰好一個 /。 +const REPOSITORY_PATTERN = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/ + /** * 判斷值是否視為「空」。使用嚴格相等,因此 `0`、`false`、字串 `"0"` 都不算空。 * @param {*} value 待判斷的值 @@ -49,7 +54,7 @@ export function requireUrl(name, value) { } catch { throw new Error(`${name} must be a valid URL`) } - if (url.protocol !== 'http:' && url.protocol !== 'https:') { + if (!ALLOWED_URL_PROTOCOLS.includes(url.protocol)) { throw new Error(`${name} must use http or https protocol`) } } @@ -62,7 +67,7 @@ export function requireUrl(name, value) { * @throws {Error} 格式不符或含 `..` 時丟出 `${name} must be in the form owner/repo without path traversal` */ export function requireRepository(name, value) { - if (String(value).includes('..') || !/^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/.test(value)) { + if (String(value).includes('..') || !REPOSITORY_PATTERN.test(value)) { throw new Error(`${name} must be in the form owner/repo without path traversal`) } }