refactor(release-cleanup): 收斂錯誤輸出並抽出驗證規則常數
- 新增 logger.failError 集中處理 Error/非 Error 的 stderr 輸出與堆疊, index.js 改用之,移除重複的 reportFatal - validate.js 將允許協定與 repository 格式抽為模組常數,便於維護擴充 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
0f4354bd01
commit
f6d2ebeab2
+2
-18
@@ -4,7 +4,7 @@ import { loadConfig } from './config.js'
|
|||||||
import { GiteaClient } from './gitea-client.js'
|
import { GiteaClient } from './gitea-client.js'
|
||||||
import { cleanupReleases } from './releases.js'
|
import { cleanupReleases } from './releases.js'
|
||||||
import { cleanupOrphanTags } from './tags.js'
|
import { cleanupOrphanTags } from './tags.js'
|
||||||
import { separator, fail } from './logger.js'
|
import { separator, failError } from './logger.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action 主流程:讀取並驗證環境設定、建立帶認證的 Gitea 客戶端,
|
* Action 主流程:讀取並驗證環境設定、建立帶認證的 Gitea 客戶端,
|
||||||
@@ -24,26 +24,10 @@ export async function main() {
|
|||||||
separator()
|
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 時不自動執行,方便撰寫整合測試。
|
// 僅在直接以 `node index.js` 執行時啟動主流程;被測試 import 時不自動執行,方便撰寫整合測試。
|
||||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||||
main().catch((error) => {
|
main().catch((error) => {
|
||||||
reportFatal(error)
|
failError(error)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,3 +51,20 @@ export function warn(message) {
|
|||||||
export function fail(message) {
|
export function fail(message) {
|
||||||
process.stderr.write(`[ERR] ${message}\n`)
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+7
-2
@@ -1,6 +1,11 @@
|
|||||||
// 參數驗證,對應原本 entrypoint.sh 的 is_empty_or_null/require_value/require_integer。
|
// 參數驗證,對應原本 entrypoint.sh 的 is_empty_or_null/require_value/require_integer。
|
||||||
// 驗證失敗時丟出 Error,由進入點統一捕捉後以非零狀態結束。
|
// 驗證失敗時丟出 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"` 都不算空。
|
* 判斷值是否視為「空」。使用嚴格相等,因此 `0`、`false`、字串 `"0"` 都不算空。
|
||||||
* @param {*} value 待判斷的值
|
* @param {*} value 待判斷的值
|
||||||
@@ -49,7 +54,7 @@ export function requireUrl(name, value) {
|
|||||||
} catch {
|
} catch {
|
||||||
throw new Error(`${name} must be a valid URL`)
|
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`)
|
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`
|
* @throws {Error} 格式不符或含 `..` 時丟出 `${name} must be in the form owner/repo without path traversal`
|
||||||
*/
|
*/
|
||||||
export function requireRepository(name, value) {
|
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`)
|
throw new Error(`${name} must be in the form owner/repo without path traversal`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user