refactor(calculate-version): 將版本計算邏輯由 bash 改寫為 Node.js 並依功能分檔

保留 entrypoint.sh 作為容器進入點,改為啟動 app/ 下的 Node.js 程式;
依功能拆分為 logger/config/version/releases/output/index,並更新 Dockerfile 改用 node:lts-alpine。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jeffery
2026-06-26 10:41:34 +08:00
co-authored by Claude Opus 4.8
parent 4983a72524
commit bd6b6017ab
13 changed files with 734 additions and 264 deletions
+140
View File
@@ -0,0 +1,140 @@
'use strict';
// 每個版本號區段的進位上限:patch / minor 達到 10 即向上進位
const SEGMENT_LIMIT = 10;
/**
* 逐區段(element-wise)比較兩個版本號數值陣列,較短的陣列視為較小。
* @param {number[]} a - 第一個版本號區段陣列,例如 [1, 2, 3]。
* @param {number[]} b - 第二個版本號區段陣列,例如 [1, 2, 0]。
* @returns {number} 大於 0 表示 a 大於 b;小於 0 表示 a 小於 b;0 表示兩者相等。
*/
function compareVersionArrays(a, b) {
const length = Math.max(a.length, b.length);
for (let i = 0; i < length; i += 1) {
if (a[i] === undefined) {
return -1;
}
if (b[i] === undefined) {
return 1;
}
if (a[i] !== b[i]) {
return a[i] - b[i];
}
}
return 0;
}
/**
* 從 release 清單解析出所有穩定版(排除 beta、排除格式不合法者)的版本號數值陣列。
* 會去除 tag 開頭的 'v',並將版本字串切成數字陣列。
* @param {Array<{tag_name?: string}>} releases - release 物件清單;每個項目以 tag_name 表示版本標籤。
* @returns {number[][]} 穩定版版本號的數值陣列清單,例如 [[1,2,3],[1,2,0]];輸入非陣列時回傳空陣列。
*/
function parseStableVersions(releases) {
if (!Array.isArray(releases)) {
return [];
}
return releases
.filter((release) => release
&& typeof release.tag_name === 'string'
&& !release.tag_name.includes('-beta.'))
.map((release) => release.tag_name.replace(/^v/, ''))
.filter((tag) => /^[0-9]+(\.[0-9]+)*$/.test(tag))
.map((tag) => tag.split('.').map(Number));
}
/**
* 取得 release 清單中最新(最大)的穩定版版本號字串,固定格式化為三段 "major.minor.patch"。
* @param {Array<{tag_name?: string}>} releases - release 物件清單。
* @returns {string} 最新穩定版版本號字串;查無穩定版時回傳 "0.0.0"。
*/
function latestStableVersion(releases) {
const versions = parseStableVersions(releases);
if (versions.length === 0) {
return '0.0.0';
}
const max = versions.reduce(
(best, current) => (compareVersionArrays(current, best) > 0 ? current : best),
);
return `${max[0] ?? 0}.${max[1] ?? 0}.${max[2] ?? 0}`;
}
/**
* 依最新穩定版字串計算下一個發行版本號:patch 加 1,patch 達 10 進位至 minorminor 達 10 進位至 major。
* @param {string} latest - 最新穩定版版本號字串,例如 "1.2.9"。
* @returns {string} 下一個發行版本號字串,格式為 "major.minor.patch",例如 "1.3.0"。
*/
function nextReleaseVersion(latest) {
const parts = String(latest).split('.').map((part) => Number(part) || 0);
let [major = 0, minor = 0, patch = 0] = parts;
patch += 1;
if (patch >= SEGMENT_LIMIT) {
patch = 0;
minor += 1;
}
if (minor >= SEGMENT_LIMIT) {
minor = 0;
major += 1;
}
return `${major}.${minor}.${patch}`;
}
/**
* 計算指定版本號的下一個 beta 流水號:取現有相符 beta 標籤的最大序號加 1,查無時回傳 1。
* @param {Array<{tag_name?: string}>} releases - release 物件清單。
* @param {string} version - 不含前綴 'v' 的版本號字串,例如 "1.3.0";用於組出 "v<version>-beta." 前綴比對。
* @returns {number} 下一個 beta 流水號;查無對應 beta 時回傳 1。
*/
function nextBetaNumber(releases, version) {
if (!Array.isArray(releases)) {
return 1;
}
const prefix = `v${version}-beta.`;
const numbers = releases
.filter((release) => release
&& typeof release.tag_name === 'string'
&& release.tag_name.startsWith(prefix))
.map((release) => Number(release.tag_name.slice(prefix.length)))
.filter((number) => Number.isFinite(number));
const max = numbers.length > 0 ? Math.max(...numbers) : 0;
return max + 1;
}
/**
* 計算最新穩定版與下一個版本號;isBeta 為 true 時產生 beta 版本號。
* @param {Array<{tag_name?: string}>} releases - release 物件清單。
* @param {boolean} isBeta - 是否產生 beta 版本號;true 時 version 形如 "<next>-beta.<n>"。
* @returns {{latest: string, version: string}} 回傳物件:
* - latest: 現有最新穩定版版本號字串(查無時為 "0.0.0")。
* - version: 本次要使用的版本號字串;非 beta 為下一個發行版(如 "1.3.0"),beta 為 "<next>-beta.<n>"(如 "1.3.0-beta.1")。
*/
function calculateVersion(releases, isBeta) {
const latest = latestStableVersion(releases);
const next = nextReleaseVersion(latest);
const version = isBeta
? `${next}-beta.${nextBetaNumber(releases, next)}`
: next;
return { latest, version };
}
module.exports = {
compareVersionArrays,
parseStableVersions,
latestStableVersion,
nextReleaseVersion,
nextBetaNumber,
calculateVersion,
};