refactor: calculate-version 由 bash 改寫為 Node.js #5
@@ -1,12 +1,27 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# 用途: calculate-version 專案的 CD workflow。
|
||||||
|
# 在程式碼推送至 master 分支時,自動釋出並標註 (tag) 成品版本。
|
||||||
|
# 更新日期: 2026/06/26 10:28:36
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# workflow 名稱,顯示於 Gitea Actions 介面。
|
||||||
name: CD
|
name: CD
|
||||||
|
# 觸發條件設定。
|
||||||
on:
|
on:
|
||||||
|
# 於 push 事件觸發。
|
||||||
push:
|
push:
|
||||||
|
# 僅在推送至 master 分支時觸發。
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
jobs:
|
jobs:
|
||||||
|
# job 識別碼:釋出並標註版本。
|
||||||
release-tag-version:
|
release-tag-version:
|
||||||
|
# job 顯示名稱。
|
||||||
name: Release Tag Version
|
name: Release Tag Version
|
||||||
|
# 指定執行於 ubuntu runner。
|
||||||
runs-on: ubuntu
|
runs-on: ubuntu
|
||||||
steps:
|
steps:
|
||||||
|
# 呼叫共用的 release-tag-version composite action,自動產生版本標籤並釋出。
|
||||||
- name: 釋出並標註成品版本
|
- name: 釋出並標註成品版本
|
||||||
|
# 版本以變數 ACTION_RELEASE_TAG_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/release-tag-version@${{ vars.ACTION_RELEASE_TAG_VERSION }}
|
||||||
|
|||||||
@@ -1,19 +1,40 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# 用途: calculate-version 專案的 CI workflow。
|
||||||
|
# 在 Pull Request 開啟或更新時,觸發 OpenCode AI 程式碼審查。
|
||||||
|
# 更新日期: 2026/06/26 10:28:36
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# workflow 名稱,顯示於 Gitea Actions 介面。
|
||||||
name: CI
|
name: CI
|
||||||
|
# 觸發條件設定。
|
||||||
on:
|
on:
|
||||||
|
# 於 Pull Request 事件觸發。
|
||||||
pull_request:
|
pull_request:
|
||||||
|
# 忽略目標分支為 master 的 PR (master 走 CD 流程,不在此審查)。
|
||||||
branches-ignore:
|
branches-ignore:
|
||||||
- master
|
- master
|
||||||
|
# 僅在 PR 開啟 (opened) 或有新 commit 推送 (synchronize) 時觸發。
|
||||||
types: [opened, synchronize]
|
types: [opened, synchronize]
|
||||||
jobs:
|
jobs:
|
||||||
|
# job 識別碼:AI 程式碼審查。
|
||||||
ai-code-review:
|
ai-code-review:
|
||||||
|
# job 顯示名稱。
|
||||||
name: AI Code Review
|
name: AI Code Review
|
||||||
|
# 指定執行於 ubuntu runner。
|
||||||
runs-on: ubuntu
|
runs-on: ubuntu
|
||||||
|
# 此 job 所需的權限 (供 AI 寫回審查結果)。
|
||||||
permissions:
|
permissions:
|
||||||
|
# 可寫入儲存庫內容。
|
||||||
contents: write
|
contents: write
|
||||||
|
# 可在 PR 上留言/變更狀態。
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
# 可建立/更新 issue。
|
||||||
issues: write
|
issues: write
|
||||||
steps:
|
steps:
|
||||||
|
# 呼叫共用的 OpenCode 程式碼審查 composite action。
|
||||||
- name: AI 程式碼審查 by OpenCode
|
- name: AI 程式碼審查 by OpenCode
|
||||||
|
# 版本以變數 ACTION_OPENCODE_CODE_REVIEW_VERSION 控制,便於統一升級。
|
||||||
uses: https://gitea.jsc.idv.tw/composite-actions/opencode-code-review@${{ vars.ACTION_OPENCODE_CODE_REVIEW_VERSION }}
|
uses: https://gitea.jsc.idv.tw/composite-actions/opencode-code-review@${{ vars.ACTION_OPENCODE_CODE_REVIEW_VERSION }}
|
||||||
with:
|
with:
|
||||||
|
# 傳入留言用 token (secret),供 action 在 PR 上發表審查留言。
|
||||||
comment_token: ${{ secrets.COMMENT_TOKEN }}
|
comment_token: ${{ secrets.COMMENT_TOKEN }}
|
||||||
|
|||||||
@@ -0,0 +1,274 @@
|
|||||||
|
# calculate-version
|
||||||
|
|
||||||
|
> 更新時間:2026/06/26 10:37:28
|
||||||
|
|
||||||
|
計算版本號的 Gitea Action。依現有 release 推算下一個穩定版或 beta 版本號,並將結果寫入 Action output `version`。核心邏輯以 Node.js 實作,置於 `app/`,由 `entrypoint.sh` 作為容器進入點啟動。
|
||||||
|
|
||||||
|
版本進位規則:`patch + 1`;當 `patch` 達 10 進位至 `minor`,`minor` 達 10 進位至 `major`。beta 版本號形如 `<next>-beta.<n>`,其中 `<n>` 為該版本既有 beta 標籤的最大序號加 1。
|
||||||
|
|
||||||
|
## 專案列表
|
||||||
|
|
||||||
|
### 專案描述
|
||||||
|
|
||||||
|
| 專案名稱 | 專案描述 |
|
||||||
|
| --- | --- |
|
||||||
|
| [calculate-version](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app) | 計算版本號的 Gitea Action:載入環境設定、分頁抓取 Gitea release、計算下一個穩定版或 beta 版本號,並寫出 Action output。 |
|
||||||
|
|
||||||
|
### 參考專案
|
||||||
|
|
||||||
|
| 專案名稱 | 參考專案列表 |
|
||||||
|
| --- | --- |
|
||||||
|
| [calculate-version](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app) | 無 |
|
||||||
|
|
||||||
|
### NuGet 套件
|
||||||
|
|
||||||
|
| 專案名稱 | NuGet 套件列表 |
|
||||||
|
| --- | --- |
|
||||||
|
| [calculate-version](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app) | 無(zero-dependency;僅使用 Node.js 內建模組與全域 fetch) |
|
||||||
|
|
||||||
|
## 功能列表
|
||||||
|
|
||||||
|
### logger(app/logger.js)
|
||||||
|
|
||||||
|
| 功能名稱 | 功能描述 |
|
||||||
|
| --- | --- |
|
||||||
|
| [logger.section](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/logger.js#L16) | [輸出帶標題的區塊段落至標準輸出,標題前後以分隔線包夾。](#loggersection) |
|
||||||
|
| [logger.info](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/logger.js#L26) | [輸出一般資訊層級的 log 訊息,自動加上 `[info]` 前綴。](#loggerinfo) |
|
||||||
|
| [logger.fail](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/logger.js#L40) | [輸出錯誤訊息至 stderr 並以狀態碼 1 終止行程。](#loggerfail) |
|
||||||
|
|
||||||
|
### config(app/config.js)
|
||||||
|
|
||||||
|
| 功能名稱 | 功能描述 |
|
||||||
|
| --- | --- |
|
||||||
|
| [config.isUnset](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/config.js#L11) | [判斷環境變數值是否視為「未設定」。](#configisunset) |
|
||||||
|
| [config.requireEnv](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/config.js#L23) | [驗證必填環境變數,未設定時拋出錯誤。](#configrequireenv) |
|
||||||
|
| [config.normalizeBetaFlag](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/config.js#L38) | [將 beta 旗標正規化為布林值。](#confignormalizebetaflag) |
|
||||||
|
| [config.loadConfig](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/config.js#L55) | [從環境變數載入並驗證執行所需的設定。](#configloadconfig) |
|
||||||
|
|
||||||
|
### version(app/version.js)
|
||||||
|
|
||||||
|
| 功能名稱 | 功能描述 |
|
||||||
|
| --- | --- |
|
||||||
|
| [version.compareVersionArrays](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/version.js#L12) | [逐區段比較兩個版本號數值陣列,較短者視為較小。](#versioncompareversionarrays) |
|
||||||
|
| [version.parseStableVersions](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/version.js#L36) | [從 release 清單解析出所有穩定版的版本號數值陣列。](#versionparsestableversions) |
|
||||||
|
| [version.latestStableVersion](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/version.js#L55) | [取得 release 清單中最新的穩定版版本號字串。](#versionlateststableversion) |
|
||||||
|
| [version.nextReleaseVersion](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/version.js#L74) | [依最新穩定版計算下一個發行版本號。](#versionnextreleaseversion) |
|
||||||
|
| [version.nextBetaNumber](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/version.js#L97) | [計算指定版本號的下一個 beta 流水號。](#versionnextbetanumber) |
|
||||||
|
| [version.calculateVersion](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/version.js#L122) | [計算最新穩定版與下一個版本號(支援 beta)。](#versioncalculateversion) |
|
||||||
|
|
||||||
|
### releases(app/releases.js)
|
||||||
|
|
||||||
|
| 功能名稱 | 功能描述 |
|
||||||
|
| --- | --- |
|
||||||
|
| [releases.fetchReleases](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/releases.js#L22) | [以分頁方式取得指定 Gitea repo 的所有 release。](#releasesfetchreleases) |
|
||||||
|
|
||||||
|
### output(app/output.js)
|
||||||
|
|
||||||
|
| 功能名稱 | 功能描述 |
|
||||||
|
| --- | --- |
|
||||||
|
| [output.writeOutput](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/output.js#L14) | [將一行 `name=value` 附加寫入 Action 的輸出檔。](#outputwriteoutput) |
|
||||||
|
|
||||||
|
## 使用範例
|
||||||
|
|
||||||
|
<a id="loggersection"></a>
|
||||||
|
### logger.section
|
||||||
|
|
||||||
|
輸出帶標題的區塊段落至標準輸出:先一個換行,接著 50 個 `=` 的主分隔線、標題文字,最後 50 個 `-` 的次分隔線,用於在 log 中建立可視段落區隔。
|
||||||
|
|
||||||
|
```js
|
||||||
|
const logger = require('./logger');
|
||||||
|
|
||||||
|
logger.section('參數檢查');
|
||||||
|
// 輸出:
|
||||||
|
//
|
||||||
|
// ==================================================
|
||||||
|
// 參數檢查
|
||||||
|
// --------------------------------------------------
|
||||||
|
```
|
||||||
|
|
||||||
|
<a id="loggerinfo"></a>
|
||||||
|
### logger.info
|
||||||
|
|
||||||
|
輸出一般資訊層級訊息至標準輸出,自動加上 `[info]` 前綴與換行;不會結束行程。
|
||||||
|
|
||||||
|
```js
|
||||||
|
const logger = require('./logger');
|
||||||
|
|
||||||
|
logger.info('IS_BETA=false');
|
||||||
|
// 輸出:[info] IS_BETA=false
|
||||||
|
```
|
||||||
|
|
||||||
|
<a id="loggerfail"></a>
|
||||||
|
### logger.fail
|
||||||
|
|
||||||
|
輸出錯誤訊息至標準錯誤輸出(stderr),並以狀態碼 1 立即終止整個行程。用於發生無法復原的錯誤時中止執行;呼叫後其下程式碼不會執行。
|
||||||
|
|
||||||
|
```js
|
||||||
|
const logger = require('./logger');
|
||||||
|
|
||||||
|
logger.fail('GITEA_SERVER_URL 未設定');
|
||||||
|
// 對 stderr 輸出:[error] GITEA_SERVER_URL 未設定
|
||||||
|
// 接著 process.exit(1) 終止行程
|
||||||
|
```
|
||||||
|
|
||||||
|
<a id="configisunset"></a>
|
||||||
|
### config.isUnset
|
||||||
|
|
||||||
|
判斷環境變數值是否視為「未設定」。`undefined`、`null`、空字串、字面字串 `"null"` 皆視為未設定。
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { isUnset } = require('./config');
|
||||||
|
|
||||||
|
isUnset(undefined); // true
|
||||||
|
isUnset('null'); // true
|
||||||
|
isUnset('false'); // false
|
||||||
|
```
|
||||||
|
|
||||||
|
<a id="configrequireenv"></a>
|
||||||
|
### config.requireEnv
|
||||||
|
|
||||||
|
驗證必填環境變數;未設定時拋出 `Error`(訊息為 `${name} 未設定`),已設定時原樣回傳值。
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { requireEnv } = require('./config');
|
||||||
|
|
||||||
|
const url = requireEnv('GITEA_SERVER_URL', process.env.GITEA_SERVER_URL);
|
||||||
|
// 未設定時拋出:Error: GITEA_SERVER_URL 未設定
|
||||||
|
```
|
||||||
|
|
||||||
|
<a id="confignormalizebetaflag"></a>
|
||||||
|
### config.normalizeBetaFlag
|
||||||
|
|
||||||
|
將 beta 旗標正規化為布林值。未設定時預設為 `false`;僅當值嚴格等於字面字串 `"true"` 時回傳 `true`。
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { normalizeBetaFlag } = require('./config');
|
||||||
|
|
||||||
|
normalizeBetaFlag('true'); // true
|
||||||
|
normalizeBetaFlag('TRUE'); // false(嚴格比較,不做大小寫轉換)
|
||||||
|
normalizeBetaFlag(undefined); // false
|
||||||
|
```
|
||||||
|
|
||||||
|
<a id="configloadconfig"></a>
|
||||||
|
### config.loadConfig
|
||||||
|
|
||||||
|
從環境變數載入並驗證執行所需的設定。`GITEA_SERVER_URL` 與 `GITEA_REPOSITORY` 為必填(未設定即拋錯);`GITEA_TOKEN` 非必填(未設定為 `null`);`IS_BETA` 會正規化為布林值。
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { loadConfig } = require('./config');
|
||||||
|
|
||||||
|
const config = loadConfig({
|
||||||
|
GITEA_SERVER_URL: 'https://gitea.example.com',
|
||||||
|
GITEA_REPOSITORY: 'owner/repo',
|
||||||
|
IS_BETA: 'true',
|
||||||
|
});
|
||||||
|
// => { serverUrl: 'https://gitea.example.com', repository: 'owner/repo', token: null, isBeta: true }
|
||||||
|
```
|
||||||
|
|
||||||
|
<a id="versioncompareversionarrays"></a>
|
||||||
|
### version.compareVersionArrays
|
||||||
|
|
||||||
|
逐區段(element-wise)比較兩個版本號數值陣列,較短的陣列視為較小。回傳值 `> 0` 表示 a 大於 b、`< 0` 表示 a 小於 b、`0` 表示相等。
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { compareVersionArrays } = require('./version');
|
||||||
|
|
||||||
|
compareVersionArrays([1, 10], [1, 2, 3]); // > 0(1.10 > 1.2.3)
|
||||||
|
compareVersionArrays([1, 2], [1, 2, 0]); // < 0(較短者較小)
|
||||||
|
```
|
||||||
|
|
||||||
|
<a id="versionparsestableversions"></a>
|
||||||
|
### version.parseStableVersions
|
||||||
|
|
||||||
|
從 release 清單解析出所有穩定版(排除 beta、排除格式不合法者)的版本號數值陣列;會去除 tag 開頭的 `v`。
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { parseStableVersions } = require('./version');
|
||||||
|
|
||||||
|
parseStableVersions([
|
||||||
|
{ tag_name: 'v1.2.3' },
|
||||||
|
{ tag_name: '2.0.0' },
|
||||||
|
{ tag_name: 'v1.0.0-beta.1' }, // 被排除
|
||||||
|
]);
|
||||||
|
// => [[1, 2, 3], [2, 0, 0]]
|
||||||
|
```
|
||||||
|
|
||||||
|
<a id="versionlateststableversion"></a>
|
||||||
|
### version.latestStableVersion
|
||||||
|
|
||||||
|
取得 release 清單中最新(最大)的穩定版版本號字串,固定格式化為三段 `major.minor.patch`;查無穩定版時回傳 `"0.0.0"`。
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { latestStableVersion } = require('./version');
|
||||||
|
|
||||||
|
latestStableVersion([{ tag_name: 'v1.2.3' }, { tag_name: 'v1.9.9' }]); // "1.9.9"
|
||||||
|
latestStableVersion([]); // "0.0.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
<a id="versionnextreleaseversion"></a>
|
||||||
|
### version.nextReleaseVersion
|
||||||
|
|
||||||
|
依最新穩定版字串計算下一個發行版本號:`patch + 1`,`patch` 達 10 進位至 `minor`,`minor` 達 10 進位至 `major`。
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { nextReleaseVersion } = require('./version');
|
||||||
|
|
||||||
|
nextReleaseVersion('1.2.8'); // "1.2.9"
|
||||||
|
nextReleaseVersion('1.2.9'); // "1.3.0"
|
||||||
|
nextReleaseVersion('1.9.9'); // "2.0.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
<a id="versionnextbetanumber"></a>
|
||||||
|
### version.nextBetaNumber
|
||||||
|
|
||||||
|
計算指定版本號的下一個 beta 流水號:取現有相符 beta 標籤(`v<version>-beta.<n>`)的最大序號加 1,查無時回傳 `1`。`version` 參數不應含前綴 `v`。
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { nextBetaNumber } = require('./version');
|
||||||
|
|
||||||
|
nextBetaNumber([{ tag_name: 'v1.3.0-beta.1' }, { tag_name: 'v1.3.0-beta.3' }], '1.3.0'); // 4
|
||||||
|
nextBetaNumber([], '1.3.0'); // 1
|
||||||
|
```
|
||||||
|
|
||||||
|
<a id="versioncalculateversion"></a>
|
||||||
|
### version.calculateVersion
|
||||||
|
|
||||||
|
計算最新穩定版與下一個版本號;`isBeta` 為 `true` 時產生 beta 版本號。回傳物件 `{ latest, version }`。
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { calculateVersion } = require('./version');
|
||||||
|
|
||||||
|
calculateVersion([{ tag_name: 'v1.2.3' }], false);
|
||||||
|
// => { latest: '1.2.3', version: '1.2.4' }
|
||||||
|
|
||||||
|
calculateVersion([{ tag_name: 'v1.2.3' }], true);
|
||||||
|
// => { latest: '1.2.3', version: '1.2.4-beta.1' }
|
||||||
|
```
|
||||||
|
|
||||||
|
<a id="releasesfetchreleases"></a>
|
||||||
|
### releases.fetchReleases
|
||||||
|
|
||||||
|
以分頁方式取得指定 Gitea repo 的所有 release,並回傳合併後的陣列。使用全域 `fetch` 逐頁請求(每頁 limit 為 10);當某頁回傳空資料、`null` 或筆數少於上限時即停止。提供 `options.token` 時以授權方式請求。網路失敗、非 2xx、無法解析或非陣列回應時拋出 `Error`。
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { fetchReleases } = require('./releases');
|
||||||
|
const logger = require('./logger');
|
||||||
|
|
||||||
|
const releases = await fetchReleases(
|
||||||
|
'https://gitea.example.com/api/v1/repos/owner/repo/releases',
|
||||||
|
{ token: process.env.GITEA_TOKEN, logger },
|
||||||
|
);
|
||||||
|
// => 所有 release 物件合併後的陣列
|
||||||
|
```
|
||||||
|
|
||||||
|
<a id="outputwriteoutput"></a>
|
||||||
|
### output.writeOutput
|
||||||
|
|
||||||
|
將一行 `name=value` 附加寫入 GitHub/Gitea Action 的輸出檔(預設取 `process.env.GITHUB_OUTPUT`)。輸出檔路徑為 falsy 時拋出 `Error`。
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { writeOutput } = require('./output');
|
||||||
|
|
||||||
|
writeOutput('version', '1.2.4');
|
||||||
|
// 對 $GITHUB_OUTPUT 附加一行:version=1.2.4
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user