From bc4cea6daf83221ccd9fd629050ac80b39ff5437 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Fri, 26 Jun 2026 15:21:45 +0800 Subject: [PATCH] =?UTF-8?q?refactor(calculate-version):=20=E6=8A=BD?= =?UTF-8?q?=E5=87=BA=20truncateString=20=E4=B8=A6=E6=98=8E=E7=A2=BA?= =?UTF-8?q?=E5=8C=96=20repository=20=E9=A9=97=E8=AD=89=E5=91=BD=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - releases 將片段截斷邏輯抽為可重用、可獨立測試的 truncateString(text, limit) 並 export - config 將 assertRepository 更名為 assertGiteaRepositoryFormat,凸顯其驗證 Gitea owner/repo 格式的領域語義 Co-Authored-By: Claude Opus 4.8 (1M context) --- app/config.js | 4 ++-- app/releases.js | 14 +++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/config.js b/app/config.js index 8de6c1f..2c482cf 100644 --- a/app/config.js +++ b/app/config.js @@ -68,7 +68,7 @@ function isValidRepoSegment(part) { } // 驗證 repository 為 owner/repo 格式(恰兩段,且每段皆為合法區段) -function assertRepository(name, value) { +function assertGiteaRepositoryFormat(name, value) { const parts = value.split('/'); const isRepoFormatValid = parts.length === 2 && parts.every(isValidRepoSegment); if (!isRepoFormatValid) { @@ -91,7 +91,7 @@ function loadConfig(env = process.env) { const serverUrl = requireEnv('GITEA_SERVER_URL', env.GITEA_SERVER_URL); assertHttpUrl('GITEA_SERVER_URL', serverUrl); 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 isBeta = normalizeBetaFlag(env.IS_BETA); diff --git a/app/releases.js b/app/releases.js index 0077843..89e3887 100644 --- a/app/releases.js +++ b/app/releases.js @@ -6,6 +6,13 @@ const RELEASES_PER_PAGE = 10; // JSON 解析失敗時,附在錯誤訊息中的回應內容片段最大字元數 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,並回傳合併後的陣列。 * @@ -66,10 +73,7 @@ async function fetchReleases(baseUrl, options = {}) { try { pageJson = JSON.parse(text); } catch { - // 先以 UTF-16 長度粗略上限截斷(避免將可能極大的回應整個陣列化), - // 再以字元(而非 UTF-16 碼元)精準截斷,避免拆分多位元組字元造成亂碼 - const boundedText = text.slice(0, API_ERROR_SNIPPET_LENGTH * 2); - const contentSnippet = Array.from(boundedText).slice(0, API_ERROR_SNIPPET_LENGTH).join(''); + const contentSnippet = truncateString(text, API_ERROR_SNIPPET_LENGTH); throw new Error(`release API 回傳資料無法解析 (page=${page}),回應內容片段:「${contentSnippet}」`); } @@ -96,4 +100,4 @@ async function fetchReleases(baseUrl, options = {}) { return combined; } -module.exports = { RELEASES_PER_PAGE, fetchReleases }; +module.exports = { RELEASES_PER_PAGE, truncateString, fetchReleases };