Compare commits

...
9 Commits
Author SHA1 Message Date
jiantw83 c8fe088e42 chore(ai-review 狀態): 清空 findings 清單
CI / 2. TEST (pull_request) Successful in 3s
CI / 3. RESULT (pull_request) Successful in 2s
CI / 1. BUILD (pull_request) Successful in 1s
2026-07-11 10:09:38 +00:00
jiantw83 33d5aacee5 docs(.docs): 刪除文件草稿目錄 2026-07-11 10:09:35 +00:00
jiantw83 6ee9195f52 test(src/index): 補齊失敗路徑輸出測試 2026-07-11 10:08:55 +00:00
jiantw83 768ab84b00 fix(src/index): 共用 log 格式化流程 2026-07-11 10:08:52 +00:00
admin c60b71e987 Merge pull request 'feat(CI 工作流程): 僅在 beta 啟用 AI 審查' (#7) from ai-review-resolve/develop-20260711-150530 into develop
Reviewed-on: #7
2026-07-11 10:05:53 +00:00
AI Review Bot 96cb99c136 chore: update ai-review findings [ai-review-bot][success]
CI / 1. BUILD (pull_request) Successful in 1s
CI / 2. TEST (pull_request) Successful in 32s
CI / 3. RESULT (pull_request) Has been skipped
2026-07-11 10:04:19 +00:00
jiantw83 24ed2ab164 docs(README 與文件草稿): 重整說明與草稿內容
CI / 1. BUILD (pull_request) Successful in 1s
CI / 2. TEST (pull_request) Successful in 3m32s
CI / 3. RESULT (pull_request) Has been skipped
2026-07-11 10:00:34 +00:00
jiantw83 1ed40a6b04 chore(action 設定): 整理用途標頭與排版 2026-07-11 10:00:30 +00:00
jiantw83 88e3e3d184 fix(calculate-version): 統一輸出與錯誤格式 2026-07-11 10:00:27 +00:00
10 changed files with 426 additions and 73 deletions
+1
View File
@@ -0,0 +1 @@
[]
+39 -34
View File
@@ -1,9 +1,14 @@
# --------------------------------------------------
# 用途:定義 CI 工作流程,處理 pull request 的版本發佈與測試。
# 更新日期:2026/07/11 17:44:33Asia/Taipei
# --------------------------------------------------
name: CI
on:
pull_request:
branches:
- master
- develop
- master
- develop
types: [opened, synchronize]
jobs:
build:
@@ -16,13 +21,13 @@ jobs:
version: ${{ env.VERSION }}
is_beta: ${{ env.IS_BETA }}
steps:
- name: Publishing Release
uses: akkuman/gitea-release-action@${{ vars.ACTION_GITEA_RELEASE_VERSION }}
with:
name: "${{ gitea.event.repository.name }} v${{ env.VERSION }}"
tag_name: "v${{ env.VERSION }}"
target_commitish: ${{ gitea.sha }}
prerelease: ${{ env.IS_BETA }}
- name: Publishing Release
uses: akkuman/gitea-release-action@${{ vars.ACTION_GITEA_RELEASE_VERSION }}
with:
name: "${{ gitea.event.repository.name }} v${{ env.VERSION }}"
tag_name: "v${{ env.VERSION }}"
target_commitish: ${{ gitea.sha }}
prerelease: ${{ env.IS_BETA }}
test:
name: 2. TEST
runs-on: ubuntu
@@ -33,36 +38,36 @@ jobs:
outputs:
version: ${{ steps.calculate-version.outputs.version }}
steps:
- name: Run Calculate Version
id: calculate-version
uses: https://gitea.jsc.idv.tw/actions/calculate-version@v${{ env.VERSION }}
with:
is_beta: ${{ env.IS_BETA == "true" }}
- name: Setup LLM CLI
uses: https://gitea.jsc.idv.tw/actions/setup-${{ vars.ACTION_SETUP_LLM_CLI }}
if: ${{ env.IS_BETA == 'true' }}
with:
oauth: ${{ secrets.LLM_OAUTH }}
- name: Run AI Code Review
uses: https://gitea.jsc.idv.tw/actions/ai-code-review@${{ vars.ACTION_AI_CODE_REVIEW_VERSION }}
id: ai-code-review
if: ${{ env.IS_BETA == 'true' }}
with:
token: ${{ secrets.TOKEN }}
model: ${{ vars.LLM_NAME }}
- name: Run Calculate Version
id: calculate-version
uses: https://gitea.jsc.idv.tw/actions/calculate-version@v${{ env.VERSION }}
with:
is_beta: ${{ env.IS_BETA == "true" }}
- name: Setup LLM CLI
uses: https://gitea.jsc.idv.tw/actions/setup-${{ vars.ACTION_SETUP_LLM_CLI }}
if: ${{ env.IS_BETA == 'true' }}
with:
oauth: ${{ secrets.LLM_OAUTH }}
- name: Run AI Code Review
uses: https://gitea.jsc.idv.tw/actions/ai-code-review@${{ vars.ACTION_AI_CODE_REVIEW_VERSION }}
id: ai-code-review
if: ${{ env.IS_BETA == 'true' }}
with:
token: ${{ secrets.TOKEN }}
model: ${{ vars.LLM_NAME }}
result:
name: 3. RESULT
runs-on: ubuntu
needs: [build,test]
needs: [build, test]
if: ${{ needs.build.outputs.is_beta == 'false' }}
env:
VERSION: ${{ needs.test.outputs.version }}
IS_BETA: ${{ needs.build.outputs.is_beta }}
steps:
- name: Publishing Release
uses: akkuman/gitea-release-action@${{ vars.ACTION_GITEA_RELEASE_VERSION }}
with:
name: "${{ gitea.event.repository.name }} v${{ env.VERSION }}"
tag_name: "v${{ env.VERSION }}"
target_commitish: ${{ gitea.sha }}
prerelease: ${{ env.IS_BETA == "true" }}
- name: Publishing Release
uses: akkuman/gitea-release-action@${{ vars.ACTION_GITEA_RELEASE_VERSION }}
with:
name: "${{ gitea.event.repository.name }} v${{ env.VERSION }}"
tag_name: "v${{ env.VERSION }}"
target_commitish: ${{ gitea.sha }}
prerelease: ${{ env.IS_BETA == "true" }}
+17 -10
View File
@@ -1,8 +1,13 @@
# --------------------------------------------------
# 用途:定義 CD 工作流程,於 master push 後部署並顯示相關 tag。
# 更新日期:2026/07/11 17:44:33Asia/Taipei
# --------------------------------------------------
name: CD
on:
push:
branches:
- master
- master
jobs:
deploy:
name: DEPLOY
@@ -10,12 +15,14 @@ jobs:
env:
COMMIT_SHA: ${{ gitea.event.commits[1].id }}
steps:
- name: Source Code Checkout
uses: actions/checkout@${{ vars.ACTION_CHECKOUT_VERSION }}
with:
fetch-depth: 0
- name: Get Commit Tag
id: commit
run: echo "tag=$(git for-each-ref --sort=-refname --format='%(refname:strip=2)' refs/tags --contains ${{ env.COMMIT_SHA }} | head -n 1)" >> $GITEA_OUTPUT
- name: Show Tag
run: echo "${{ steps.commit.outputs.tag }}"
- name: Source Code Checkout
uses: actions/checkout@${{ vars.ACTION_CHECKOUT_VERSION }}
with:
fetch-depth: 0
- name: Get Commit Tag
id: commit
run: >-
echo "tag=$(git for-each-ref --sort=-refname --format='%(refname:strip=2)' refs/tags --contains ${{ env.COMMIT_SHA }} | head -n 1)" >> $GITEA_OUTPUT
- name: Show Tag
run: >-
echo "[DEPLOY][INF][$(TZ='Asia/Taipei' date +'%Y/%m/%d %H:%M:%S')]: ${{ steps.commit.outputs.tag }}"
+40 -8
View File
@@ -1,9 +1,41 @@
# GITEA COMPOSITE ACTION 的工作流列表
# 工作流程說明
- CI
- BUILD
- TEST
- RESULT
- CD
- BUILD
- DEPLOY
以下內容整理 `.gitea/workflows/` 目前的工作流程與用途,作為後續覆蓋 `.gitea/workflows/readme.md` 的說明文件。
## 總覽
| Workflow 名稱 | 檔案位置 | 觸發條件 | 主要用途 | 相關參數 |
| ------------- | ------------------------------ | --------------------------------------------------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| CI | `.gitea/workflows/ci.yaml` | `pull_request``master``develop`,事件型別為 `opened``synchronize` | 在 PR 階段建立 release、計算版本,beta 分支還會執行 AI code review | `VERSION``IS_BETA``vars.ACTION_GITEA_RELEASE_VERSION``vars.ACTION_SETUP_LLM_CLI``vars.ACTION_AI_CODE_REVIEW_VERSION``secrets.LLM_OAUTH``secrets.TOKEN``vars.LLM_NAME` |
| CD | `.gitea/workflows/master.yaml` | `push``master` | 根據 commit 找出對應 tag,供部署流程或後續檢查使用 | `COMMIT_SHA``vars.ACTION_CHECKOUT_VERSION` |
## CI
- 檔案位置:`.gitea/workflows/ci.yaml`
- 觸發條件:`pull_request``master``develop`,事件型別包含 `opened``synchronize`
- 用途說明:先發佈 release 草稿,再呼叫 `calculate-version` action 計算版本;若是 beta 分支,還會設定 LLM CLI 並執行 AI code review。
- 主要輸入 / 環境參數:
- `VERSION`:預設為 `0.0.0-beta.${{ gitea.run_number }}`,供後續 job 使用。
- `IS_BETA`:由 `gitea.base_ref == 'develop'` 推得,控制 beta 與正式版流程。
- `vars.ACTION_GITEA_RELEASE_VERSION``gitea-release-action` 版本。
- `vars.ACTION_SETUP_LLM_CLI`LLM CLI 安裝 action 版本。
- `vars.ACTION_AI_CODE_REVIEW_VERSION`AI code review action 版本。
- `secrets.LLM_OAUTH``secrets.TOKEN`beta 流程所需憑證。
- `vars.LLM_NAME`AI code review 使用的模型名稱。
- 重要注意事項:
- `result` job 只在 `IS_BETA == 'false'` 時執行。
- `test` job 的 action 來源是 `https://gitea.jsc.idv.tw/actions/calculate-version@v${{ env.VERSION }}`,版本號需與 release 流程對齊。
-`develop``master` 的 release 節奏不同,需確認 `VERSION` 的預設生成邏輯是否符合預期。
## CD
- 檔案位置:`.gitea/workflows/master.yaml`
- 觸發條件:`push``master`
- 用途說明:檢出原始碼後,根據提交 SHA 找出對應 tag,並輸出查得的結果。
- 主要輸入 / 環境參數:
- `COMMIT_SHA`:目前流程中使用 `gitea.event.commits[1].id`,需確認該索引在所有 push 事件下都存在。
- `vars.ACTION_CHECKOUT_VERSION``actions/checkout` 版本。
- 重要注意事項:
- `fetch-depth: 0` 是必要條件,否則 `git for-each-ref --contains` 可能找不到完整 tag。
- `Get Commit Tag``run` 指令假設 tag 存在;若找不到,輸出會是空字串,需人工確認下游是否能接受。
- `Show Tag` 目前採用統一輸出格式草稿,正式實作時仍需確認 shell 內的時間取得方式。
+5
View File
@@ -1,3 +1,8 @@
# --------------------------------------------------
# 用途:定義 calculate-version action 的 Docker 執行環境。
# 更新日期:2026/07/11 17:44:33Asia/Taipei
# --------------------------------------------------
FROM node:lts-alpine
WORKDIR /action
+120 -6
View File
@@ -1,6 +1,6 @@
# calculate-version
更新時間:2026/07/11 14:03:52Asia/Taipei
更新時間:2026/07/11 17:53:32Asia/Taipei
這個專案是一個 Gitea Docker action,會讀取 repository 的 release 清單,計算下一個正式版或 beta 版本號,並將結果輸出成 action output。
@@ -10,19 +10,19 @@
| 專案名稱 | 專案描述 |
| --- | --- |
| [calculate-version](https://gitea.jsc.idv.tw/actions/calculate-version/src/branch/develop/) | 這是一個用來計算 Gitea release 下一版版本號的 Node.js action,支援正式版與 beta 版推算。 |
| [calculate-version](https://gitea.jsc.idv.tw/actions/calculate-version/src/branch/ai-review-resolve/develop-20260711-150530/) | 這是一個以 Node.js 實作的 Gitea Docker action,提供 release 擷取、版本解析、beta 推算與 action 主流程輸出。 |
### 參考專案表
| 專案名稱 | 參考專案列表 |
| --- | --- |
| [calculate-version](https://gitea.jsc.idv.tw/actions/calculate-version/src/branch/develop/) | 無 |
| [calculate-version](https://gitea.jsc.idv.tw/actions/calculate-version/src/branch/ai-review-resolve/develop-20260711-150530/) | 無 |
### NuGet 套件表
| 專案名稱 | NuGet 套件列表 |
| --- | --- |
| [calculate-version](https://gitea.jsc.idv.tw/actions/calculate-version/src/branch/develop/) | 無 |
| [calculate-version](https://gitea.jsc.idv.tw/actions/calculate-version/src/branch/ai-review-resolve/develop-20260711-150530/) | 無 |
## 功能列表
@@ -30,8 +30,122 @@
| 功能名稱 | 功能描述 |
| --- | --- |
| 無 | 此專案未公開可列入 README 的功能。 |
| [calculate-version.normalizeBetaFlag](https://gitea.jsc.idv.tw/actions/calculate-version/src/branch/ai-review-resolve/develop-20260711-150530/src/index.js#L72) | [把空值或 `null` 正規化成 beta 旗標字串](#calculateversionnormalizebetaflag) |
| [calculate-version.parseVersionParts](https://gitea.jsc.idv.tw/actions/calculate-version/src/branch/ai-review-resolve/develop-20260711-150530/src/index.js#L107) | [把版本字串拆成數字片段](#calculateversionparseversionparts) |
| [calculate-version.compareVersionParts](https://gitea.jsc.idv.tw/actions/calculate-version/src/branch/ai-review-resolve/develop-20260711-150530/src/index.js#L125) | [逐段比較版本片段,供排序使用](#calculateversioncompareversionparts) |
| [calculate-version.stableVersionFromTag](https://gitea.jsc.idv.tw/actions/calculate-version/src/branch/ai-review-resolve/develop-20260711-150530/src/index.js#L148) | [解析穩定版 tag 為版本片段](#calculateversionstableversionfromtag) |
| [calculate-version.latestStableVersion](https://gitea.jsc.idv.tw/actions/calculate-version/src/branch/ai-review-resolve/develop-20260711-150530/src/index.js#L169) | [從 release 清單找出最新穩定版](#calculateversionlateststableversion) |
| [calculate-version.nextReleaseVersion](https://gitea.jsc.idv.tw/actions/calculate-version/src/branch/ai-review-resolve/develop-20260711-150530/src/index.js#L195) | [將正式版版本號遞增一版](#calculateversionnextreleaseversion) |
| [calculate-version.nextBetaNumber](https://gitea.jsc.idv.tw/actions/calculate-version/src/branch/ai-review-resolve/develop-20260711-150530/src/index.js#L224) | [推算下一個 beta 編號](#calculateversionnextbetanumber) |
| [calculate-version.calculateVersion](https://gitea.jsc.idv.tw/actions/calculate-version/src/branch/ai-review-resolve/develop-20260711-150530/src/index.js#L252) | [回傳目前最新版本與下一個版本](#calculateversioncalculateversion) |
| [calculate-version.fetchReleases](https://gitea.jsc.idv.tw/actions/calculate-version/src/branch/ai-review-resolve/develop-20260711-150530/src/index.js#L334) | [分頁抓取 repository release 清單](#calculateversionfetchreleases) |
| [calculate-version.main](https://gitea.jsc.idv.tw/actions/calculate-version/src/branch/ai-review-resolve/develop-20260711-150530/src/index.js#L373) | [執行 action 主流程並輸出結果](#calculateversionmain) |
## 使用範例
此專案目前沒有可對外列入 README 的公開方法,因此沒有對應的使用範例。
### <a id="calculateversionnormalizebetaflag"></a>calculate-version.normalizeBetaFlag
把空值、`null` 或未定義值正規化成 `false`,讓後續版本判斷只處理字串。
```js
const { normalizeBetaFlag } = require('./src/index.js');
const isBeta = normalizeBetaFlag(process.env.IS_BETA);
```
### <a id="calculateversionparseversionparts"></a>calculate-version.parseVersionParts
將版本字串拆成數字陣列,非數字片段會保守視為 `0`
```js
const { parseVersionParts } = require('./src/index.js');
const parts = parseVersionParts('1.2.3');
```
### <a id="calculateversioncompareversionparts"></a>calculate-version.compareVersionParts
逐段比較兩組版本片段,適合搭配 `Array.prototype.sort()` 使用。
```js
const { compareVersionParts } = require('./src/index.js');
const sorted = [[1, 2, 10], [1, 2, 3]].sort(compareVersionParts);
```
### <a id="calculateversionstableversionfromtag"></a>calculate-version.stableVersionFromTag
從 tag 名稱擷取穩定版版本資訊,像 `v1.2.3` 會被解析成版本片段。
```js
const { stableVersionFromTag } = require('./src/index.js');
const parts = stableVersionFromTag('v1.2.3');
```
### <a id="calculateversionlateststableversion"></a>calculate-version.latestStableVersion
從 release 清單中找出最新的穩定版版本號,找不到時回傳 `0.0.0`
```js
const { latestStableVersion } = require('./src/index.js');
const latest = latestStableVersion([
{ tag_name: 'v1.2.2' },
{ tag_name: 'v1.2.3-beta.1' },
{ tag_name: 'v1.2.3' },
]);
```
### <a id="calculateversionnextreleaseversion"></a>calculate-version.nextReleaseVersion
將正式版版本號遞增一版,`patch``minor` 都會以 10 為進位界線。
```js
const { nextReleaseVersion } = require('./src/index.js');
const nextVersion = nextReleaseVersion('1.2.9');
```
### <a id="calculateversionnextbetanumber"></a>calculate-version.nextBetaNumber
推算下一個 beta 編號,會找出目前版本對應的最大 `beta.N` 再加一。
```js
const { nextBetaNumber } = require('./src/index.js');
const betaNumber = nextBetaNumber([
{ tag_name: 'v1.2.4-beta.1' },
{ tag_name: 'v1.2.4-beta.3' },
], '1.2.4');
```
### <a id="calculateversioncalculateversion"></a>calculate-version.calculateVersion
回傳目前最新版本與下一個版本,beta 模式時會產生對應的 `-beta.N` 結果。
```js
const { calculateVersion } = require('./src/index.js');
const [latest, nextVersion] = calculateVersion([
{ tag_name: 'v1.2.3' },
{ tag_name: 'v1.2.4-beta.1' },
], 'false');
```
### <a id="calculateversionfetchreleases"></a>calculate-version.fetchReleases
分頁抓取 repository 的 release 清單,直到 API 回傳空資料或最後一頁不足頁面上限。
```js
const { fetchReleases } = require('./src/index.js');
// 需人工確認:baseUrl 與 repository 必須對應實際 Gitea 環境。
(async () => {
const releases = await fetchReleases('https://gitea.example.com', 'owner/repo', 'token-value');
console.log(releases);
})();
```
### <a id="calculateversionmain"></a>calculate-version.main
執行 action 主流程,會讀取環境變數、抓取 release、計算版本並輸出結果。
```js
const { main } = require('./src/index.js');
// 需人工確認:這個入口通常應由 Gitea action 啟動,且需要完整環境變數。
(async () => {
await main();
})();
```
+14 -9
View File
@@ -1,19 +1,24 @@
name: 'Calculate Version'
description: '計算版本號'
author: 'Jeffery'
# --------------------------------------------------
# 用途:宣告 Calculate Version action 的輸入、輸出與 Docker 執行方式。
# 更新日期:2026/07/11 17:44:33Asia/Taipei
# --------------------------------------------------
name: "Calculate Version"
description: "計算版本號"
author: "Jeffery"
inputs:
is_beta:
description: '是否為 beta 版本'
default: 'false'
description: "是否為 beta 版本"
default: "false"
runner_token:
description: '用於讀取 release 的 token'
description: "用於讀取 release 的 token"
required: false
outputs:
version:
description: '計算出的版本號'
description: "計算出的版本號"
runs:
using: 'docker'
image: 'Dockerfile'
using: "docker"
image: "Dockerfile"
env:
GITEA_SERVER_URL: ${{ gitea.server_url }}
GITEA_REPOSITORY: ${{ gitea.repository }}
+5
View File
@@ -1,4 +1,9 @@
#!/bin/sh
# --------------------------------------------------
# 用途:啟動 calculate-version action,將所有參數交給 Node 主程式。
# 更新日期:2026/07/11 17:44:33Asia/Taipei
# --------------------------------------------------
set -eu
exec node /action/src/index.js "$@"
+159 -6
View File
@@ -6,23 +6,76 @@ const { URL } = require('url');
const RELEASES_PER_PAGE = 10;
let currentStage = null;
/**
* 設定目前訊息輸出的階段名稱。
*
* @param {string} title 階段名稱,通常是流程區塊名稱。
* @remarks
* 供 `info()` 與錯誤輸出使用,讓訊息可以帶上統一前綴。
*/
function section(title) {
currentStage = title;
}
function info(message) {
const timestamp = new Date()
/**
* 產生統一格式的時間戳記字串。
*
* @returns {string} Asia/Taipei 時區格式化後的時間戳記。
* @remarks
* 由 `info()` 與錯誤輸出共用,避免不同輸出路徑各自拼出不同格式。
*/
function formatTimestamp() {
return new Date()
.toLocaleString('sv-SE', { timeZone: 'Asia/Taipei', hour12: false })
.replace(/-/g, '/');
const stagePrefix = currentStage ? `[${currentStage}]` : '';
process.stdout.write(`${stagePrefix}[INF][${timestamp}]: ${message}\n`);
}
/**
* 以統一格式組裝單行 log。
*
* @param {string} level 訊息等級。
* @param {string} message 要輸出的訊息內容。
* @returns {string} 已格式化完成且含換行的 log 字串。
* @remarks
* 讓一般資訊與錯誤訊息共用同一套前綴格式,降低未來調整格式時的維護成本。
*/
function formatLogLine(level, message) {
const stagePrefix = currentStage ? `[${currentStage}]` : '';
return `${stagePrefix}[${level}][${formatTimestamp()}]: ${message}\n`;
}
/**
* 以統一格式輸出資訊訊息。
*
* @param {string} message 要輸出的訊息內容。
* @remarks
* 會附加目前階段與 Asia/Taipei 時區時間戳記,適合用於流程進度與狀態輸出。
*/
function info(message) {
process.stdout.write(formatLogLine('INF', message));
}
/**
* 以例外方式終止流程。
*
* @param {string} message 錯誤訊息。
* @remarks
* 當輸入參數缺失或流程無法繼續時使用,呼叫後會直接拋出 `Error`。
*/
function fail(message) {
throw new Error(message);
}
/**
* 驗證必要環境變數是否有值。
*
* @param {string} name 環境變數名稱,用於錯誤訊息。
* @param {string} value 環境變數值。
* @returns {string} 驗證通過後的原始值。
* @remarks
* 適合用在 action 啟動時檢查 GITEA 相關必要設定。若值缺失,會直接拋出例外。
*/
function requireEnv(name, value) {
if (!value || value === 'null') {
fail(`${name} 未設定`);
@@ -31,6 +84,14 @@ function requireEnv(name, value) {
return value;
}
/**
* 將 beta 旗標正規化成字串。
*
* @param {string} [value='false'] 原始 beta 旗標。
* @returns {string} 正規化後的字串,空值會變成 `false`。
* @remarks
* 用於 action 入口參數,避免 `undefined`、空字串或字串 `null` 進入後續判斷。
*/
function normalizeBetaFlag(value = 'false') {
if (!value || value === 'null') {
return 'false';
@@ -39,6 +100,13 @@ function normalizeBetaFlag(value = 'false') {
return String(value);
}
/**
* 將計算結果寫入 action output。
*
* @param {string} version 要輸出的版本號。
* @remarks
* 若執行環境提供 `GITHUB_OUTPUT`,會寫入檔案;否則退回標準輸出,方便本機除錯。
*/
function writeOutput(version) {
const line = `version=${version}\n`;
const outputPath = process.env.GITHUB_OUTPUT;
@@ -51,6 +119,14 @@ function writeOutput(version) {
process.stdout.write(line);
}
/**
* 將版本字串拆成數字陣列。
*
* @param {string|number} version 版本字串。
* @returns {number[]} 由 major、minor、patch 等片段組成的數字陣列。
* @remarks
* 非數字片段會被轉成 `0`,適合用於後續比較與遞增計算。
*/
function parseVersionParts(version) {
return String(version)
.split('.')
@@ -60,6 +136,15 @@ function parseVersionParts(version) {
});
}
/**
* 比較兩組版本片段的大小。
*
* @param {number[]} left 左側版本片段。
* @param {number[]} right 右側版本片段。
* @returns {number} 左小於右回傳負值,左大於右回傳正值,相等回傳 0。
* @remarks
* 可直接搭配 `Array.prototype.sort()` 使用,用來找出最新穩定版本。
*/
function compareVersionParts(left, right) {
const maxLength = Math.max(left.length, right.length);
@@ -75,6 +160,14 @@ function compareVersionParts(left, right) {
return 0;
}
/**
* 從 release tag 解析穩定版版本資訊。
*
* @param {string} tagName release tag 名稱。
* @returns {number[]|null} 解析成功時回傳版本片段,否則回傳 `null`。
* @remarks
* 只接受純數字與點號組成的穩定版標籤,例如 `v1.2.3` 或 `1.2.3`。
*/
function stableVersionFromTag(tagName) {
if (typeof tagName !== 'string' || tagName.includes('-beta.')) {
return null;
@@ -88,6 +181,14 @@ function stableVersionFromTag(tagName) {
return parseVersionParts(versionText);
}
/**
* 取得最新的穩定版版本號。
*
* @param {Array<object>} releaseJson release API 回傳資料。
* @returns {string} 最新穩定版版本號;找不到時回傳 `0.0.0`。
* @remarks
* 主要用於計算下一版版本號的基礎值,會忽略 beta 標籤與不合法版本字串。
*/
function latestStableVersion(releaseJson) {
if (!Array.isArray(releaseJson) || releaseJson.length === 0) {
return '0.0.0';
@@ -106,6 +207,14 @@ function latestStableVersion(releaseJson) {
return `${latest[0] ?? 0}.${latest[1] ?? 0}.${latest[2] ?? 0}`;
}
/**
* 計算下一個正式版版本號。
*
* @param {string} latestVersion 目前最新的正式版版本號。
* @returns {string} 下一個正式版版本號。
* @remarks
* 進位規則以 10 為界,適合與 `latestStableVersion()` 搭配使用。
*/
function nextReleaseVersion(latestVersion) {
const [majorRaw, minorRaw, patchRaw] = parseVersionParts(latestVersion);
let major = majorRaw ?? 0;
@@ -126,6 +235,15 @@ function nextReleaseVersion(latestVersion) {
return `${major}.${minor}.${patch}`;
}
/**
* 計算下一個 beta 編號。
*
* @param {Array<object>} releaseJson release API 回傳資料。
* @param {string} version 目標正式版版本號。
* @returns {number} 下一個 beta 編號,最小為 1。
* @remarks
* 用於 beta 發版流程,會忽略不符合命名格式或無法解析成整數的 tag。
*/
function nextBetaNumber(releaseJson, version) {
if (!Array.isArray(releaseJson) || releaseJson.length === 0) {
return 1;
@@ -145,6 +263,15 @@ function nextBetaNumber(releaseJson, version) {
return Math.max(...values) + 1;
}
/**
* 計算目前最新版本與下一個版本。
*
* @param {Array<object>} releaseJson release API 回傳資料。
* @param {string} isBeta 是否計算 beta 版本,`true` 代表 beta。
* @returns {string[]} 回傳 `[latestVersion, nextVersion]`。
* @remarks
* 這是 action 的核心邏輯,通常由 `main()` 取得 release 後再呼叫。
*/
function calculateVersion(releaseJson, isBeta) {
if (!Array.isArray(releaseJson) || releaseJson.length === 0) {
return isBeta === 'true'
@@ -163,6 +290,15 @@ function calculateVersion(releaseJson, isBeta) {
return [baseVersion, nextVersion];
}
/**
* 以 HTTP/HTTPS 取得 JSON 回應。
*
* @param {URL} url 請求目標。
* @param {string} token 驗證 token;空值或字串 `null` 會被忽略。
* @returns {Promise<unknown>} 成功時解析為 JSON 物件或空字串。
* @remarks
* 失敗時會拋出錯誤,適合拿來包裝 release API 的低階請求。
*/
function requestJson(url, token) {
return new Promise((resolve, reject) => {
const client = url.protocol === 'http:' ? http : https;
@@ -208,6 +344,16 @@ function requestJson(url, token) {
});
}
/**
* 逐頁抓取 repository 的 release 清單。
*
* @param {string|URL} baseUrl Gitea API 基底網址。
* @param {string} repository repository 路徑。
* @param {string} token API 存取 token。
* @returns {Promise<Array<object>>} 合併後的 release 清單。
* @remarks
* 會依 `RELEASES_PER_PAGE` 分頁,當 API 回傳空資料或最後一頁不足數量時停止。
*/
async function fetchReleases(baseUrl, repository, token) {
const combined = [];
let page = 1;
@@ -240,6 +386,13 @@ async function fetchReleases(baseUrl, repository, token) {
return combined;
}
/**
* 執行 calculate-version action 的主流程。
*
* @returns {Promise<void>} 完成時不回傳值,失敗時會拋出例外。
* @remarks
* 需要在 Gitea action 環境中執行,並依賴 `GITEA_SERVER_URL`、`GITEA_REPOSITORY` 與 token 相關環境變數。
*/
async function main() {
section('參數檢查');
@@ -268,7 +421,7 @@ async function main() {
if (require.main === module) {
main().catch((error) => {
process.stderr.write(`[error] ${error.message}\n`);
process.stderr.write(formatLogLine('ERR', error.message));
process.exit(1);
});
}
+26
View File
@@ -0,0 +1,26 @@
const assert = require('node:assert/strict');
const { spawnSync } = require('node:child_process');
const path = require('node:path');
const test = require('node:test');
test('主流程失敗時會輸出階段前綴與錯誤碼', () => {
const result = spawnSync(
process.execPath,
[path.join(__dirname, 'index.js')],
{
cwd: path.join(__dirname, '..'),
env: {
...process.env,
GITEA_SERVER_URL: '',
GITEA_REPOSITORY: 'owner/repo',
RUNNER_TOKEN: 'token',
IS_BETA: 'false',
},
encoding: 'utf8',
},
);
assert.equal(result.status, 1);
assert.match(result.stderr, /^\[參數檢查\]\[ERR\]\[\d{4}\/\d{2}\/\d{2} /);
assert.match(result.stderr, /GITEA_SERVER_URL 未設定/);
});