Compare commits

..
Author SHA1 Message Date
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
jiantw83 5883abd306 feat(CI 工作流程): 僅在 beta 啟用 AI 審查
CI / 2. TEST (pull_request) Successful in 45s
CI / 3. RESULT (pull_request) Has been skipped
CI / 1. BUILD (pull_request) Successful in 2s
2026-07-11 06:30:30 +00:00
admin 140b9c6fb1 Merge pull request 'feat(CI 工作流程): 新增 AI 程式碼審查步驟' (#6) from ai-review-resolve/develop-20260711-144500 into develop
Reviewed-on: #6
2026-07-11 06:28:09 +00:00
jiantw83 73edbfcc17 feat(CI 工作流程): 新增 AI 程式碼審查步驟
CI / 1. BUILD (pull_request) Successful in 2s
CI / 2. TEST (pull_request) Successful in 36s
CI / 3. RESULT (pull_request) Has been skipped
2026-07-11 06:27:06 +00:00
admin 91f012e7a1 Merge pull request 'fix(calculate-version): 統一輸出訊息格式與 README 說明' (#5) from ai-review-resolve/develop-20260711-141530 into develop
Reviewed-on: #5
2026-07-11 06:24:50 +00:00
jiantw83 84ea25ef11 fix(CI 工作流程): 修正結果 job 的條件判斷
CI / 1. BUILD (pull_request) Successful in 1s
CI / 2. TEST (pull_request) Successful in 3s
CI / 3. RESULT (pull_request) Has been skipped
2026-07-11 06:24:24 +00:00
jiantw83 7a71cddc80 docs(README): 重建專案說明
CI / 1. BUILD (pull_request) Successful in 2s
CI / 2. TEST (pull_request) Successful in 3s
CI / 3. RESULT (pull_request) Successful in 2s
2026-07-11 06:21:24 +00:00
jiantw83 9127f26fd8 fix(calculate-version): 統一輸出訊息格式 2026-07-11 06:21:24 +00:00
jiantw83 34b0699e2b fix(CD 工作流程): 改為以 refname 排序選取 tag
CI / 1. BUILD (pull_request) Successful in 1s
CI / 2. TEST (pull_request) Successful in 1s
CI / 3. RESULT (pull_request) Successful in 1s
2026-07-11 06:00:09 +00:00
jiantw83 8d6672e5a3 fix(CD 工作流程): 改為選取包含 commit 的最新 tag
CI / 1. BUILD (pull_request) Successful in 2s
CI / 2. TEST (pull_request) Successful in 1s
CI / 3. RESULT (pull_request) Successful in 1s
2026-07-11 05:54:50 +00:00
12 changed files with 663 additions and 52 deletions
+19
View File
@@ -0,0 +1,19 @@
# Dockerfile draft
<!--
Purpose: 建立 action 執行映像 / build the action runtime image
Timestamp: 2026/07/11 17:23:33 (Asia/Taipei)
-->
```dockerfile
# FROM node:lts-alpine
# WORKDIR /action
# COPY src/ /action/src/
# COPY entrypoint.sh /action/entrypoint.sh
# RUN chmod +x /action/entrypoint.sh
# ENTRYPOINT ["/action/entrypoint.sh"]
```
+31
View File
@@ -0,0 +1,31 @@
# action.yml draft
<!--
Purpose: 計算版本號 / calculate the next version number for the action
Timestamp: 2026/07/11 17:23:33 (Asia/Taipei)
-->
```yaml
# name: 'Calculate Version'
# description: '計算版本號'
# author: 'Jeffery'
# inputs:
# is_beta:
# description: '是否為 beta 版本'
# default: 'false'
# runner_token:
# description: '用於讀取 release 的 token'
# required: false
# outputs:
# version:
# description: '計算出的版本號'
# runs:
# using: 'docker'
# image: 'Dockerfile'
# env:
# GITEA_SERVER_URL: ${{ gitea.server_url }}
# GITEA_REPOSITORY: ${{ gitea.repository }}
# GITEA_TOKEN: ${{ gitea.token }}
# RUNNER_TOKEN: ${{ inputs.runner_token || secrets.GITEA_TOKEN || secrets.RUNNER_TOKEN }}
# IS_BETA: ${{ inputs.is_beta }}
```
+13
View File
@@ -0,0 +1,13 @@
# entrypoint.sh draft
<!--
Purpose: 啟動 Node.js 主程式 / start the Node.js entrypoint
Timestamp: 2026/07/11 17:23:33 (Asia/Taipei)
-->
```sh
# #!/bin/sh
# set -eu
# exec node /action/src/index.js "$@"
```
+184
View File
@@ -0,0 +1,184 @@
# `src/index.js` function drafts
## 1. `section(title)`
- 來源位置: `src/index.js:9-11`
- 完整簽名: `function section(title)`
- 行為分析: 只把 `currentStage` 更新成傳入標題,讓後續 `info()` 能夠在 log 前加上階段前綴。沒有回傳值,也沒有其他副作用。 / It only updates `currentStage` so later `info()` calls can prefix logs with the current stage. It returns nothing and has no other side effects.
- 建議摘要: 設定目前執行階段。 / Set the current execution stage.
- 參數註解:
- `title`: 階段名稱字串,通常是流程標題,例如 `參數檢查`。 / Stage title string, typically a workflow label such as `參數檢查`.
- 備註與使用情境: 當主流程要切換到不同步驟時先呼叫它,這樣 `info()` 的輸出會帶上對應階段,方便追 log。 / Call it before a workflow step so `info()` output carries the right stage prefix for easier log tracing.
- 內部呼叫: 無。 / None.
## 2. `info(message)`
- 來源位置: `src/index.js:13-20`
- 完整簽名: `function info(message)`
- 行為分析: 產生台灣時區的時間戳,結合目前 `currentStage` 組成固定格式的資訊 log,並直接寫到 `stdout`。時間格式化是輸出格式整理,不改變流程邏輯。 / It builds a Taiwan-time timestamp, combines it with `currentStage`, and writes a fixed-format info log directly to `stdout`. The timestamp formatting is output normalization only and does not change logic.
- 建議摘要: 輸出帶階段前綴與台灣時間的資訊訊息。 / Emit an info log with stage prefix and Taiwan timestamp.
- 參數註解:
- `message`: 要輸出的訊息內容。 / Message body to print.
- 備註與使用情境: 當 action 需要對齊 Gitea action log 風格時使用,例如列出版本 URL、頁數與計算結果。 / Use it when the action should emit structured logs, such as release URLs, page counts, or computed versions.
- 內部呼叫: 無;但依賴 `currentStage``new Date()``toLocaleString()``process.stdout.write()`。 / None; it depends on `currentStage`, `new Date()`, `toLocaleString()`, and `process.stdout.write()`.
## 3. `fail(message)`
- 來源位置: `src/index.js:22-24`
- 完整簽名: `function fail(message)`
- 行為分析: 直接丟出 `Error`,用於統一中止流程並讓呼叫端接住錯誤。 / It throws an `Error` immediately, providing a single place to abort the flow and let callers handle the failure.
- 建議摘要: 以錯誤訊息中止執行。 / Abort execution with an error message.
- 參數註解:
- `message`: 錯誤訊息。 / Error message.
- 備註與使用情境: `requireEnv()` 會用它回報必要環境變數缺失。 / `requireEnv()` uses it to report missing required environment variables.
- 內部呼叫: 無。 / None.
## 4. `requireEnv(name, value)`
- 來源位置: `src/index.js:26-32`
- 完整簽名: `function requireEnv(name, value)`
- 行為分析: 檢查環境變數值是否為空或字串 `null`;若是就透過 `fail()` 中止,否則原樣回傳。這是嚴格驗證,不做額外轉型。 / It checks whether an environment value is empty or the string `null`; if so it aborts via `fail()`, otherwise it returns the original value unchanged. This is strict validation with no extra coercion.
- 建議摘要: 驗證必要環境變數並回傳原值。 / Validate a required environment variable and return the original value.
- 參數註解:
- `name`: 變數名稱,用於錯誤訊息。 / Variable name used in the error message.
- `value`: 已讀取的環境變數值。 / The already-read environment value.
- 備註與使用情境: 在 `main()` 讀取 `GITEA_SERVER_URL``GITEA_REPOSITORY` 時使用,避免後續 URL 組裝失敗。 / Used in `main()` for `GITEA_SERVER_URL` and `GITEA_REPOSITORY` so later URL assembly does not fail silently.
- 內部呼叫: `fail()`。 / `fail()`.
## 5. `normalizeBetaFlag(value = 'false')`
- 來源位置: `src/index.js:34-40`
- 完整簽名: `function normalizeBetaFlag(value = 'false')`
- 行為分析: 將空值或字串 `null` 標準化成 `'false'`;其他值則轉成字串後回傳。這讓後續 beta 判斷只處理字串。 / It normalizes empty values or the string `null` to `'false'`; all other values are stringified and returned. This keeps later beta checks string-based.
- 建議摘要: 將 beta 旗標標準化成字串。 / Normalize the beta flag to a string.
- 參數註解:
- `value`: 來源值,預設為 `'false'`。 / Source value, defaulting to `'false'`.
- 備註與使用情境: 當 runner/secret 可能傳入空字串、`null` 或布林字面值時,這個函式可避免型別分歧。 / Useful when runner or secret inputs may arrive as empty strings, `null`, or boolean-like values.
- 內部呼叫: 無。 / None.
## 6. `writeOutput(version)`
- 來源位置: `src/index.js:42-52`
- 完整簽名: `function writeOutput(version)`
- 行為分析: 組出 `version=...` 這一行;若 `GITHUB_OUTPUT` 存在就附加到檔案,否則直接寫到 `stdout`。輸出格式是 action contract 的一部分,這裡僅做路徑分流,不改內容。 / It builds a `version=...` line; if `GITHUB_OUTPUT` exists it appends to that file, otherwise it writes directly to `stdout`. The output format is part of the action contract; this function only chooses the destination and does not alter the payload.
- 建議摘要: 將版本號寫入 GitHub/Gitea action output。 / Write the version to the action output.
- 參數註解:
- `version`: 要輸出的版本字串。 / Version string to emit.
- 備註與使用情境: 在本 action 結尾輸出最終版本,讓下游 workflow 可直接讀取 `version` output。 / Use it at the end of the action so downstream workflows can consume the `version` output.
- 內部呼叫: 無;但依賴 `fs.appendFileSync()``process.env.GITHUB_OUTPUT``process.stdout.write()`。 / None; it depends on `fs.appendFileSync()`, `process.env.GITHUB_OUTPUT`, and `process.stdout.write()`.
## 7. `parseVersionParts(version)`
- 來源位置: `src/index.js:54-61`
- 完整簽名: `function parseVersionParts(version)`
- 行為分析: 將版本字串以 `.` 切開,逐段用十進位整數解析;無法解析的段落會變成 `0`。這是保守的數字化處理,方便後續比較與遞增。 / It splits a version string on `.`, parses each part as a base-10 integer, and turns non-numeric parts into `0`. This is conservative normalization for later comparison and incrementing.
- 建議摘要: 將版本字串拆成數字陣列。 / Split a version string into numeric parts.
- 參數註解:
- `version`: 例如 `1.2.3``v1.2.3` 經前置處理後的純版本字串。 / A version string such as `1.2.3`, usually already stripped of any leading `v`.
- 備註與使用情境: `stableVersionFromTag()``nextReleaseVersion()` 都依賴它來統一版本比較基礎。 / `stableVersionFromTag()` and `nextReleaseVersion()` both rely on it to normalize version math.
- 內部呼叫: 無。 / None.
## 8. `compareVersionParts(left, right)`
- 來源位置: `src/index.js:63-76`
- 完整簽名: `function compareVersionParts(left, right)`
- 行為分析: 依序比較兩個數字陣列,遇到第一個不同的欄位就回傳差值;若全相同則回傳 `0`。這是典型的版本排序 comparator。 / It compares two numeric arrays element by element, returning the first non-zero difference, or `0` if all parts match. This is a standard version-sorting comparator.
- 建議摘要: 比較兩組版本分段大小。 / Compare two version-part arrays.
- 參數註解:
- `left`: 左側版本分段。 / Left-side version parts.
- `right`: 右側版本分段。 / Right-side version parts.
- 備註與使用情境: 在 `latestStableVersion()` 排序 release 版本時使用。 / Used by `latestStableVersion()` when sorting releases.
- 內部呼叫: 無。 / None.
## 9. `stableVersionFromTag(tagName)`
- 來源位置: `src/index.js:78-89`
- 完整簽名: `function stableVersionFromTag(tagName)`
- 行為分析: 排除非字串、含 `-beta.` 的 tag,然後允許 `v1.2.3``1.2.3` 這類純數字版本,最後回傳數字陣列;不符合者回傳 `null`。這是在提取 stable release。 / It rejects non-strings and tags containing `-beta.`, then accepts pure numeric versions like `v1.2.3` or `1.2.3`, returning an array of numbers; anything else returns `null`. This extracts stable releases only.
- 建議摘要: 從 tag 名稱抽出穩定版版本。 / Extract a stable version from a tag name.
- 參數註解:
- `tagName`: release 的 `tag_name`。 / Release `tag_name`.
- 備註與使用情境: 當 release 清單混有 beta tag 與正式 tag 時,用它可以避免 beta 參與最新穩定版計算。 / Useful when a release list mixes beta and stable tags, so beta tags do not affect the stable baseline.
- 內部呼叫: `parseVersionParts()`。 / `parseVersionParts()`.
## 10. `latestStableVersion(releaseJson)`
- 來源位置: `src/index.js:91-107`
- 完整簽名: `function latestStableVersion(releaseJson)`
- 行為分析: 若輸入不是非空陣列就回傳 `0.0.0`;否則先挑出所有穩定版 tag,再用 `compareVersionParts()` 排序,取最後一筆組回 `x.y.z`。這會忽略 beta tag,也會把不足三段的版本以缺省 0 補齊。 / It returns `0.0.0` when the input is not a non-empty array; otherwise it filters stable tags, sorts them with `compareVersionParts()`, and rebuilds the last one as `x.y.z`. Beta tags are ignored, and missing third parts are padded with `0`.
- 建議摘要: 找出最新的正式版版本號。 / Find the latest stable release version.
- 參數註解:
- `releaseJson`: release API 回傳的陣列。 / Array returned by the release API.
- 備註與使用情境: 在計算下一版時作為 base version,確保 beta 只建立在最新正式版之上。 / Used as the base version for next-release calculation so beta builds rest on the latest stable release.
- 內部呼叫: `stableVersionFromTag()``compareVersionParts()`。 / `stableVersionFromTag()` and `compareVersionParts()`.
## 11. `nextReleaseVersion(latestVersion)`
- 來源位置: `src/index.js:109-127`
- 完整簽名: `function nextReleaseVersion(latestVersion)`
- 行為分析: 將版本拆成 major/minor/patch 後只先遞增 patch;若 patch 到 10 就進位到 minorminor 到 10 再進位到 major。這是專案自訂的十進位進位規則,不是一般 npm semver 的語意。 / It splits a version into major/minor/patch, increments patch first, then carries into minor at 10 and into major at 10. This is a project-specific decimal carry rule, not standard npm semver behavior.
- 建議摘要: 計算下一個正式版版本號。 / Compute the next stable release version.
- 參數註解:
- `latestVersion`: 目前最新正式版,例如 `1.2.9`。 / Current stable version, for example `1.2.9`.
- 備註與使用情境: 當 release 進入下一版時,以這個函式產生候選正式版。 / Use it to derive the candidate stable version for the next release.
- 內部呼叫: `parseVersionParts()`。 / `parseVersionParts()`.
## 12. `nextBetaNumber(releaseJson, version)`
- 來源位置: `src/index.js:129-146`
- 完整簽名: `function nextBetaNumber(releaseJson, version)`
- 行為分析: 在 release 清單中找出符合 `v{version}-beta.N` 的 tag,取最大 `N` 再加 1;若找不到就從 1 開始。這只對指定版本前綴生效。 / It scans the release list for tags matching `v{version}-beta.N`, takes the maximum `N`, and adds one; if none are found, it starts at 1. It only applies to the specified version prefix.
- 建議摘要: 取得下一個 beta 序號。 / Get the next beta sequence number.
- 參數註解:
- `releaseJson`: release API 回傳的陣列。 / Array returned by the release API.
- `version`: 目標正式版版本字串,例如 `1.2.4`。 / Target stable version string, such as `1.2.4`.
- 備註與使用情境: 當要發行同一個正式版的多個 beta 時,這個函式可確保序號遞增。 / Use it when multiple beta builds are published for the same stable target so the suffix increments correctly.
- 內部呼叫: 無。 / None.
## 13. `calculateVersion(releaseJson, isBeta)`
- 來源位置: `src/index.js:148-164`
- 完整簽名: `function calculateVersion(releaseJson, isBeta)`
- 行為分析: 先處理空 release 清單的起始值,再依 `isBeta` 決定回傳正式版或 beta 版的 `[latestTag, newVersion]`。beta 模式會以最新正式版為 base,再加上下一個 beta 序號。 / It first handles the empty-release baseline, then returns either a stable or beta `[latestTag, newVersion]` tuple depending on `isBeta`. In beta mode it uses the latest stable release as the base and appends the next beta number.
- 建議摘要: 計算最新版與下一版。 / Calculate the latest and next version.
- 參數註解:
- `releaseJson`: release 清單。 / Release list.
- `isBeta`: 是否輸出 beta 版本,期望值為 `'true'` 或其他字串。 / Whether to emit a beta version; expected values are `'true'` or another string.
- 備註與使用情境: 這是 action 的核心版本決策點,`main()` 只負責組裝輸入與輸出結果。 / This is the actions core versioning decision point; `main()` only gathers inputs and emits the result.
- 內部呼叫: `latestStableVersion()``nextReleaseVersion()``nextBetaNumber()`。 / `latestStableVersion()`, `nextReleaseVersion()`, and `nextBetaNumber()`.
## 14. `requestJson(url, token)`
- 來源位置: `src/index.js:166-209`
- 完整簽名: `function requestJson(url, token)`
- 行為分析: 依 URL 協定選擇 `http``https`,以 GET 發送請求,必要時加上 token header,收集回應後嘗試解析 JSON。非 2xx 會丟錯,空 body 會回傳空字串。 / It picks `http` or `https` from the URL protocol, sends a GET request, adds a token header when needed, collects the response, and tries to parse JSON. Non-2xx responses throw, and an empty body resolves to an empty string.
- 建議摘要: 以 Promise 包裝的 JSON HTTP 請求。 / Promise-wrapped JSON HTTP request.
- 參數註解:
- `url`: `URL` 物件,指向 release API。 / `URL` object pointing to the release API.
- `token`: 存取 token,可為空。 / Access token, optional.
- 備註與使用情境: 這個函式負責所有 release API 取資料的底層 I/O,錯誤訊息已針對 API 回應格式做保守處理。 / This function handles the low-level I/O for all release API fetches, with conservative error handling for API responses.
- 內部呼叫: 無;但使用 `http.request()``https.request()``JSON.parse()`。 / None; it uses `http.request()`, `https.request()`, and `JSON.parse()`.
## 15. `fetchReleases(baseUrl, repository, token)`
- 來源位置: `src/index.js:211-241`
- 完整簽名: `async function fetchReleases(baseUrl, repository, token)`
- 行為分析: 以固定頁大小分頁呼叫 release API,直到回應為空、`null` 或最後一頁資料少於 `RELEASES_PER_PAGE` 為止,並把所有頁面串成一個陣列。這是典型的保守型 pagination 收集。 / It pages through the release API with a fixed page size until the response is empty, `null`, or shorter than `RELEASES_PER_PAGE`, concatenating all pages into one array. This is a conservative pagination collector.
- 建議摘要: 取得所有 release 頁面資料。 / Fetch all paginated release data.
- 參數註解:
- `baseUrl`: Gitea server base URL。 / Gitea server base URL.
- `repository`: `owner/repo` 格式的 repository 名稱。 / Repository name in `owner/repo` form.
- `token`: API 存取 token。 / API access token.
- 備註與使用情境: 當 repository release 數量超過單頁上限時,這個函式確保版本計算看到完整資料。 / Use it when release counts exceed one page so version calculation still sees the full dataset.
- 內部呼叫: `requestJson()``info()`。 / `requestJson()` and `info()`.
## 16. `main()`
- 來源位置: `src/index.js:243-267`
- 完整簽名: `async function main()`
- 行為分析: 依序做參數檢查、組出 release URL、抓取 release、計算版本、寫出 output。它同時負責 log 分段與 env 讀取,是整個 action 的執行入口。 / It performs parameter checks, builds the release URL, fetches releases, calculates the version, and writes the output. It also manages stage logging and environment reads, making it the actions execution entry point.
- 建議摘要: 執行版本計算流程。 / Run the version calculation flow.
- 參數註解: 無。 / None.
- 備註與使用情境: 直接執行 `node src/index.js` 時會走這個函式;在 action 容器內也是主要入口。 / This runs when executing `node src/index.js` directly and is also the main entry inside the action container.
- 內部呼叫: `section()``requireEnv()``normalizeBetaFlag()``info()``fetchReleases()``calculateVersion()``writeOutput()`。 / `section()`, `requireEnv()`, `normalizeBetaFlag()`, `info()`, `fetchReleases()`, `calculateVersion()`, and `writeOutput()`.
+19 -1
View File
@@ -1,3 +1,8 @@
# --------------------------------------------------
# 用途:定義 CI 工作流程,處理 pull request 的版本發佈與測試。
# 更新日期:2026/07/11 17:44:33Asia/Taipei
# --------------------------------------------------
name: CI
on:
pull_request:
@@ -38,10 +43,23 @@ jobs:
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 }}
+9 -2
View File
@@ -1,3 +1,8 @@
# --------------------------------------------------
# 用途:定義 CD 工作流程,於 master push 後部署並顯示相關 tag。
# 更新日期:2026/07/11 17:44:33Asia/Taipei
# --------------------------------------------------
name: CD
on:
push:
@@ -16,6 +21,8 @@ jobs:
fetch-depth: 0
- name: Get Commit Tag
id: commit
run: echo "tag=$(git describe --contains ${{ env.COMMIT_SHA }})" >> $GITEA_OUTPUT
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 }}"
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
+151
View File
@@ -0,0 +1,151 @@
# calculate-version
更新時間:2026/07/11 17:53:32Asia/Taipei
這個專案是一個 Gitea Docker action,會讀取 repository 的 release 清單,計算下一個正式版或 beta 版本號,並將結果輸出成 action output。
## 專案列表
### 專案描述表
| 專案名稱 | 專案描述 |
| --- | --- |
| [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/ai-review-resolve/develop-20260711-150530/) | 無 |
### NuGet 套件表
| 專案名稱 | NuGet 套件列表 |
| --- | --- |
| [calculate-version](https://gitea.jsc.idv.tw/actions/calculate-version/src/branch/ai-review-resolve/develop-20260711-150530/) | 無 |
## 功能列表
### calculate-version
| 功能名稱 | 功能描述 |
| --- | --- |
| [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) |
## 使用範例
### <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 "$@"
+144 -3
View File
@@ -4,19 +4,55 @@ const https = require('https');
const { URL } = require('url');
const RELEASES_PER_PAGE = 10;
let currentStage = null;
/**
* 設定目前訊息輸出的階段名稱。
*
* @param {string} title 階段名稱,通常是流程區塊名稱。
* @remarks
* 供 `info()` 與錯誤輸出使用,讓訊息可以帶上統一前綴。
*/
function section(title) {
process.stdout.write(`\n==================================================\n${title}\n--------------------------------------------------\n`);
currentStage = title;
}
/**
* 以統一格式輸出資訊訊息。
*
* @param {string} message 要輸出的訊息內容。
* @remarks
* 會附加目前階段與 Asia/Taipei 時區時間戳記,適合用於流程進度與狀態輸出。
*/
function info(message) {
process.stdout.write(`[info] ${message}\n`);
const timestamp = new Date()
.toLocaleString('sv-SE', { timeZone: 'Asia/Taipei', hour12: false })
.replace(/-/g, '/');
const stagePrefix = currentStage ? `[${currentStage}]` : '';
process.stdout.write(`${stagePrefix}[INF][${timestamp}]: ${message}\n`);
}
/**
* 以例外方式終止流程。
*
* @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} 未設定`);
@@ -25,6 +61,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';
@@ -33,6 +77,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;
@@ -45,6 +96,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('.')
@@ -54,6 +113,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);
@@ -69,6 +137,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;
@@ -82,6 +158,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';
@@ -100,6 +184,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;
@@ -120,6 +212,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;
@@ -139,6 +240,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'
@@ -157,6 +267,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;
@@ -202,6 +321,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;
@@ -234,6 +363,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('參數檢查');
@@ -262,7 +398,12 @@ async function main() {
if (require.main === module) {
main().catch((error) => {
process.stderr.write(`[error] ${error.message}\n`);
const timestamp = new Date()
.toLocaleString('sv-SE', { timeZone: 'Asia/Taipei', hour12: false })
.replace(/-/g, '/');
const stagePrefix = currentStage ? `[${currentStage}]` : '';
process.stderr.write(`${stagePrefix}[ERR][${timestamp}]: ${error.message}\n`);
process.exit(1);
});
}