Files
Jeffery 4b450ae2d8
CI / Release Tag Version (pull_request) Successful in 4s
CI / Calculate Version (pull_request) Successful in 21s
docs(README): 同步 logger 功能列表與使用範例(移除 section、新增 forStage)
2026-06-30 18:22:27 +08:00

13 KiB
Raw Permalink Blame History

calculate-version

計算版本號的 Gitea Action:依儲存庫現有的 release,推算下一個穩定版或 beta 版本號,並寫出為 Action output 供後續步驟取用。以 Docker 容器執行,容器內由 Node.js 主程式實作。

更新時間:2026/06/30 18:18:13Asia/Taipei

專案列表

專案描述

專案名稱 專案描述
calculate-version 計算版本號的 Gitea Action:載入並驗證環境變數設定、分頁取得 Gitea release、解析最新穩定版並推算下一個穩定版或 beta 版本號,最後將結果寫入 Action output。

參考專案

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

NuGet 套件

專案名稱 NuGet 套件列表
calculate-version 無(本專案為純 Node.jspackage.json 未宣告任何 npm 相依)

功能列表

calculate-version

功能名稱 功能描述
config.isUnset 判斷環境變數值是否視為「未設定」(undefinednull/空字串/字面 "null")。
config.requireEnv 驗證必填環境變數,未設定時拋出錯誤。
config.normalizeBetaFlag 將 beta 旗標正規化為布林值(僅字面 "true" 為真)。
config.loadConfig 從環境變數載入並驗證執行所需的設定。
index.main Action 進入點:依序執行參數檢查、取得舊版本、計算版本號並寫出 output。
logger.info 輸出 [階段][INF][時間] 格式的資訊訊息至標準輸出(階段選填)。
logger.error 輸出 [階段][ERR][時間] 格式的錯誤訊息至標準錯誤輸出(階段選填)。
logger.forStage 建立綁定特定階段的子記錄器,自動為每則訊息帶入階段前綴。
output.writeOutput 將一行 name=value 附加寫入 Action 的輸出檔。
releases.fetchReleases 以分頁方式取得指定 Gitea repo 的所有 release。
version.compareVersionArrays 逐區段比較兩個版本號數值陣列,較短者視為較小。
version.parseStableVersions 從 release 清單解析出所有穩定版的版本號數值陣列。
version.latestStableVersion 取得最新(最大)的穩定版版本號字串。
version.nextReleaseVersion 依最新穩定版計算下一個發行版本號(逢 10 進位)。
version.nextBetaNumber 計算指定版本號的下一個 beta 流水號。
version.nextVersion 依已知的最新穩定版計算本次要使用的版本號(含 beta)。
version.calculateVersion 計算最新穩定版與下一個版本號。

使用範例

config.isUnset

判斷單一環境變數值是否應視為「未設定」。下列任一情況回傳 trueundefinednull、空字串、字面字串 "null";其餘回傳 false。常用於 loadConfig 內判斷必填與選填設定。

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

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

config.requireEnv

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

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

const url = requireEnv('GITEA_SERVER_URL', process.env.GITEA_SERVER_URL);
// 未設定時 → throw Error('GITEA_SERVER_URL 未設定')

config.normalizeBetaFlag

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

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

normalizeBetaFlag('true');    // true
normalizeBetaFlag('false');   // false
normalizeBetaFlag(undefined); // false

config.loadConfig

從環境變數載入並驗證執行所需設定。GITEA_SERVER_URL(須為合法 http/https URL)與 GITEA_REPOSITORY(須為 owner/repo 形式、排除路徑穿越)為必填;GITEA_TOKEN 選填(未設定為 null);IS_BETA 正規化為布林值。驗證失敗會拋出 Error

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

const config = loadConfig(process.env);
// → { serverUrl, repository, token, isBeta }

index.main

Action 進入點,依序執行三個階段:1. 參數檢查(loadConfig)、2. 取得舊版本(fetchReleases + latestStableVersion)、3. 計算版本號(nextVersion)並以 writeOutput 寫出。相依模組可透過 deps 注入以利測試;失敗時向外拋出例外。

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

// 以預設相依(讀環境變數、實際打 API)執行
await main();

// 測試時注入替身
await main({
  loadConfig: () => ({ serverUrl: 'https://gitea.example.com', repository: 'owner/repo', token: null, isBeta: false }),
  fetchReleases: async () => [{ tag_name: 'v1.2.3' }],
  writeOutput: () => {},
});
// → 回傳 '1.2.4'

logger.info

輸出一般資訊(INF)層級的 log 訊息至標準輸出,格式統一為 [{階段}][INF][{台灣時間}]: {訊息}(時間為 Asia/Taipei,格式 yyyy/MM/dd HH:mm:ss),結尾換行。階段(stage)為選填,省略時略過 [{階段}] 區塊。

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

