refactor(calculate-version): 抽出 truncateString 並明確化 repository 驗證命名

- releases 將片段截斷邏輯抽為可重用、可獨立測試的 truncateString(text, limit) 並 export
- config 將 assertRepository 更名為 assertGiteaRepositoryFormat,凸顯其驗證 Gitea owner/repo 格式的領域語義

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jeffery
2026-06-26 15:21:45 +08:00
co-authored by Claude Opus 4.8
parent 84a936a2a4
commit bc4cea6daf
2 changed files with 11 additions and 7 deletions
+2 -2
View File
@@ -68,7 +68,7 @@ function isValidRepoSegment(part) {
} }
// 驗證 repository 為 owner/repo 格式(恰兩段,且每段皆為合法區段) // 驗證 repository 為 owner/repo 格式(恰兩段,且每段皆為合法區段)
function assertRepository(name, value) { function assertGiteaRepositoryFormat(name, value) {
const parts = value.split('/'); const parts = value.split('/');
const isRepoFormatValid = parts.length === 2 && parts.every(isValidRepoSegment); const isRepoFormatValid = parts.length === 2 && parts.every(isValidRepoSegment);
if (!isRepoFormatValid) { if (!isRepoFormatValid) {
@@ -91,7 +91,7 @@ function loadConfig(env = process.env) {
const serverUrl = requireEnv('GITEA_SERVER_URL', env.GITEA_SERVER_URL); const serverUrl = requireEnv('GITEA_SERVER_URL', env.GITEA_SERVER_URL);
assertHttpUrl('GITEA_SERVER_URL', serverUrl); assertHttpUrl('GITEA_SERVER_URL', serverUrl);
const repository = requireEnv('GITEA_REPOSITORY', env.GITEA_REPOSITORY); const repository = requireEnv('GITEA_REPOSITORY', env.GITEA_REPOSITORY);
assertRepository('GITEA_REPOSITORY', repository); assertGiteaRepositoryFormat('GITEA_REPOSITORY', repository);
const token = isUnset(env.GITEA_TOKEN) ? null : env.GITEA_TOKEN; const token = isUnset(env.GITEA_TOKEN) ? null : env.GITEA_TOKEN;
const isBeta = normalizeBetaFlag(env.IS_BETA); const isBeta = normalizeBetaFlag(env.IS_BETA);
+9 -5
View File
@@ -6,6 +6,13 @@ const RELEASES_PER_PAGE = 10;
// JSON 解析失敗時,附在錯誤訊息中的回應內容片段最大字元數 // JSON 解析失敗時,附在錯誤訊息中的回應內容片段最大字元數
const API_ERROR_SNIPPET_LENGTH = 200; const API_ERROR_SNIPPET_LENGTH = 200;
// 以字元(而非 UTF-16 碼元)安全截斷字串至 limit 個字元,避免拆分多位元組字元造成亂碼;
// 先以長度上限粗略截掉過長輸入,避免將可能極大的字串整個陣列化
function truncateString(text, limit) {
const bounded = text.slice(0, limit * 2);
return Array.from(bounded).slice(0, limit).join('');
}
/** /**
* 以分頁方式取得指定 Gitea repo 的所有 release,並回傳合併後的陣列。 * 以分頁方式取得指定 Gitea repo 的所有 release,並回傳合併後的陣列。
* *
@@ -66,10 +73,7 @@ async function fetchReleases(baseUrl, options = {}) {
try { try {
pageJson = JSON.parse(text); pageJson = JSON.parse(text);
} catch { } catch {
// 先以 UTF-16 長度粗略上限截斷(避免將可能極大的回應整個陣列化), const contentSnippet = truncateString(text, API_ERROR_SNIPPET_LENGTH);
// 再以字元(而非 UTF-16 碼元)精準截斷,避免拆分多位元組字元造成亂碼
const boundedText = text.slice(0, API_ERROR_SNIPPET_LENGTH * 2);
const contentSnippet = Array.from(boundedText).slice(0, API_ERROR_SNIPPET_LENGTH).join('');
throw new Error(`release API 回傳資料無法解析 (page=${page}),回應內容片段:「${contentSnippet}`); throw new Error(`release API 回傳資料無法解析 (page=${page}),回應內容片段:「${contentSnippet}`);
} }
@@ -96,4 +100,4 @@ async function fetchReleases(baseUrl, options = {}) {
return combined; return combined;
} }
module.exports = { RELEASES_PER_PAGE, fetchReleases }; module.exports = { RELEASES_PER_PAGE, truncateString, fetchReleases };