Files
calculate-version/app/version.js
T

161 lines
6.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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 表示兩者相等。
* 相異區段回傳的是該段差值(非固定 ±1)。
* @throws {TypeError} 當 a 或 b 非陣列(無 length 屬性)時拋出。
*/
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"。
* @remarks 「逢 10 進位」為本專案自訂約定(非標準 SemVer);輸入會先 String() 轉字串,
* 缺段或非數字段一律補 0(如 null/undefined → "0.0.1"),major 無進位上限。
*/
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 版本號。
*
* 此函式不負責「取得舊版本」(latest 由呼叫端傳入),僅負責「計算版本號」這一步,
* 讓取得舊版本與計算新版本兩個階段可各自獨立。
*
* @param {string} latest - 已知的最新穩定版版本號字串(如 "1.2.3";查無時為 "0.0.0")。
* @param {Array<{tag_name?: string}>} releases - release 物件清單;beta 流水號需據此比對。
* @param {boolean} isBeta - 是否產生 beta 版本號;true 時回傳形如 "<next>-beta.<n>"。
* @returns {string} 本次要使用的版本號字串;非 beta 為下一個發行版(如 "1.3.0"),
* beta 為 "<next>-beta.<n>"(如 "1.3.0-beta.1")。
*/
function nextVersion(latest, releases, isBeta) {
const next = nextReleaseVersion(latest);
return isBeta
? `${next}-beta.${nextBetaNumber(releases, next)}`
: next;
}
/**
* 計算最新穩定版與下一個版本號;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);
return { latest, version: nextVersion(latest, releases, isBeta) };
}
module.exports = {
compareVersionArrays,
parseStableVersions,
latestStableVersion,
nextReleaseVersion,
nextBetaNumber,
nextVersion,
calculateVersion,
};