'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 進位至 minor,minor 達 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-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 形如 "-beta."。 * @returns {{latest: string, version: string}} 回傳物件: * - latest: 現有最新穩定版版本號字串(查無時為 "0.0.0")。 * - version: 本次要使用的版本號字串;非 beta 為下一個發行版(如 "1.3.0"),beta 為 "-beta."(如 "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, };