JefferyandClaude Opus 4.8 da83f4fc30 fix(calculate-version): 強化片段截斷與 repository 驗證健全性
- releases 改以 Array.from 依字元截斷回應片段,避免拆分多位元組字元造成亂碼,並標記片段內容
- config assertRepository 加入每段長度上限,並將 valid 更名為語義明確的 isRepoFormatValid

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 14:05:08 +08:00
2026-06-25 13:47:14 +00:00

calculate-version

更新時間:2026/06/26 10:49:46

計算版本號的 Gitea Action。依現有 release 推算下一個穩定版或 beta 版本號,並將結果寫入 Action output version。核心邏輯以 Node.js 實作,置於 app/,由 entrypoint.sh 作為容器進入點啟動。

版本進位規則:patch + 1;當 patch 達 10 進位至 minorminor 達 10 進位至 major。beta 版本號形如 <next>-beta.<n>,其中 <n> 為該版本既有 beta 標籤的最大序號加 1。

專案列表

專案描述

專案名稱 專案描述
calculate-version 計算版本號的 Gitea Action:載入並驗證環境設定、分頁抓取 Gitea release、計算下一個穩定版或 beta 版本號,並寫出 Action output。

參考專案

專案名稱 參考專案列表
calculate-version

NuGet 套件

專案名稱 NuGet 套件列表
calculate-version 無(zero-dependency;僅使用 Node.js 內建模組與全域 fetch

功能列表

indexapp/index.js

功能名稱 功能描述
index.main Action 進入點:協調設定載入、release 取得、版本計算與輸出(支援相依注入)。

loggerapp/logger.js

功能名稱 功能描述
logger.section 輸出帶標題的區塊段落至標準輸出,標題前後以分隔線包夾。
logger.info 輸出一般資訊層級的 log 訊息,自動加上 [info] 前綴。
logger.error 輸出錯誤訊息至 stderr(僅輸出,不終止行程)。

configapp/config.js

功能名稱 功能描述
config.isUnset 判斷環境變數值是否視為「未設定」。
config.requireEnv 驗證必填環境變數,未設定時拋出錯誤。
config.normalizeBetaFlag 將 beta 旗標正規化為布林值。
config.loadConfig 從環境變數載入並驗證執行所需的設定。

versionapp/version.js

功能名稱 功能描述
version.compareVersionArrays 逐區段比較兩個版本號數值陣列,較短者視為較小。
version.parseStableVersions 從 release 清單解析出所有穩定版的版本號數值陣列。
version.latestStableVersion 取得 release 清單中最新的穩定版版本號字串。
version.nextReleaseVersion 依最新穩定版計算下一個發行版本號。
version.nextBetaNumber 計算指定版本號的下一個 beta 流水號。
version.calculateVersion 計算最新穩定版與下一個版本號(支援 beta)。

releasesapp/releases.js

功能名稱 功能描述
releases.fetchReleases 以分頁方式取得指定 Gitea repo 的所有 release。

outputapp/output.js

功能名稱 功能描述
output.writeOutput 將一行 name=value 附加寫入 Action 的輸出檔。

使用範例

index.main

Action 進入點:依序協調設定載入、release 取得、版本計算與輸出寫出,回傳計算出的版本號。失敗時向外拋出例外(由進入點頂層攔截並以狀態碼 1 結束)。相依模組可透過 deps 注入,便於測試。模組僅在被直接執行(node app/index.js)時自動啟動。

const { main } = require('./index');

// 測試或自訂情境:注入假的相依
const version = await main({
  loadConfig: () => ({ serverUrl: 'https://gitea.example.com', repository: 'owner/repo', token: null, isBeta: false }),
  fetchReleases: async () => ([{ tag_name: 'v1.2.3' }]),
  writeOutput: () => {},
  log: { section() {}, info() {}, error() {} },
});
// version === '1.2.4'

logger.section

輸出帶標題的區塊段落至標準輸出:先一個換行,接著 50 個 = 的主分隔線、標題文字,最後 50 個 - 的次分隔線,用於在 log 中建立可視段落區隔。

const logger = require('./logger');

logger.section('參數檢查');
// 輸出:
//
// ==================================================
// 參數檢查
// --------------------------------------------------

logger.info

輸出一般資訊層級訊息至標準輸出,自動加上 [info] 前綴與換行;不會結束行程。

const logger = require('./logger');

logger.info('IS_BETA=false');
// 輸出:[info] IS_BETA=false

logger.error

輸出錯誤訊息至標準錯誤輸出(stderr),自動加上 [error] 前綴與換行。僅負責輸出,不終止行程;是否結束由呼叫端(進入點)決定,以利測試與錯誤復原。

const logger = require('./logger');

logger.error('GITEA_SERVER_URL 未設定');
// 對 stderr 輸出:[error] GITEA_SERVER_URL 未設定

config.isUnset

判斷環境變數值是否視為「未設定」。undefinednull、空字串、字面字串 "null" 皆視為未設定。

const { isUnset } = require('./config');

isUnset(undefined); // true
isUnset('null');    // true
isUnset('false');   // false

config.requireEnv

驗證必填環境變數;未設定時拋出 Error(訊息為 ${name} 未設定),已設定時原樣回傳值。

const { requireEnv } = require('./config');

const url = requireEnv('GITEA_SERVER_URL', process.env.GITEA_SERVER_URL);
// 未設定時拋出:Error: GITEA_SERVER_URL 未設定

config.normalizeBetaFlag

將 beta 旗標正規化為布林值。未設定時預設為 false;僅當值嚴格等於字面字串 "true" 時回傳 true

const { normalizeBetaFlag } = require('./config');

normalizeBetaFlag('true');  // true
normalizeBetaFlag('TRUE');  // false(嚴格比較,不做大小寫轉換)
normalizeBetaFlag(undefined); // false

config.loadConfig

從環境變數載入並驗證執行所需的設定。GITEA_SERVER_URLGITEA_REPOSITORY 為必填(未設定即拋錯),且 GITEA_SERVER_URL 須為合法的 http/https URLGITEA_TOKEN 非必填(未設定為 null);IS_BETA 會正規化為布林值。

const { loadConfig } = require('./config');

const config = loadConfig({
  GITEA_SERVER_URL: 'https://gitea.example.com',
  GITEA_REPOSITORY: 'owner/repo',
  IS_BETA: 'true',
});
// => { serverUrl: 'https://gitea.example.com', repository: 'owner/repo', token: null, isBeta: true }

version.compareVersionArrays

逐區段(element-wise)比較兩個版本號數值陣列,較短的陣列視為較小。回傳值 > 0 表示 a 大於 b、< 0 表示 a 小於 b、0 表示相等。

const { compareVersionArrays } = require('./version');

compareVersionArrays([1, 10], [1, 2, 3]); // > 01.10 > 1.2.3
compareVersionArrays([1, 2], [1, 2, 0]);  // < 0(較短者較小)

version.parseStableVersions

從 release 清單解析出所有穩定版(排除 beta、排除格式不合法者)的版本號數值陣列;會去除 tag 開頭的 v

const { parseStableVersions } = require('./version');

parseStableVersions([
  { tag_name: 'v1.2.3' },
  { tag_name: '2.0.0' },
  { tag_name: 'v1.0.0-beta.1' }, // 被排除
]);
// => [[1, 2, 3], [2, 0, 0]]

version.latestStableVersion

取得 release 清單中最新(最大)的穩定版版本號字串,固定格式化為三段 major.minor.patch;查無穩定版時回傳 "0.0.0"

const { latestStableVersion } = require('./version');

latestStableVersion([{ tag_name: 'v1.2.3' }, { tag_name: 'v1.9.9' }]); // "1.9.9"
latestStableVersion([]); // "0.0.0"

version.nextReleaseVersion

依最新穩定版字串計算下一個發行版本號:patch + 1patch 達 10 進位至 minorminor 達 10 進位至 major

const { nextReleaseVersion } = require('./version');

nextReleaseVersion('1.2.8'); // "1.2.9"
nextReleaseVersion('1.2.9'); // "1.3.0"
nextReleaseVersion('1.9.9'); // "2.0.0"

version.nextBetaNumber

計算指定版本號的下一個 beta 流水號:取現有相符 beta 標籤(v<version>-beta.<n>)的最大序號加 1,查無時回傳 1version 參數不應含前綴 v

const { nextBetaNumber } = require('./version');

nextBetaNumber([{ tag_name: 'v1.3.0-beta.1' }, { tag_name: 'v1.3.0-beta.3' }], '1.3.0'); // 4
nextBetaNumber([], '1.3.0'); // 1

version.calculateVersion

計算最新穩定版與下一個版本號;isBetatrue 時產生 beta 版本號。回傳物件 { latest, version }

const { calculateVersion } = require('./version');

calculateVersion([{ tag_name: 'v1.2.3' }], false);
// => { latest: '1.2.3', version: '1.2.4' }

calculateVersion([{ tag_name: 'v1.2.3' }], true);
// => { latest: '1.2.3', version: '1.2.4-beta.1' }

releases.fetchReleases

以分頁方式取得指定 Gitea repo 的所有 release,並回傳合併後的陣列。使用全域 fetch 逐頁請求(每頁 limit 為 10);當某頁回傳空資料、null 或筆數少於上限時即停止。提供 options.token 時以授權方式請求。網路失敗、回應讀取失敗、非 2xx、無法解析或非陣列回應時拋出 Error(含頁碼)。

const { fetchReleases } = require('./releases');
const logger = require('./logger');

const releases = await fetchReleases(
  'https://gitea.example.com/api/v1/repos/owner/repo/releases',
  { token: process.env.GITEA_TOKEN, logger },
);
// => 所有 release 物件合併後的陣列

output.writeOutput

將一行 name=value 附加寫入 GitHub/Gitea Action 的輸出檔(預設取 process.env.GITHUB_OUTPUT)。輸出檔路徑為 falsy 時拋出 Error

const { writeOutput } = require('./output');

writeOutput('version', '1.2.4');
// 對 $GITHUB_OUTPUT 附加一行:version=1.2.4
2026-06-30 10:24:24 +00:00
Languages
JavaScript 91.3%
Dockerfile 5.3%
Shell 3.4%