logger.info('NEW_VERSION=1.2.4');
// 輸出:[INF][2026/06/30 17:38:51]: NEW_VERSION=1.2.4

logger.info('NEW_VERSION=1.2.4', '計算版本號');
// 輸出:[計算版本號][INF][2026/06/30 17:38:51]: NEW_VERSION=1.2.4

logger.error

輸出錯誤(ERR)層級的 log 訊息至標準錯誤輸出(stderr),格式統一為 [{階段}][ERR][{台灣時間}]: {訊息}。階段為選填。僅負責輸出,不終止行程,是否結束由呼叫端決定。

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

logger.error('GITEA_SERVER_URL 未設定');
// stderr 輸出:[ERR][2026/06/30 17:38:51]: GITEA_SERVER_URL 未設定

logger.forStage

建立綁定特定階段(stage)的子記錄器,回傳 { info, error };其方法只需傳入 message,即會自動在每則訊息開頭帶入 [{階段}] 前綴。進入點以此為每個處理階段建立子記錄器,使該階段(含其呼叫的 fetchReleases 等模組)輸出的訊息都帶一致的階段前綴。

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

const stageLog = logger.forStage('取得舊版本');
stageLog.info('使用授權 token 取得 release');
// 輸出:[取得舊版本][INF][2026/06/30 17:38:51]: 使用授權 token 取得 release

output.writeOutput

將一行 name=value 以 append 方式寫入 GitHub/Gitea Action 的輸出檔(預設取自環境變數 GITHUB_OUTPUT)。輸出檔路徑為 falsy 時拋出 Error。value 僅支援單行字串。

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

writeOutput('version', '1.2.4');
// 對 process.env.GITHUB_OUTPUT 指向的檔案附加一行:version=1.2.4

releases.fetchReleases

以分頁方式(每頁 10 筆)透過全域 fetch 取得指定 Gitea repo 的所有 release,回傳合併後的陣列。有 token 時以 Authorization: token <token> 授權,否則匿名請求;遇空頁或不足一頁即停止。請求失敗、非 2xx、無法解析或非陣列時拋出 Error

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

const releases = await fetchReleases(
  'https://gitea.example.com/api/v1/repos/owner/repo/releases',
  { token: process.env.GITEA_TOKEN, logger },
);

version.compareVersionArrays

逐區段(element-wise)比較兩個版本號數值陣列,較短的陣列視為較小。回傳大於 0 表示 a 大於 b、小於 0 表示 a 小於 b、0 表示相等;相異區段回傳的是該段差值(非固定 ±1)。傳入 nullundefined 會在讀取 .length 時拋 TypeError

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

compareVersionArrays([1, 10, 0], [1, 9, 0]); // > 0
compareVersionArrays([1, 2], [1, 2, 0]);     // < 0
compareVersionArrays([1, 2, 3], [1, 2, 3]);  // 0

version.parseStableVersions

從 release 清單解析出所有穩定版(排除含 -beta. 與格式不合法者)的版本號數值陣列;會去除 tag 開頭的 v。輸入非陣列時回傳空陣列。

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

parseStableVersions([
  { tag_name: 'v1.2.3' },
  { tag_name: '2.0.0' },
  { tag_name: 'v1.3.0-beta.1' },
]);
// → [[1, 2, 3], [2, 0, 0]]

version.latestStableVersion

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

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

latestStableVersion([{ tag_name: 'v1.9.0' }, { tag_name: 'v1.10.0' }]); // '1.10.0'
latestStableVersion([]); // '0.0.0'

version.nextReleaseVersion

依最新穩定版字串計算下一個發行版本號:patch 加 1,patch 達 10 進位至 minorminor 達 10 進位至 major(逢 10 進位為本專案自訂約定,非標準 SemVer)。

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

nextReleaseVersion('1.2.3'); // '1.2.4'
nextReleaseVersion('1.2.9'); // '1.3.0'
nextReleaseVersion('1.9.9'); // '2.0.0'

version.nextBetaNumber

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

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

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

version.nextVersion

依「已知的最新穩定版」計算本次要使用的版本號(不負責取得舊版本)。isBetafalse 時回傳下一個發行版;為 true 時回傳 <next>-beta.<n>

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

nextVersion('1.2.3', [], false); // '1.2.4'
nextVersion('1.2.3', [{ tag_name: 'v1.2.4-beta.2' }], true); // '1.2.4-beta.3'

version.calculateVersion

計算最新穩定版與下一個版本號;組合 latestStableVersionnextVersion。回傳物件含 latest(現有最新穩定版,查無為 "0.0.0")與 version(本次版本號)。

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

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

calculateVersion([{ tag_name: 'v1.2.3' }, { tag_name: 'v1.2.4-beta.2' }], true);
// → { latest: '1.2.3', version: '1.2.4-beta.3' }