286 lines
13 KiB
Markdown
286 lines
13 KiB
Markdown
# calculate-version
|
||
|
||
計算版本號的 Gitea Action:依儲存庫現有的 release,推算下一個穩定版或 beta 版本號,並寫出為 Action output 供後續步驟取用。以 Docker 容器執行,容器內由 Node.js 主程式實作。
|
||
|
||
> 更新時間:2026/06/30 18:18:13(Asia/Taipei)
|
||
|
||
## 專案列表
|
||
|
||
### 專案描述
|
||
|
||
| 專案名稱 | 專案描述 |
|
||
| --- | --- |
|
||
| [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) | 無(本專案為純 Node.js,package.json 未宣告任何 npm 相依) |
|
||
|
||
## 功能列表
|
||
|
||
### calculate-version
|
||
|
||
| 功能名稱 | 功能描述 |
|
||
| --- | --- |
|
||
| [config.isUnset](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/config.js#L11) | [判斷環境變數值是否視為「未設定」(undefined/null/空字串/字面 "null")。](#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 旗標正規化為布林值(僅字面 "true" 為真)。](#confignormalizebetaflag) |
|
||
| [config.loadConfig](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/config.js#L101) | [從環境變數載入並驗證執行所需的設定。](#configloadconfig) |
|
||
| [index.main](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/index.js#L30) | [Action 進入點:依序執行參數檢查、取得舊版本、計算版本號並寫出 output。](#indexmain) |
|
||
| [logger.info](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/logger.js#L58) | [輸出 `[階段][INF][時間]` 格式的資訊訊息至標準輸出(階段選填)。](#loggerinfo) |
|
||
| [logger.error](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/logger.js#L76) | [輸出 `[階段][ERR][時間]` 格式的錯誤訊息至標準錯誤輸出(階段選填)。](#loggererror) |
|
||
| [logger.forStage](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/logger.js#L89) | [建立綁定特定階段的子記錄器,自動為每則訊息帶入階段前綴。](#loggerforstage) |
|
||
| [output.writeOutput](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/output.js#L16) | [將一行 `name=value` 附加寫入 Action 的輸出檔。](#outputwriteoutput) |
|
||
| [releases.fetchReleases](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/releases.js#L24) | [以分頁方式取得指定 Gitea repo 的所有 release。](#releasesfetchreleases) |
|
||
| [version.compareVersionArrays](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/version.js#L15) | [逐區段比較兩個版本號數值陣列,較短者視為較小。](#versioncompareversionarrays) |
|
||
| [version.parseStableVersions](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/version.js#L39) | [從 release 清單解析出所有穩定版的版本號數值陣列。](#versionparsestableversions) |
|
||
| [version.latestStableVersion](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/version.js#L58) | [取得最新(最大)的穩定版版本號字串。](#versionlateststableversion) |
|
||
| [version.nextReleaseVersion](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/version.js#L79) | [依最新穩定版計算下一個發行版本號(逢 10 進位)。](#versionnextreleaseversion) |
|
||
| [version.nextBetaNumber](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/version.js#L102) | [計算指定版本號的下一個 beta 流水號。](#versionnextbetanumber) |
|
||
| [version.nextVersion](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/version.js#L131) | [依已知的最新穩定版計算本次要使用的版本號(含 beta)。](#versionnextversion) |
|
||
| [version.calculateVersion](https://gitea.jsc.idv.tw/docker-actions/calculate-version/src/branch/develop/app/version.js#L147) | [計算最新穩定版與下一個版本號。](#versioncalculateversion) |
|
||
|
||
## 使用範例
|
||
|
||
<a id="configisunset"></a>
|
||
### config.isUnset
|
||
|
||
判斷單一環境變數值是否應視為「未設定」。下列任一情況回傳 `true`:`undefined`、`null`、空字串、字面字串 `"null"`;其餘回傳 `false`。常用於 `loadConfig` 內判斷必填與選填設定。
|
||
|
||
```js
|
||
const { isUnset } = require('./app/config');
|
||
|
||
isUnset(undefined); // true
|
||
isUnset('null'); // true
|
||
isUnset(''); // true
|
||
isUnset('abc'); // false
|
||
```
|
||
|
||
<a id="configrequireenv"></a>
|
||
### config.requireEnv
|
||
|
||
驗證必填環境變數;值被視為未設定時拋出 `Error`(訊息為 `${name} 未設定`),否則原樣回傳該值。
|
||
|
||
```js
|
||
const { requireEnv } = require('./app/config');
|
||
|
||
const url = requireEnv('GITEA_SERVER_URL', process.env.GITEA_SERVER_URL);
|
||
// 未設定時 → throw Error('GITEA_SERVER_URL 未設定')
|
||
```
|
||
|
||
<a id="confignormalizebetaflag"></a>
|
||
### config.normalizeBetaFlag
|
||
|
||
將 beta 旗標環境變數正規化為布林值。未設定時預設為 `false`;僅當值嚴格等於字面字串 `"true"` 時回傳 `true`。
|
||
|
||
```js
|
||
const { normalizeBetaFlag } = require('./app/config');
|
||
|
||
normalizeBetaFlag('true'); // true
|
||
normalizeBetaFlag('false'); // false
|
||
normalizeBetaFlag(undefined); // false
|
||
```
|
||
|
||
<a id="configloadconfig"></a>
|
||
### config.loadConfig
|
||
|
||
從環境變數載入並驗證執行所需設定。`GITEA_SERVER_URL`(須為合法 http/https URL)與 `GITEA_REPOSITORY`(須為 `owner/repo` 形式、排除路徑穿越)為必填;`GITEA_TOKEN` 選填(未設定為 `null`);`IS_BETA` 正規化為布林值。驗證失敗會拋出 `Error`。
|
||
|
||
```js
|
||
const { loadConfig } = require('./app/config');
|
||
|
||
const config = loadConfig(process.env);
|
||
// → { serverUrl, repository, token, isBeta }
|
||
```
|
||
|
||
<a id="indexmain"></a>
|
||
### index.main
|
||
|
||
Action 進入點,依序執行三個階段:1. 參數檢查(`loadConfig`)、2. 取得舊版本(`fetchReleases` + `latestStableVersion`)、3. 計算版本號(`nextVersion`)並以 `writeOutput` 寫出。相依模組可透過 `deps` 注入以利測試;失敗時向外拋出例外。
|
||
|
||
```js
|
||
const { main } = require('./app/index');
|
||
|
||
// 以預設相依(讀環境變數、實際打 API)執行
|
||
await main();
|
||
|
||
// 測試時注入替身
|
||
await main({
|
||
loadConfig: () => ({ serverUrl: 'https://gitea.example.com', repository: 'owner/repo', token: null, isBeta: false }),
|
||
fetchReleases: async () => [{ tag_name: 'v1.2.3' }],
|
||
writeOutput: () => {},
|
||
});
|
||
// → 回傳 '1.2.4'
|
||
```
|
||
|
||
<a id="loggerinfo"></a>
|
||
### logger.info
|
||
|
||
輸出一般資訊(INF)層級的 log 訊息至標準輸出,格式統一為 `[{階段}][INF][{台灣時間}]: {訊息}`(時間為 Asia/Taipei,格式 `yyyy/MM/dd HH:mm:ss`),結尾換行。階段(`stage`)為選填,省略時略過 `[{階段}]` 區塊。
|
||
|
||
```js
|
||
const logger = require('./app/logger');
|
||
|
||
logger.info('NEW_VERSION=1.2.4');
|
||
// 輸出:[INF][2026/06/30 17:38:51]: NEW_VERSION=1.2.4
|
||
|
||
logger.info('NEW_VERSION=1.2.4', '計算版本號');
|
||
// 輸出:[計算版本號][INF][2026/06/30 17:38:51]: NEW_VERSION=1.2.4
|
||
```
|
||
|
||
<a id="loggererror"></a>
|
||
### logger.error
|
||
|
||
輸出錯誤(ERR)層級的 log 訊息至標準錯誤輸出(stderr),格式統一為 `[{階段}][ERR][{台灣時間}]: {訊息}`。階段為選填。僅負責輸出,不終止行程,是否結束由呼叫端決定。
|
||
|
||
```js
|
||
const logger = require('./app/logger');
|
||
|
||
logger.error('GITEA_SERVER_URL 未設定');
|
||
// stderr 輸出:[ERR][2026/06/30 17:38:51]: GITEA_SERVER_URL 未設定
|
||
```
|
||
|
||
<a id="loggerforstage"></a>
|
||
### logger.forStage
|
||
|
||
建立綁定特定階段(stage)的子記錄器,回傳 `{ info, error }`;其方法只需傳入 `message`,即會自動在每則訊息開頭帶入 `[{階段}]` 前綴。進入點以此為每個處理階段建立子記錄器,使該階段(含其呼叫的 `fetchReleases` 等模組)輸出的訊息都帶一致的階段前綴。
|
||
|
||
```js
|
||
const logger = require('./app/logger');
|
||
|
||
const stageLog = logger.forStage('取得舊版本');
|
||
stageLog.info('使用授權 token 取得 release');
|
||
// 輸出:[取得舊版本][INF][2026/06/30 17:38:51]: 使用授權 token 取得 release
|
||
```
|
||
|
||
<a id="outputwriteoutput"></a>
|
||
### output.writeOutput
|
||
|
||
將一行 `name=value` 以 append 方式寫入 GitHub/Gitea Action 的輸出檔(預設取自環境變數 `GITHUB_OUTPUT`)。輸出檔路徑為 falsy 時拋出 `Error`。value 僅支援單行字串。
|
||
|
||
```js
|
||
const { writeOutput } = require('./app/output');
|
||
|
||
writeOutput('version', '1.2.4');
|
||
// 對 process.env.GITHUB_OUTPUT 指向的檔案附加一行:version=1.2.4
|
||
```
|
||
|
||
<a id="releasesfetchreleases"></a>
|
||
### releases.fetchReleases
|
||
|
||
以分頁方式(每頁 10 筆)透過全域 `fetch` 取得指定 Gitea repo 的所有 release,回傳合併後的陣列。有 token 時以 `Authorization: token <token>` 授權,否則匿名請求;遇空頁或不足一頁即停止。請求失敗、非 2xx、無法解析或非陣列時拋出 `Error`。
|
||
|
||
```js
|
||
const { fetchReleases } = require('./app/releases');
|
||
const logger = require('./app/logger');
|
||
|
||
const releases = await fetchReleases(
|
||
'https://gitea.example.com/api/v1/repos/owner/repo/releases',
|
||
{ token: process.env.GITEA_TOKEN, logger },
|
||
);
|
||
```
|
||
|
||
<a id="versioncompareversionarrays"></a>
|
||
### version.compareVersionArrays
|
||
|
||
逐區段(element-wise)比較兩個版本號數值陣列,較短的陣列視為較小。回傳大於 0 表示 `a` 大於 `b`、小於 0 表示 `a` 小於 `b`、0 表示相等;相異區段回傳的是該段差值(非固定 ±1)。傳入 `null`/`undefined` 會在讀取 `.length` 時拋 `TypeError`。
|
||
|
||
```js
|
||
const { compareVersionArrays } = require('./app/version');
|
||
|
||
compareVersionArrays([1, 10, 0], [1, 9, 0]); // > 0
|
||
compareVersionArrays([1, 2], [1, 2, 0]); // < 0
|
||
compareVersionArrays([1, 2, 3], [1, 2, 3]); // 0
|
||
```
|
||
|
||
<a id="versionparsestableversions"></a>
|
||
### version.parseStableVersions
|
||
|
||
從 release 清單解析出所有穩定版(排除含 `-beta.` 與格式不合法者)的版本號數值陣列;會去除 tag 開頭的 `v`。輸入非陣列時回傳空陣列。
|
||
|
||
```js
|
||
const { parseStableVersions } = require('./app/version');
|
||
|
||
parseStableVersions([
|
||
{ tag_name: 'v1.2.3' },
|
||
{ tag_name: '2.0.0' },
|
||
{ tag_name: 'v1.3.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('./app/version');
|
||
|
||
latestStableVersion([{ tag_name: 'v1.9.0' }, { tag_name: 'v1.10.0' }]); // '1.10.0'
|
||
latestStableVersion([]); // '0.0.0'
|
||
```
|
||
|
||
<a id="versionnextreleaseversion"></a>
|
||
### version.nextReleaseVersion
|
||
|
||
依最新穩定版字串計算下一個發行版本號:patch 加 1,patch 達 10 進位至 minor,minor 達 10 進位至 major(逢 10 進位為本專案自訂約定,非標準 SemVer)。
|
||
|
||
```js
|
||
const { nextReleaseVersion } = require('./app/version');
|
||
|
||
nextReleaseVersion('1.2.3'); // '1.2.4'
|
||
nextReleaseVersion('1.2.9'); // '1.3.0'
|
||
nextReleaseVersion('1.9.9'); // '2.0.0'
|
||
```
|
||
|
||
<a id="versionnextbetanumber"></a>
|
||
### version.nextBetaNumber
|
||
|
||
計算指定版本號的下一個 beta 流水號:取現有相符 `v<version>-beta.<n>` 標籤的最大序號加 1,查無時回傳 1。
|
||
|
||
```js
|
||
const { nextBetaNumber } = require('./app/version');
|
||
|
||
nextBetaNumber([
|
||
{ tag_name: 'v1.2.4-beta.1' },
|
||
{ tag_name: 'v1.2.4-beta.3' },
|
||
], '1.2.4'); // 4
|
||
nextBetaNumber([], '1.2.4'); // 1
|
||
```
|
||
|
||
<a id="versionnextversion"></a>
|
||
### version.nextVersion
|
||
|
||
依「已知的最新穩定版」計算本次要使用的版本號(不負責取得舊版本)。`isBeta` 為 `false` 時回傳下一個發行版;為 `true` 時回傳 `<next>-beta.<n>`。
|
||
|
||
```js
|
||
const { nextVersion } = require('./app/version');
|
||
|
||
nextVersion('1.2.3', [], false); // '1.2.4'
|
||
nextVersion('1.2.3', [{ tag_name: 'v1.2.4-beta.2' }], true); // '1.2.4-beta.3'
|
||
```
|
||
|
||
<a id="versioncalculateversion"></a>
|
||
### version.calculateVersion
|
||
|
||
計算最新穩定版與下一個版本號;組合 `latestStableVersion` 與 `nextVersion`。回傳物件含 `latest`(現有最新穩定版,查無為 `"0.0.0"`)與 `version`(本次版本號)。
|
||
|
||
```js
|
||
const { calculateVersion } = require('./app/version');
|
||
|
||
calculateVersion([{ tag_name: 'v1.2.3' }], false);
|
||
// → { latest: '1.2.3', version: '1.2.4' }
|
||
|
||
calculateVersion([{ tag_name: 'v1.2.3' }, { tag_name: 'v1.2.4-beta.2' }], true);
|
||
// → { latest: '1.2.3', version: '1.2.4-beta.3' }
|
||
```
|