Merge pull request 'refactor(版本計算流程): 將 main 拆成參數檢查、取得舊版本、計算版本號三階段' (#10) from develop into master
CD / Release Tag Version (push) Successful in 5s

Reviewed-on: docker-actions/calculate-version#10
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
This commit was merged in pull request #10.
This commit is contained in:
2026-06-30 08:28:57 +00:00
4 changed files with 64 additions and 33 deletions
+15 -19
View File
@@ -16,25 +16,21 @@ on:
# 僅在 PR 開啟 (opened) 或有新 commit 推送 (synchronize) 時觸發。 # 僅在 PR 開啟 (opened) 或有新 commit 推送 (synchronize) 時觸發。
types: [opened, synchronize] types: [opened, synchronize]
jobs: jobs:
# job 識別碼:AI 程式碼審查。 release-tag-version:
ai-code-review: name: Release Tag Version
# job 顯示名稱。
name: AI Code Review
# 指定執行於 ubuntu runner。
runs-on: ubuntu runs-on: ubuntu
# 此 job 所需的權限 (供 AI 寫回審查結果)。 outputs:
permissions: version: ${{ steps.release-tag-version.outputs.version }}
# 可寫入儲存庫內容。
contents: write
# 可在 PR 上留言/變更狀態。
pull-requests: write
# 可建立/更新 issue。
issues: write
steps: steps:
# 呼叫共用的 OpenCode 程式碼審查 composite action。 - name: 釋出並標註成品版本
- name: AI 程式碼審查 by OpenCode id: release-tag-version
# 版本以變數 ACTION_OPENCODE_CODE_REVIEW_VERSION 控制,便於統一升級。 uses: https://gitea.jsc.idv.tw/composite-actions/release-tag-version@${{ vars.ACTION_RELEASE_TAG_VERSION }}
uses: https://gitea.jsc.idv.tw/composite-actions/opencode-code-review@${{ vars.ACTION_OPENCODE_CODE_REVIEW_VERSION }}
with: with:
# 傳入留言用 token (secret),供 action 在 PR 上發表審查留言。 is_beta: true
comment_token: ${{ secrets.COMMENT_TOKEN }} calculate-version:
name: Calculate Version
runs-on: ubuntu
needs: release-tag-version
steps:
- name: 計算版本號
uses: https://gitea.jsc.idv.tw/docker-actions/calculate-version@v${{ needs.release-tag-version.outputs.version }}
+16 -8
View File
@@ -3,7 +3,7 @@
const logger = require('./logger'); const logger = require('./logger');
const { loadConfig } = require('./config'); const { loadConfig } = require('./config');
const { fetchReleases } = require('./releases'); const { fetchReleases } = require('./releases');
const { calculateVersion } = require('./version'); const { latestStableVersion, nextVersion } = require('./version');
const { writeOutput } = require('./output'); const { writeOutput } = require('./output');
/** /**
@@ -12,15 +12,18 @@ const { writeOutput } = require('./output');
* 失敗時(loadConfig 或 fetchReleases 拋錯)直接向外拋出例外,由呼叫端決定如何結束, * 失敗時(loadConfig 或 fetchReleases 拋錯)直接向外拋出例外,由呼叫端決定如何結束,
* 以利單元測試覆蓋失敗路徑。相依模組可透過 deps 注入,預設使用各兄弟模組的實作。 * 以利單元測試覆蓋失敗路徑。相依模組可透過 deps 注入,預設使用各兄弟模組的實作。
* *
* 流程分為三個階段:1. 參數檢查 2. 取得舊版本 3. 計算版本號。
*
* @param {Object} [deps={}] - 可注入的相依,供測試替換。 * @param {Object} [deps={}] - 可注入的相依,供測試替換。
* @param {Function} [deps.loadConfig] - 載入設定的函式。 * @param {Function} [deps.loadConfig] - 載入設定的函式。
* @param {Function} [deps.fetchReleases] - 取得 release 的函式。 * @param {Function} [deps.fetchReleases] - 取得 release 的函式。
* @param {Function} [deps.calculateVersion] - 計算版本的函式。 * @param {Function} [deps.latestStableVersion] - 從 release 清單取得最新穩定版(舊版本的函式。
* @param {Function} [deps.nextVersion] - 依舊版本計算本次版本號的函式。
* @param {Function} [deps.writeOutput] - 寫出 output 的函式。 * @param {Function} [deps.writeOutput] - 寫出 output 的函式。
* @param {{section:Function, info:Function, error:Function}} [deps.log] - log 記錄器。 * @param {{section:Function, info:Function, error:Function}} [deps.log] - log 記錄器。
* @returns {Promise<string>} 計算出的版本號。 * @returns {Promise<string>} 計算出的版本號。
* @throws {Error} 當任一注入相依(loadConfigfetchReleasescalculateVersionwriteOutput拋出例外時, * @throws {Error} 當任一注入相依(loadConfigfetchReleaseslatestStableVersionnextVersionwriteOutput
* 原樣向外傳播,由呼叫端決定如何結束。 * 拋出例外時,原樣向外傳播,由呼叫端決定如何結束。
* @remarks 由模組底部的 require.main 守衛在被直接執行時呼叫;失敗時頂層以模組層級 logger.error 輸出 * @remarks 由模組底部的 require.main 守衛在被直接執行時呼叫;失敗時頂層以模組層級 logger.error 輸出
* 並 exit(1)。單元測試可透過 deps 注入替身以覆蓋成功與各失敗路徑。 * 並 exit(1)。單元測試可透過 deps 注入替身以覆蓋成功與各失敗路徑。
*/ */
@@ -28,11 +31,13 @@ async function main(deps = {}) {
const { const {
loadConfig: loadConfigFn = loadConfig, loadConfig: loadConfigFn = loadConfig,
fetchReleases: fetchReleasesFn = fetchReleases, fetchReleases: fetchReleasesFn = fetchReleases,
calculateVersion: calculateVersionFn = calculateVersion, latestStableVersion: latestStableVersionFn = latestStableVersion,
nextVersion: nextVersionFn = nextVersion,
writeOutput: writeOutputFn = writeOutput, writeOutput: writeOutputFn = writeOutput,
log = logger, log = logger,
} = deps; } = deps;
// 1. 參數檢查:載入並驗證環境變數設定
log.section('參數檢查'); log.section('參數檢查');
const config = loadConfigFn(); const config = loadConfigFn();
@@ -41,17 +46,20 @@ async function main(deps = {}) {
log.info(config.token ? 'GITEA_TOKEN=***' : 'GITEA_TOKEN=未提供'); log.info(config.token ? 'GITEA_TOKEN=***' : 'GITEA_TOKEN=未提供');
log.info(`IS_BETA=${config.isBeta}`); log.info(`IS_BETA=${config.isBeta}`);
log.section('取得版本資料'); // 2. 取得版本:抓取 release 清單並解析出最新穩定版
log.section('取得舊版本');
const releaseUrl = `${config.serverUrl}/api/v1/repos/${config.repository}/releases`; const releaseUrl = `${config.serverUrl}/api/v1/repos/${config.repository}/releases`;
log.info(`RELEASE_URL=${releaseUrl}`); log.info(`RELEASE_URL=${releaseUrl}`);
const releases = await fetchReleasesFn(releaseUrl, { token: config.token, logger: log }); const releases = await fetchReleasesFn(releaseUrl, { token: config.token, logger: log });
const latest = latestStableVersionFn(releases);
const { latest, version } = calculateVersionFn(releases, config.isBeta);
log.info(`LATEST_VERSION=${latest}`); log.info(`LATEST_VERSION=${latest}`);
// 3. 計算版本號:依舊版本算出本次版本號並寫出 output
log.section('計算版本號'); log.section('計算版本號');
const version = nextVersionFn(latest, releases, config.isBeta);
log.info(`NEW_VERSION=${version}`); log.info(`NEW_VERSION=${version}`);
writeOutputFn('version', version); writeOutputFn('version', version);
+11
View File
@@ -8,6 +8,7 @@ const {
latestStableVersion, latestStableVersion,
nextReleaseVersion, nextReleaseVersion,
nextBetaNumber, nextBetaNumber,
nextVersion,
calculateVersion, calculateVersion,
} = require('../version'); } = require('../version');
@@ -77,6 +78,16 @@ test('nextBetaNumber 取對應 beta 流水號最大值 + 1', () => {
assert.equal(nextBetaNumber(releases, '1.2.4'), 4); assert.equal(nextBetaNumber(releases, '1.2.4'), 4);
}); });
test('nextVersion 依舊版本計算下一個穩定版', () => {
assert.equal(nextVersion('1.2.3', [], false), '1.2.4');
assert.equal(nextVersion('0.0.0', [], false), '0.0.1');
});
test('nextVersion 依舊版本計算下一個 beta 版', () => {
const releases = [{ tag_name: 'v1.2.4-beta.2' }];
assert.equal(nextVersion('1.2.3', releases, true), '1.2.4-beta.3');
});
test('calculateVersion 計算下一個穩定版', () => { test('calculateVersion 計算下一個穩定版', () => {
assert.deepEqual( assert.deepEqual(
calculateVersion([{ tag_name: 'v1.2.3' }], false), calculateVersion([{ tag_name: 'v1.2.3' }], false),
+22 -6
View File
@@ -115,6 +115,26 @@ function nextBetaNumber(releases, version) {
return max + 1; 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 版本號。 * 計算最新穩定版與下一個版本號;isBeta 為 true 時產生 beta 版本號。
* @param {Array<{tag_name?: string}>} releases - release 物件清單。 * @param {Array<{tag_name?: string}>} releases - release 物件清單。
@@ -125,13 +145,8 @@ function nextBetaNumber(releases, version) {
*/ */
function calculateVersion(releases, isBeta) { function calculateVersion(releases, isBeta) {
const latest = latestStableVersion(releases); const latest = latestStableVersion(releases);
const next = nextReleaseVersion(latest);
const version = isBeta return { latest, version: nextVersion(latest, releases, isBeta) };
? `${next}-beta.${nextBetaNumber(releases, next)}`
: next;
return { latest, version };
} }
module.exports = { module.exports = {
@@ -140,5 +155,6 @@ module.exports = {
latestStableVersion, latestStableVersion,
nextReleaseVersion, nextReleaseVersion,
nextBetaNumber, nextBetaNumber,
nextVersion,
calculateVersion, calculateVersion,
}; };