Compare commits

..

77 Commits

Author SHA1 Message Date
jiantw83 9ffdf2241a Merge pull request 'feat/ai_code_review' (#60) from feat/ai_code_review into develop
Reviewed-on: #60
2026-05-15 17:17:13 +00:00
AI Review Bot 9caf84432f chore: update ai-review findings [ai-review-bot][success] 2026-05-15 17:13:07 +00:00
jiantw83 74c544d4e4 chore: update review exclusions 2026-05-15 17:11:52 +00:00
AI Review Bot be954f4cb1 chore: update ai-review findings [ai-review-bot][success] 2026-05-15 17:08:18 +00:00
jiantw83 9e0253f74d chore: triage review findings 2026-05-15 17:07:00 +00:00
AI Review Bot fadcf9b14c chore: update ai-review findings [ai-review-bot][failure] 2026-05-15 16:57:14 +00:00
jiantw83 4bdde4f7ce test: harden version entrypoint 2026-05-15 16:55:04 +00:00
AI Review Bot 8cef794557 chore: update ai-review findings [ai-review-bot][failure] 2026-05-15 16:18:42 +00:00
jiantw83 6b7f257664 refactor: simplify version entrypoint output 2026-05-15 16:16:01 +00:00
jiantw83 7e181a8ab2 chore: adjust workflow release and review settings 2026-05-15 16:13:15 +00:00
jiantw83 43cddbd2c4 feat: update workflow names for clarity and add AI code review workflow 2026-05-13 03:28:58 +00:00
Jeffery d407899afb feat: 將參數輸出移到最後一個步驟 2026-03-26 14:51:28 +08:00
jiantw83 54cba9b783 更新 action.yaml 2026-03-25 06:40:59 +00:00
jiantw83 e6708957b0 更新 Dockerfile 2026-03-25 05:42:30 +00:00
jiantw83 4e2af3ffe5 更新 action.yaml 2026-03-25 02:46:44 +00:00
Jeffery 11c39bc75a test: 修正基本版本號的過濾方法 2026-03-24 18:34:55 +08:00
Jeffery 76006da109 Merge branch 'develop' of https://gitea.jsc.idv.tw/actions/calculate-version into develop 2026-03-24 18:17:22 +08:00
Jeffery 16d2afca48 feat: 加入 beta 版本計算 2026-03-24 18:17:18 +08:00
jiantw83 28aed39d80 Merge branch 'develop' of https://gitea.jsc.idv.tw/actions/calculate-version into develop 2026-03-21 19:47:28 +08:00
jiantw83 5c70769ed3 feat: 將清理成品的行為合併到發布專案 2026-03-21 19:47:23 +08:00
jiantw83 9a79e99f42 更新 .gitea/workflows/master.yaml 2026-03-21 07:21:41 +00:00
jiantw83 535e2fb2c2 feat: 加上 Gitea Runner Token 2026-03-21 10:58:38 +08:00
jiantw83 ea5f9e2399 feat: 清理成品 2026-03-21 10:53:09 +08:00
Jeffery 0ccffa8b1c feat: 變更工作流名稱 2026-03-20 18:14:16 +08:00
Jeffery bbf48129b4 fix: 修正工作流相依 2026-03-20 18:09:37 +08:00
Jeffery 3a04e691db feat: 重構 CD 工作流 2026-03-20 18:07:23 +08:00
Jeffery fb052a75c4 test: 使用不同的執行器 2026-03-20 18:03:42 +08:00
Jeffery 697311db2d feat: 移除無用的部分 2026-03-20 17:47:26 +08:00
Jeffery 559cb45312 feat: 改回檔案的方式繼續想怎麼處理 2026-03-20 17:38:00 +08:00
Jeffery 8cf6a41f3c test: 相信 AI 再試一次 GITHUB_OUTPUT 2026-03-20 17:31:29 +08:00
Jeffery b418abbbac feat: 修正工作流 2026-03-20 17:26:04 +08:00
Jeffery 94f0e99392 feat: 顯示另一個工作目錄 2026-03-20 17:22:49 +08:00
Jeffery e382432c7b feat: 顯示工作目錄的內容 2026-03-20 17:22:20 +08:00
Jeffery dfaa8530e1 feat: 從不同的工作目錄取得版本檔案 2026-03-20 17:20:48 +08:00
Jeffery 3a95e3e96b test: 從共用資料夾讀取版本檔案 2026-03-20 17:13:34 +08:00
Jeffery 6041e730fa feat: 更新版本號 2026-03-20 17:12:10 +08:00
Jeffery 20d104ab9b test: 將版本檔案放到共用資料夾 2026-03-20 17:11:41 +08:00
Jeffery f63edd5214 fix: 修正指令 2026-03-20 17:09:35 +08:00
Jeffery 22d14c0e9f feat: 發布新版 2026-03-20 17:07:22 +08:00
Jeffery f03aa4ba23 feat: 在外部書出版本號 2026-03-20 17:06:00 +08:00
Jeffery cf3194631f feat: 更新版本 2026-03-20 17:03:57 +08:00
Jeffery 7426c5d835 test: 嘗試使用兩種模式 2026-03-20 17:03:33 +08:00
Jeffery 58ae22ed1e feat: 使用新版 2026-03-20 16:51:56 +08:00
Jeffery 4e236c8d0f feat: 發布新版 2026-03-20 16:51:35 +08:00
Jeffery d7cd868c04 feat: 使用新版本 2026-03-20 16:49:42 +08:00
Jeffery 3ca0c5a9ab feat: 使用新版本 2026-03-20 16:45:44 +08:00
Jeffery 8a9c4d1ca9 feat: 發布新版本 2026-03-20 16:45:14 +08:00
Jeffery de7804c7d1 feat: 使用新版本 2026-03-20 16:42:44 +08:00
Jeffery ccd4ef297e feat: 更新版本號 2026-03-20 16:42:16 +08:00
Jeffery 6ff37643b6 test: 嘗試排除沒有資料夾的問題 2026-03-20 16:41:49 +08:00
Jeffery 9a15897128 feat: 使用新版本 2026-03-20 16:39:09 +08:00
Jeffery a6bc95e595 feat: 更新版本 2026-03-20 16:38:36 +08:00
Jeffery 2aa38cd6db feat: 修正邏輯 2026-03-20 16:37:45 +08:00
Jeffery 52bdc38674 feat: 如果沒有 token 就不需要處理 2026-03-20 16:32:06 +08:00
Jeffery 309b4e9d81 feat: 更新版本號 2026-03-20 16:26:52 +08:00
Jeffery 40caa20c9f feat: 重新 CD 2026-03-20 16:25:24 +08:00
Jeffery 655a954261 feat: 沒有 runner token 也不要出錯 2026-03-20 16:24:40 +08:00
Jeffery fbcb52e04e feat: 顯示資訊 2026-03-20 16:21:40 +08:00
Jeffery 381beced2c feat: 更新版本號 2026-03-20 16:18:35 +08:00
Jeffery f2825c3740 feat: 從工具外部取得 token 2026-03-20 16:17:30 +08:00
Jeffery 919886cb55 feat: 從工具外部取得 token 2026-03-20 16:16:05 +08:00
Jeffery 9dfb8f1ecb feat: 只能拉舊的版本 2026-03-20 16:12:14 +08:00
Jeffery 446392f75e feat: 發布專案後,嘗試計算版本號 2026-03-20 16:11:26 +08:00
Jeffery 5c8d62ca6d feat: 嘗試工具是否有被修正 2026-03-20 16:09:50 +08:00
Jeffery 52627c620d feat: 強制發布版本 2026-03-20 16:09:02 +08:00
Jeffery 1e76690fa6 Revert "test: 改用 ubuntu 嘗試執行腳本"
This reverts commit d3abfab99a.
2026-03-20 16:08:05 +08:00
Jeffery d61d76244d Revert "test: 修改編碼嘗試執行腳本"
This reverts commit 66b143f3f7.
2026-03-20 16:08:01 +08:00
Jeffery 2c17bc5720 Revert "feat: 移除 entrypoint 的 /r"
This reverts commit ed3de05b12.
2026-03-20 16:07:57 +08:00
Jeffery dfb9608d3d Revert "feat: 安裝 bash 套件"
This reverts commit b6dabdbbf8.
2026-03-20 16:07:53 +08:00
Jeffery b6dabdbbf8 feat: 安裝 bash 套件 2026-03-20 16:05:32 +08:00
Jeffery ed3de05b12 feat: 移除 entrypoint 的 /r 2026-03-20 16:03:06 +08:00
Jeffery 66b143f3f7 test: 修改編碼嘗試執行腳本 2026-03-20 15:58:58 +08:00
Jeffery d3abfab99a test: 改用 ubuntu 嘗試執行腳本 2026-03-20 15:56:46 +08:00
Jeffery 98cee8fc65 feat: 安裝 bash 工具用來執行腳本 2026-03-20 15:54:14 +08:00
Jeffery 0259dbe114 feat: 強制指定計算版本號的工具版本 2026-03-20 15:52:20 +08:00
Jeffery eb67824f3e feat: 使用最新的工具計算版本號 2026-03-20 15:48:10 +08:00
Jeffery a2ab02b8c1 feat: 重構計算版本號 2026-03-20 15:46:26 +08:00
18 changed files with 936 additions and 195 deletions
+14
View File
@@ -0,0 +1,14 @@
# Triage Findings
When the task is to triage review findings, follow this workflow:
1. Merge all findings into one list.
2. Remove duplicates.
3. Sort by severity: `critical` -> `warning` -> `info`.
4. Renumber from 1 after sorting.
5. Fix real issues with the smallest safe change.
6. Add false positives to `.gitea/ai-review/exclusions.json`, preserving the original wording, language, and semantics as much as possible.
7. Add or update tests when behavior changes.
8. Re-check the issue after each fix.
Use the repo-local `triage-findings` skill for the same workflow when running in Codex.
+29
View File
@@ -0,0 +1,29 @@
---
name: triage-findings
description: Triage findings, fix real issues, and exclude false positives.
---
# Triage Findings
## Use
直接輸入:`triage-findings 問題原始檔(文字或截圖)`
## Workflow
1. Merge all findings.
2. Sort by severity:
- critical
- warning
- info
3. Renumber from 1.
4. Fix real issues.
5. Put false positives into `.gitea/ai-review/exclusions.json`, preserving the original wording, language, and semantics as much as possible.
6. Add tests when behavior changes.
## Output Rules
- Keep the final list short.
- Keep numbering contiguous.
- Preserve file path, location, and fix.
- When writing exclusions, prefer the original issue text over paraphrased rewrites.
+45
View File
@@ -0,0 +1,45 @@
---
name: triage-findings
description: Merge code-review findings, sort and renumber them by severity, resolve real issues, and move false positives into exclusions.
---
# Triage Findings
## When To Use
Use this skill when you receive multiple review findings, screenshots, comments, or issue lists that need to become one final triaged list.
It is also used when some findings are false positives and should be moved into the exclusions list.
## Workflow
1. Collect all findings into one list.
2. Merge duplicates into a single finding when they describe the same issue.
3. Sort the final list by severity:
- critical
- warning
- info
4. Renumber the sorted list from 1 upward.
5. Rewrite each finding concisely so the final list reads cleanly and consistently.
6. If a finding is a false positive, do not keep it in the final list.
7. Add false positives to the exclusions list using the existing schema in the repo or task context, and preserve the original finding wording as much as possible, including language and semantics.
## Resolution Flow
After the list is merged and ordered, resolve the remaining findings one by one.
1. Start from the highest severity item.
2. Identify the root cause in the relevant file or context.
3. Apply the smallest safe change that fixes the issue.
4. Add or update tests when behavior changes.
5. Re-check the issue after the change.
6. If the item is confirmed false positive, move it to exclusions instead of changing code.
7. Continue until the list is either fixed or explicitly excluded.
## Output Rules
- Keep the final findings list in severity order, then by any stable secondary order needed to make it readable.
- Keep numbering contiguous after filtering and merging.
- Preserve useful details like file path, location, and suggested fix.
- Keep exclusions entries minimal and consistent with the project schema.
- When writing exclusions, prefer the original issue text and language; only paraphrase if needed to fit the schema.
- If the source already provides a severity or title, keep it unless it conflicts with the final ordering.
@@ -0,0 +1,4 @@
interface:
display_name: "Triage Findings"
short_description: "Triage, sort, fix, and exclude review findings"
default_prompt: "Use $triage-findings to merge review findings, sort and renumber them by severity, resolve real issues one by one, and add false positives to exclusions."
+29
View File
@@ -0,0 +1,29 @@
---
name: triage-findings
description: Triage findings, fix real issues, and exclude false positives.
---
# Triage Findings
## Use
直接輸入:`triage-findings 問題原始檔(文字或截圖)`
## Workflow
1. Merge all findings.
2. Sort by severity:
- critical
- warning
- info
3. Renumber from 1.
4. Fix real issues.
5. Put false positives into `.gitea/ai-review/exclusions.json`, preserving the original wording, language, and semantics as much as possible.
6. Add tests when behavior changes.
## Output Rules
- Keep the final list short.
- Keep numbering contiguous.
- Preserve file path, location, and fix.
- When writing exclusions, prefer the original issue text over paraphrased rewrites.
+86
View File
@@ -0,0 +1,86 @@
[
{
"level": "critical",
"role": "Aria",
"location": "entrypoint.sh",
"suggestion": "檔案結尾缺少一個換行符號。根據 POSIX 規範,所有文字檔案都應以換行符號結束。請在檔案的最後一行後新增一個換行符號。"
},
{
"level": "critical",
"role": "Maya",
"location": "entrypoint.sh:86-91",
"suggestion": "版本號遞增邏輯中,當 `PATCH` 達到 `10` 時會遞增 `MINOR`,當 `MINOR` 達到 `10` 時會遞增 `MAJOR`。這與標準的語義化版本控制(Semantic Versioning)慣例不同(例如,通常 `1.9.9` 之後是 `1.9.10`,而非 `2.0.0`)。請釐清預期的版本控制方案。如果目標是標準語義版本,則應移除 `MINOR` 和 `MAJOR` 在 `10` 時的進位邏輯。如果這是一個自訂的十進位版本系統,請務必清楚文件化此行為,並新增特定測試案例,例如 `v0.0.9`、`v0.9.9`、`v0.9.0`、`v1.9.9`,以驗證進位行為是否符合預期。"
},
{
"level": "warning",
"role": "Rex",
"location": "entrypoint.sh:42, entrypoint.sh:43, entrypoint.sh:55",
"suggestion": "GITEA_SERVER_URL 和 GITEA_REPOSITORY 變數直接用於構建 URL。雖然在 CI/CD 環境中這些變數通常被視為受信任的輸入,但若這些輸入來源不可信,可能導致伺服器端請求偽造(SSRF)或路徑遍歷攻擊。建議對這些數據進行更嚴格的輸入驗證,例如檢查 URL 是否符合預期的格式和網域,並確保儲存庫名稱不包含惡意字元(如 `../` 或特殊編碼字元)。"
},
{
"level": "warning",
"role": "Aria",
"location": "entrypoint.sh:44",
"suggestion": "`require_env` 函數中的 `printf '%s=%s\n' \"$name\" \"$value\"` 會直接回顯變數數值。對於 `RUNNER_TOKEN` 這類敏感資訊,這可能導致安全風險。建議修改此函數,使其僅回顯變數名稱,或對敏感變數的值進行遮蔽處理。例如,可以改為 `info \"$name 已設定\"`,或在呼叫 `require_env` 之前處理敏感變數的輸出。"
},
{
"level": "info",
"role": "Rex",
"location": "entrypoint.sh:109",
"suggestion": "將 NEW_VERSION 輸出到 GITHUB_OUTPUT 時,雖然目前 NEW_VERSION 的格式(如 `X.Y.Z` 或 `X.Y.Z-beta.N`)相對安全,不太可能包含換行符或其他特殊字元,但一般而言,應確保輸出到環境變數或檔案的內容不包含可能導致指令注入的特殊字元。對於複雜或多行內容,應使用 GitHub Actions 建議的多行輸出語法以確保安全。"
},
{
"level": "critical",
"role": "Leo",
"location": "entrypoint.sh:98-142",
"suggestion": "在 `calculate_version` 函式中,`jq` 腳本的邏輯過於複雜且龐大。重新實作了 `latest_stable_version`、`next_release_version`、`next_beta_number` 等邏輯。Bash 與 jq 的跨語言邏輯重複,增加維護難度與錯誤風險。建議:將 `calculate_version` 改寫成純 Bash。使用外部 `jq` 篩選器而非 inline jq。若必須使用 jq,建議拆分成多個函式並使用:`read -r -d '' JQ_SCRIPT << 'EOF'` 以提高可讀性。"
},
{
"level": "critical",
"role": "Leo",
"location": "tests/entrypoint_test.sh:50-109",
"suggestion": "在 `make_mock_jq` 函式中,為了測試 `entrypoint.sh`,於 Python 中重新實作核心版本計算邏輯。測試程式重複了正式程式的 jq 腳本。若 `entrypoint.sh` 的版本計算邏輯變更,Mock 也需同步更新,容易造成錯誤。建議:簡化 `entrypoint.sh` 的 jq 邏輯(如前一建議)。Mock jq 僅根據輸入回傳預定義輸出,而非重新計算邏輯。"
},
{
"level": "warning",
"role": "Zara",
"location": "entrypoint.sh:65, entrypoint.sh:90",
"suggestion": "腳本多次對完整的 `RELEASE_JSON` 字串執行 `jq` 命令來提取不同資訊。儘管 `RELEASE_JSON` 已載入記憶體,但每次 `jq` 呼叫都會啟動新程序並重新解析字串。對於擁有大量發行版本的儲存庫,這可能導致顯著的效能開銷。建議考慮將這些 `jq` 操作合併單次處理,以一次性提取所有所需資料(最新的穩定版本和首個出的新版本最高 beta 編號),從而減少 CPU 週期和程序啟動的開銷。"
},
{
"level": "warning",
"role": "Zara",
"location": "entrypoint.sh:58",
"suggestion": "腳本從 Gitea API 獲取所有發行版本。如果儲存庫中的發行版本數量非常龐大,這可能會消耗大量的網路頻寬和記憶體。儘管目前的邏輯可能需要檢查多個發行版本(例如,尋找最新的穩定版或特定的 beta 版本),但值得研究 Gitea API 是否提供更精細的過濾(例如,按標籤名稱模式或排除 beta 標籤)或分頁功能,以減少獲取的資料量,特別是當腳本只需要部分發行版本資訊時。"
},
{
"level": "warning",
"role": "Leo",
"location": "entrypoint.sh:159",
"suggestion": "在 `main` 函式中,`curl -fsS` 會抑制進度與錯誤細節。雖然 `set -e` 會在 curl 失敗時終止,但 stderr 的輸出資訊不足。建議在 `fail` 區塊中提供更明確錯誤內容,或包裝 `curl -fsS -H ... \"$RELEASE_URL\"` 以便記錄更具體的失敗原因。"
},
{
"level": "warning",
"role": "Leo",
"location": "entrypoint.sh:110",
"suggestion": "`calculate_version` 中的 jq 腳本使用魔術數字 `10` 用於 patch >= 10 判斷。建議提取為具名常數,或改用 Bash `readonly` 常數。"
},
{
"level": "warning",
"role": "Maya",
"location": "tests/entrypoint_test.sh:209",
"suggestion": "`next_release_version` 函數的測試案例涵蓋了:patch、minor 版本進位,但缺少 major 版本進位測試。例如:`99.9.9 -> 100.0.0`。建議新增此邊界條件測試,以確保版本號計算完整性。"
},
{
"level": "warning",
"role": "Maya",
"location": "tests/entrypoint_test.sh:209",
"suggestion": "目前測試案例中,雖然 `run_entrypoint` 函數接受 `token` 參數,但沒有明確測試驗證 `RUNNER_TOKEN` 存在時,`curl` 是否正確使用授權標頭。建議新增測試案例驗證 `RUNNER_TOKEN` 行為。"
},
{
"level": "warning",
"role": "Maya",
"location": "tests/entrypoint_test.sh:209",
"suggestion": "測試腳本執行過程中會建立暫存檔案與目錄:`response_file`、`workdir`,但目前沒有明確清理機制。可能導致測試環境累積不必要檔案。建議在測試腳本開頭或結尾加入:`trap 'rm -rf \"$workdir\" \"$response_file\"' EXIT`,以確保測試完成後所有暫存資源皆被妥善移除。"
}
]
+1
View File
@@ -0,0 +1 @@
[]
+14 -14
View File
@@ -1,28 +1,28 @@
name: CD
on: on:
push: push:
branches: branches:
- master - master
jobs: jobs:
cd: version:
name: "CD > 發布專案" name: 計算版本號
runs-on: docker runs-on: ubuntu
env: outputs:
RUNNER_TOOL_CACHE: /toolcache version: ${{ steps.version.outputs.version }}
steps: steps:
- name: 計算版本號 - name: 計算版本號
id: version id: version
uses: https://gitea.jsc.idv.tw/actions/calculate-version@${{ vars.ACTION_CALCULATE_VERSION }} uses: https://gitea.jsc.idv.tw/actions/calculate-version@${{ vars.ACTION_CALCULATE_VERSION }}
with: release:
gitea-server: ${{ gitea.server_url }} name: 發布專案
repository: ${{ gitea.repository }} runs-on: ubuntu
token: ${{ secrets.GITEA_TOKEN }} needs: version
steps:
- name: 發布專案 - name: 發布專案
uses: akkuman/gitea-release-action@${{ vars.ACTION_RELEASE_VERSION }} uses: akkuman/gitea-release-action@${{ vars.ACTION_RELEASE_VERSION }}
with: with:
tag_name: "v${{ steps.version.outputs.VERSION }}" tag_name: v${{ needs.version.outputs.version }}
- name: 清理舊版本 (保留最新2個) - name: 清理成品
uses: https://gitea.jsc.idv.tw/actions/cleanup-release@${{ vars.ACTION_CLEANUP_RELEASE_VERSION }} uses: https://gitea.jsc.idv.tw/actions/cleanup-release@${{ vars.ACTION_CLEANUP_RELEASE_VERSION }}
with: with:
gitea-server: ${{ gitea.server_url }} RUNNER_TOKEN: ${{ secrets.RUNNER_TOKEN }}
repository: ${{ gitea.repository }}
token: ${{ secrets.GITEA_TOKEN }}
+23
View File
@@ -0,0 +1,23 @@
name: AI
on:
pull_request:
branches-ignore:
- master
types: [opened, synchronize]
jobs:
code-review:
name: Code Review
runs-on: ubuntu
steps:
- name: AI Code Review
uses: https://gitea.jsc.idv.tw/actions/code-review@${{ vars.ACTION_CODE_REVIEW_VERSION }}
with:
GITEA_TOKEN: ${{ secrets.RUNNER_TOKEN }}
GITEA_COMMENT_TOKEN: ${{ secrets.GITEA_TOKEN }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }},${{ secrets.GEMINI_API_KEY_1 }},${{ secrets.GEMINI_API_KEY_2 }},${{ secrets.GEMINI_API_KEY_3 }},${{ secrets.GEMINI_API_KEY_4 }},${{ secrets.GEMINI_API_KEY_5 }},${{ secrets.GEMINI_API_KEY_6 }},${{ secrets.GEMINI_API_KEY_7 }},${{ secrets.GEMINI_API_KEY_8 }},${{ secrets.GEMINI_API_KEY_9 }},${{ secrets.GEMINI_API_KEY_10 }},${{ secrets.GEMINI_API_KEY_11 }},${{ secrets.GEMINI_API_KEY_12 }},${{ secrets.GEMINI_API_KEY_13 }},${{ secrets.GEMINI_API_KEY_14 }},${{ secrets.GEMINI_API_KEY_15 }},${{ secrets.GEMINI_API_KEY_16 }},${{ secrets.GEMINI_API_KEY_17 }},${{ secrets.GEMINI_API_KEY_18 }},${{ secrets.GEMINI_API_KEY_19 }}
GEMINI_BASE_URL: https://generativelanguage.googleapis.com/v1beta
GEMINI_MODEL: ${{ vars.GEMINI_MODEL }}
permissions:
contents: write
pull-requests: write
issues: write
+14
View File
@@ -0,0 +1,14 @@
# Triage Findings
Use the triage-finding workflow for review issue lists:
1. Merge findings into one list.
2. Remove duplicates.
3. Sort by severity: `critical` -> `warning` -> `info`.
4. Renumber from 1.
5. Fix real issues with the smallest safe change.
6. Put false positives into `.gitea/ai-review/exclusions.json`, preserving the original wording, language, and semantics as much as possible.
7. Add or update tests when behavior changes.
8. Re-check after each fix.
The full reusable skill lives in `.claude/skills/triage-findings/SKILL.md`.
+14
View File
@@ -0,0 +1,14 @@
# Triage Findings
Use the triage-finding workflow for review issue lists:
1. Merge findings into one list.
2. Remove duplicates.
3. Sort by severity: `critical` -> `warning` -> `info`.
4. Renumber from 1.
5. Fix real issues with the smallest safe change.
6. Put false positives into `.gitea/ai-review/exclusions.json`, preserving the original wording, language, and semantics as much as possible.
7. Add or update tests when behavior changes.
8. Re-check after each fix.
The reusable skill lives in `.gemini/skills/triage-findings/SKILL.md`.
+16
View File
@@ -0,0 +1,16 @@
# Triage Findings
When the task is to triage review findings, follow this workflow:
1. Merge all findings into one list.
2. Remove duplicates.
3. Sort by severity: `critical` -> `warning` -> `info`.
4. Renumber from 1 after sorting.
5. Fix real issues with the smallest safe change.
6. Add false positives to `.gitea/ai-review/exclusions.json`, preserving the original wording, language, and semantics as much as possible.
7. Add or update tests when behavior changes.
8. Re-check the issue after each fix.
Use the repo-local `triage-findings` skill for the same workflow when running in Codex.
Trigger it with `/triage-findings`.
+10
View File
@@ -0,0 +1,10 @@
FROM alpine:latest
# 安裝必要的工具
RUN apk add --no-cache --no-check-certificate bash curl jq
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
+14
View File
@@ -0,0 +1,14 @@
# Triage Findings
Use the triage-finding workflow for review issue lists:
1. Merge findings into one list.
2. Remove duplicates.
3. Sort by severity: `critical` -> `warning` -> `info`.
4. Renumber from 1.
5. Fix real issues with the smallest safe change.
6. Put false positives into `.gitea/ai-review/exclusions.json`, preserving the original wording, language, and semantics as much as possible.
7. Add or update tests when behavior changes.
8. Re-check after each fix.
The reusable skill lives in `.gemini/skills/triage-findings/SKILL.md`.
+14 -84
View File
@@ -1,88 +1,18 @@
name: 'Calculate Version' name: 'CALCULATE VERSION'
description: '自動計算語義化版本號' description: '計算版本號'
author: 'Jeffery'
inputs: inputs:
gitea-server: IS_BETA:
description: 'Gitea 伺服器 URL' description: '是否為 beta 版本'
required: true default: "false"
repository:
description: '儲存庫名稱 (格式: owner/repo)'
required: true
token:
description: 'Gitea API Token'
required: true
initial-version:
description: '初始版本號'
required: false
default: '0.0.1'
patch-limit:
description: '小版號上限 (達到時進位中版號)'
required: false
default: '10'
minor-limit:
description: '中版號上限 (達到時進位大版號)'
required: false
default: '10'
outputs: outputs:
version: version:
description: '計算出的版本號' description: '計算出的版本號'
value: ${{ steps.calculate.outputs.version }}
previous-version:
description: '上一個版本號'
value: ${{ steps.calculate.outputs.previous-version }}
runs: runs:
using: 'composite' using: 'docker'
steps: image: 'Dockerfile'
- name: 計算版本號 env:
id: calculate GITEA_SERVER_URL: ${{ gitea.server_url }}
shell: bash GITEA_REPOSITORY: ${{ gitea.repository }}
run: | RUNNER_TOKEN: ${{ inputs.RUNNER_TOKEN || secrets.GITEA_TOKEN || secrets.RUNNER_TOKEN }}
# 獲取最新的 release tag IS_BETA: ${{ inputs.IS_BETA }}
LATEST_TAG=$(curl -s "${{ inputs.gitea-server }}/api/v1/repos/${{ inputs.repository }}/releases" \
-H "Authorization: token ${{ inputs.token }}" \
-H "Accept: application/json" | \
jq -r 'if length > 0 then .[0].tag_name else "v0.0.0" end' | sed 's/^v//')
# 如果沒有找到版本或版本為空,使用初始版本前一版
if [ -z "$LATEST_TAG" ] || [ "$LATEST_TAG" = "null" ]; then
# 解析初始版本並減1來設定基準
IFS='.' read -r INIT_MAJOR INIT_MINOR INIT_PATCH <<< "${{ inputs.initial-version }}"
if [ $INIT_PATCH -gt 0 ]; then
LATEST_TAG="$INIT_MAJOR.$INIT_MINOR.$((INIT_PATCH - 1))"
elif [ $INIT_MINOR -gt 0 ]; then
LATEST_TAG="$INIT_MAJOR.$((INIT_MINOR - 1)).$((inputs.patch-limit - 1))"
elif [ $INIT_MAJOR -gt 0 ]; then
LATEST_TAG="$((INIT_MAJOR - 1)).$((inputs.minor-limit - 1)).$((inputs.patch-limit - 1))"
else
LATEST_TAG="0.0.0"
fi
fi
echo "上一版本: $LATEST_TAG"
echo "previous-version=$LATEST_TAG" >> $GITHUB_OUTPUT
# 分解版本號
IFS='.' read -r MAJOR MINOR PATCH <<< "$LATEST_TAG"
# 確保變數為數字
MAJOR=${MAJOR:-0}
MINOR=${MINOR:-0}
PATCH=${PATCH:-0}
# 增加 patch 版本
PATCH=$((PATCH + 1))
# 如果 patch >= 上限,重置為 0 並增加 minor
if [ $PATCH -ge ${{ inputs.patch-limit }} ]; then
PATCH=0
MINOR=$((MINOR + 1))
fi
# 如果 minor >= 上限,重置為 0 並增加 major
if [ $MINOR -ge ${{ inputs.minor-limit }} ]; then
MINOR=0
MAJOR=$((MAJOR + 1))
fi
NEW_VERSION="$MAJOR.$MINOR.$PATCH"
echo "新版本: $NEW_VERSION"
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
+210
View File
@@ -0,0 +1,210 @@
#!/bin/bash
set -euo pipefail
readonly LINE="=================================================="
readonly SUBLINE="--------------------------------------------------"
section() {
printf '\n%s\n%s\n%s\n' "$LINE" "$1" "$SUBLINE"
}
info() {
printf '[info] %s\n' "$1"
}
fail() {
printf '[error] %s\n' "$1" >&2
exit 1
}
require_env() {
local name="$1"
local value="$2"
if [ -z "$value" ] || [ "$value" = "null" ]; then
fail "$name 未設定"
fi
printf '%s=%s\n' "$name" "$value"
}
write_output() {
printf 'version=%s\n' "$1" >> "$GITHUB_OUTPUT"
}
normalize_beta_flag() {
local value="${1:-false}"
if [ -z "$value" ] || [ "$value" = "null" ]; then
printf '%s\n' "false"
return
fi
printf '%s\n' "$value"
}
latest_stable_version() {
local release_json="$1"
if [ -z "$release_json" ] || [ "$release_json" = "null" ]; then
printf '%s\n' "0.0.0"
return
fi
printf '%s' "$release_json" | jq -r '
if type == "array" then
[ .[] | select(.tag_name? and (.tag_name | test("-beta\\.") | not)) | .tag_name ][0]
// "v0.0.0"
else
"v0.0.0"
end
| sub("^v"; "")
'
}
next_release_version() {
local latest_version="$1"
local major minor patch
IFS='.' read -r major minor patch <<< "$latest_version"
major="${major:-0}"
minor="${minor:-0}"
patch="${patch:-0}"
patch=$((patch + 1))
if [ "$patch" -ge 10 ]; then
patch=0
minor=$((minor + 1))
fi
if [ "$minor" -ge 10 ]; then
minor=0
major=$((major + 1))
fi
printf '%s.%s.%s\n' "$major" "$minor" "$patch"
}
next_beta_number() {
local release_json="$1"
local version="$2"
if [ -z "$release_json" ] || [ "$release_json" = "null" ]; then
printf '%s\n' "1"
return
fi
printf '%s' "$release_json" | jq -r --arg prefix "v${version}-beta." '
if type == "array" then
[ .[]
| select(.tag_name? and (.tag_name | startswith($prefix)))
| .tag_name
| ltrimstr($prefix)
| try tonumber catch empty
] | if length > 0 then max else 0 end
else
0
end
' | awk '{print $1 + 1}'
}
calculate_version() {
local release_json="$1"
local is_beta="$2"
if [ -z "$release_json" ] || [ "$release_json" = "null" ]; then
if [ "$is_beta" = "true" ]; then
printf '%s\t%s\n' "0.0.0" "0.0.1-beta.1"
else
printf '%s\t%s\n' "0.0.0" "0.0.1"
fi
return
fi
printf '%s' "$release_json" | jq -r --arg is_beta "$is_beta" '
def stable_version:
if type == "array" then
[ .[] | select(.tag_name? and (.tag_name | test("-beta\\.") | not)) | .tag_name ][0]
// "v0.0.0"
else
"v0.0.0"
end
| sub("^v"; "");
def next_release($latest):
($latest | split(".") | map(tonumber? // 0)) as $parts
| ($parts[0] // 0) as $major
| ($parts[1] // 0) as $minor
| ($parts[2] // 0) as $patch
| ($patch + 1) as $next_patch
| if $next_patch >= 10 then
if ($minor + 1) >= 10 then
"\(($major + 1)).0.0"
else
"\($major).\($minor + 1).0"
end
else
"\($major).\($minor).\($next_patch)"
end;
def beta_max($prefix):
[ .[]
| select(.tag_name? and (.tag_name | startswith($prefix)))
| .tag_name
| ltrimstr($prefix)
| try tonumber catch empty
] | if length > 0 then max else 0 end;
(stable_version) as $base
| (next_release($base)) as $next
| if $is_beta == "true" then
$base + "\t" + ($next + "-beta." + ((beta_max("v" + $next + "-beta.") + 1) | tostring))
else
$base + "\t" + $next
end
'
}
main() {
section "參數檢查"
require_env "GITEA_SERVER_URL" "${GITEA_SERVER_URL:-}"
require_env "GITEA_REPOSITORY" "${GITEA_REPOSITORY:-}"
if [ -n "${RUNNER_TOKEN:-}" ] && [ "${RUNNER_TOKEN:-}" != "null" ]; then
info "RUNNER_TOKEN=***"
else
info "RUNNER_TOKEN=未提供"
fi
IS_BETA="$(normalize_beta_flag "${IS_BETA:-false}")"
info "IS_BETA=$IS_BETA"
section "取得版本資料"
RELEASE_URL="$GITEA_SERVER_URL/api/v1/repos/$GITEA_REPOSITORY/releases"
info "RELEASE_URL=$RELEASE_URL"
if [ -n "${RUNNER_TOKEN:-}" ] && [ "${RUNNER_TOKEN:-}" != "null" ]; then
info "使用授權 token 取得 release"
RELEASE_JSON="$(curl -fsS -H "Authorization: token $RUNNER_TOKEN" "$RELEASE_URL")"
else
info "使用匿名請求取得 release"
RELEASE_JSON="$(curl -fsS "$RELEASE_URL")"
fi
VERSION_INFO="$(calculate_version "$RELEASE_JSON" "$IS_BETA")"
IFS=$'\t' read -r LATEST_TAG NEW_VERSION <<< "$VERSION_INFO"
info "LATEST_VERSION=$LATEST_TAG"
section "計算版本號"
info "NEW_VERSION=$NEW_VERSION"
write_output "$NEW_VERSION"
}
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
main "$@"
fi
-97
View File
@@ -1,97 +0,0 @@
# Calculate Version Action
這是一個自動計算語義化版本號的 GitHub Action,支援自定義版本遞增規則。
## 功能特色
- 🔄 自動從 Gitea releases 獲取最新版本
- 📈 支援自定義版本遞增規則
- 🎯 可配置的版本號上限
- 🔧 完全可重用的 composite action
## 輸入參數
| 參數名稱 | 必要 | 預設值 | 描述 |
|---------|------|--------|------|
| `gitea-server` | ✅ | - | Gitea 伺服器 URL |
| `repository` | ✅ | - | 儲存庫名稱 (格式: owner/repo) |
| `token` | ✅ | - | Gitea API Token |
| `initial-version` | ❌ | `0.0.1` | 初始版本號 |
| `patch-limit` | ❌ | `10` | 小版號上限 (達到時進位中版號) |
| `minor-limit` | ❌ | `10` | 中版號上限 (達到時進位大版號) |
## 輸出參數
| 參數名稱 | 描述 |
|---------|------|
| `version` | 計算出的新版本號 |
| `previous-version` | 上一個版本號 |
## 使用範例
### 基本用法
```yaml
- name: 計算版本號
id: version
uses: ./.gitea/actions/calculate-version
with:
gitea-server: ${{ gitea.server_url }}
repository: ${{ gitea.repository }}
token: ${{ secrets.GITEA_TOKEN }}
```
### 自定義設定
```yaml
- name: 計算版本號
id: version
uses: ./.gitea/actions/calculate-version
with:
gitea-server: ${{ gitea.server_url }}
repository: ${{ gitea.repository }}
token: ${{ secrets.GITEA_TOKEN }}
initial-version: '1.0.0'
patch-limit: '5' # 小版號到 4 時進位
minor-limit: '5' # 中版號到 4 時進位
```
### 使用輸出
```yaml
- name: 顯示版本資訊
run: |
echo "上一版本: ${{ steps.version.outputs.previous-version }}"
echo "新版本: ${{ steps.version.outputs.version }}"
- name: 建立 Release
uses: akkuman/gitea-release-action@v1
with:
name: "My App v${{ steps.version.outputs.version }}"
tag_name: "v${{ steps.version.outputs.version }}"
```
## 版本遞增規則
1. **預設增加小版號 (patch)**:每次執行都會增加小版號
2. **小版號進位**:當小版號 ≥ `patch-limit` 時,重置為 0 並增加中版號
3. **中版號進位**:當中版號 ≥ `minor-limit` 時,重置為 0 並增加大版號
### 範例版本演進 (預設設定)
```
初始:0.0.1
第2次:0.0.2
...
第10次:0.0.9
第11次:0.1.0 ← 小版號重置,中版號進位
...
第101次:0.9.9
第102次:1.0.0 ← 中版號重置,大版號進位
```
## 注意事項
- Action 必須放在 `.gitea/actions/calculate-version/` 目錄下
- 需要有 `GITEA_TOKEN` secret 來存取 API
- 第一次執行時會從 `initial-version` 開始計算
+399
View File
@@ -0,0 +1,399 @@
#!/bin/bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
source "$ROOT_DIR/entrypoint.sh"
declare -a CLEANUP_PATHS=()
cleanup() {
if [ "${#CLEANUP_PATHS[@]}" -gt 0 ]; then
rm -rf "${CLEANUP_PATHS[@]}"
fi
}
trap cleanup EXIT
fail() {
printf '[error] %s\n' "$1" >&2
exit 1
}
assert_eq() {
local expected="$1"
local actual="$2"
local label="$3"
if [ "$expected" != "$actual" ]; then
fail "$label: expected '$expected', got '$actual'"
fi
}
make_mock_curl() {
local bin_dir="$1"
local response_file="$2"
cat >"$bin_dir/curl" <<'EOF'
#!/bin/sh
if [ -n "${FAKE_CURL_LOG_FILE:-}" ]; then
printf '%s\n' "$*" >> "$FAKE_CURL_LOG_FILE"
fi
if [ "${FAKE_CURL_STATUS:-0}" != "0" ]; then
exit "$FAKE_CURL_STATUS"
fi
cat "${FAKE_CURL_RESPONSE_FILE:?}"
EOF
chmod +x "$bin_dir/curl"
}
make_mock_jq() {
local bin_dir="$1"
cat >"$bin_dir/jq" <<'EOF'
#!/bin/sh
is_beta=""
query=""
while [ "$#" -gt 0 ]; do
case "$1" in
-r)
shift
;;
--arg)
if [ "$2" = "is_beta" ]; then
is_beta="$3"
fi
shift 3
;;
*)
query="$1"
shift
break
;;
esac
done
python3 -c 'import json, sys
query = sys.argv[1]
is_beta = sys.argv[2]
payload = sys.stdin.read()
try:
data = json.loads(payload)
except Exception:
sys.exit(4)
def next_version(latest):
parts = [int(p or 0) for p in latest.split(".")]
while len(parts) < 3:
parts.append(0)
major, minor, patch = parts[:3]
patch += 1
if patch >= 10:
patch = 0
minor += 1
if minor >= 10:
minor = 0
major += 1
return f"{major}.{minor}.{patch}"
def beta_max(data, prefix):
values = []
for item in data:
if not isinstance(item, dict):
continue
tag = item.get("tag_name")
if isinstance(tag, str) and tag.startswith(prefix):
suffix = tag[len(prefix):]
try:
values.append(int(suffix))
except Exception:
pass
return max(values) if values else 0
if not isinstance(data, list):
base = "0.0.0"
else:
base = "0.0.0"
for item in data:
if not isinstance(item, dict):
continue
tag = item.get("tag_name")
if isinstance(tag, str) and "-beta." not in tag:
base = tag[1:] if tag.startswith("v") else tag
break
next_ver = next_version(base)
if is_beta == "true":
beta = beta_max(data, f"v{next_ver}-beta.") + 1
sys.stdout.write(f"{base}\t{next_ver}-beta.{beta}")
else:
sys.stdout.write(f"{base}\t{next_ver}")
' "$query" "$is_beta"
EOF
chmod +x "$bin_dir/jq"
}
run_entrypoint() {
local response_file="$1"
local is_beta="$2"
local token="${3:-}"
local fake_status="${4:-0}"
local workdir
local output_file
local bin_dir
local stdout_file
local stderr_file
workdir="$(mktemp -d)"
CLEANUP_PATHS+=("$workdir")
bin_dir="$workdir/bin"
mkdir -p "$bin_dir"
make_mock_curl "$bin_dir" "$response_file"
make_mock_jq "$bin_dir"
output_file="$workdir/github_output"
stdout_file="$workdir/stdout"
stderr_file="$workdir/stderr"
if [ -n "$token" ]; then
FAKE_CURL_STATUS="$fake_status" \
FAKE_CURL_RESPONSE_FILE="$response_file" \
FAKE_CURL_LOG_FILE="$workdir/curl_args" \
PATH="$bin_dir:$PATH" \
GITEA_SERVER_URL="https://gitea.example.com" \
GITEA_REPOSITORY="org/repo" \
RUNNER_TOKEN="$token" \
IS_BETA="$is_beta" \
GITHUB_OUTPUT="$output_file" \
bash "$ROOT_DIR/entrypoint.sh" >"$stdout_file" 2>"$stderr_file"
else
FAKE_CURL_STATUS="$fake_status" \
FAKE_CURL_RESPONSE_FILE="$response_file" \
FAKE_CURL_LOG_FILE="$workdir/curl_args" \
PATH="$bin_dir:$PATH" \
GITEA_SERVER_URL="https://gitea.example.com" \
GITEA_REPOSITORY="org/repo" \
IS_BETA="$is_beta" \
GITHUB_OUTPUT="$output_file" \
bash "$ROOT_DIR/entrypoint.sh" >"$stdout_file" 2>"$stderr_file"
fi
printf '%s\n' "$output_file"
}
test_unit_helpers() {
assert_eq "false" "$(normalize_beta_flag "")" "normalize_beta_flag empty"
assert_eq "false" "$(normalize_beta_flag "null")" "normalize_beta_flag null"
assert_eq "true" "$(normalize_beta_flag "true")" "normalize_beta_flag true"
assert_eq "0.1.0" "$(next_release_version "0.0.9")" "next_release_version carry patch"
assert_eq "2.0.0" "$(next_release_version "1.9.9")" "next_release_version carry minor"
assert_eq "100.0.0" "$(next_release_version "99.9.9")" "next_release_version carry major"
}
test_stable_release_flow() {
local workdir
local response_file
local output_file
workdir="$(mktemp -d)"
CLEANUP_PATHS+=("$workdir")
response_file="$workdir/releases.json"
cat >"$response_file" <<'EOF'
[
{"tag_name":"v1.2.3-beta.1"},
{"tag_name":"v1.2.3"},
{"tag_name":"v1.2.4-beta.1"}
]
EOF
output_file="$(run_entrypoint "$response_file" "false")"
assert_eq "version=1.2.4" "$(cat "$output_file")" "stable release output"
}
test_beta_release_flow() {
local workdir
local response_file
local output_file
workdir="$(mktemp -d)"
CLEANUP_PATHS+=("$workdir")
response_file="$workdir/releases.json"
cat >"$response_file" <<'EOF'
[
{"tag_name":"v1.2.3"},
{"tag_name":"v1.2.4-beta.1"},
{"tag_name":"v1.2.4-beta.3"}
]
EOF
output_file="$(run_entrypoint "$response_file" "true")"
assert_eq "version=1.2.4-beta.4" "$(cat "$output_file")" "beta release output"
}
test_empty_release_list() {
local workdir
local response_file
local output_file
workdir="$(mktemp -d)"
CLEANUP_PATHS+=("$workdir")
response_file="$workdir/releases.json"
printf '%s\n' '[]' >"$response_file"
output_file="$(run_entrypoint "$response_file" "false")"
assert_eq "version=0.0.1" "$(cat "$output_file")" "empty release list"
}
test_only_beta_releases() {
local workdir
local response_file
local output_file
workdir="$(mktemp -d)"
CLEANUP_PATHS+=("$workdir")
response_file="$workdir/releases.json"
cat >"$response_file" <<'EOF'
[
{"tag_name":"v2.4.6-beta.1"},
{"tag_name":"v2.4.6-beta.2"}
]
EOF
output_file="$(run_entrypoint "$response_file" "false")"
assert_eq "version=0.0.1" "$(cat "$output_file")" "only beta releases"
}
test_null_release_payload() {
local workdir
local response_file
local output_file
workdir="$(mktemp -d)"
CLEANUP_PATHS+=("$workdir")
response_file="$workdir/releases.json"
printf '%s\n' 'null' >"$response_file"
output_file="$(run_entrypoint "$response_file" "false")"
assert_eq "version=0.0.1" "$(cat "$output_file")" "null release payload"
}
test_token_auth_header() {
local workdir
local response_file
local bin_dir
local output_file
local curl_args
local stdout_file
local stderr_file
workdir="$(mktemp -d)"
CLEANUP_PATHS+=("$workdir")
response_file="$workdir/releases.json"
printf '%s\n' '[]' >"$response_file"
bin_dir="$workdir/bin"
mkdir -p "$bin_dir"
make_mock_curl "$bin_dir" "$response_file"
make_mock_jq "$bin_dir"
output_file="$workdir/github_output"
stdout_file="$workdir/stdout"
stderr_file="$workdir/stderr"
FAKE_CURL_STATUS=0 \
FAKE_CURL_RESPONSE_FILE="$response_file" \
FAKE_CURL_LOG_FILE="$workdir/curl_args" \
PATH="$bin_dir:$PATH" \
GITEA_SERVER_URL="https://gitea.example.com" \
GITEA_REPOSITORY="org/repo" \
RUNNER_TOKEN="secret-token" \
IS_BETA="false" \
GITHUB_OUTPUT="$output_file" \
bash "$ROOT_DIR/entrypoint.sh" >"$stdout_file" 2>"$stderr_file"
curl_args="$workdir/curl_args"
if ! grep -q 'Authorization: token secret-token' "$curl_args"; then
fail "token auth header: missing Authorization header"
fi
assert_eq "version=0.0.1" "$(cat "$output_file")" "token auth output"
}
test_malformed_release_payload() {
local workdir
local response_file
local bin_dir
local output_file
local stdout_file
local stderr_file
workdir="$(mktemp -d)"
CLEANUP_PATHS+=("$workdir")
response_file="$workdir/releases.json"
printf '%s\n' '{' >"$response_file"
bin_dir="$workdir/bin"
mkdir -p "$bin_dir"
make_mock_curl "$bin_dir" "$response_file"
make_mock_jq "$bin_dir"
output_file="$workdir/github_output"
stdout_file="$workdir/stdout"
stderr_file="$workdir/stderr"
if FAKE_CURL_STATUS=0 \
FAKE_CURL_RESPONSE_FILE="$response_file" \
PATH="$bin_dir:$PATH" \
GITEA_SERVER_URL="https://gitea.example.com" \
GITEA_REPOSITORY="org/repo" \
IS_BETA="false" \
GITHUB_OUTPUT="$output_file" \
bash "$ROOT_DIR/entrypoint.sh" >"$stdout_file" 2>"$stderr_file"; then
fail "malformed release payload: expected failure"
fi
}
test_curl_failure() {
local workdir
local response_file
local bin_dir
local output_file
local stdout_file
local stderr_file
workdir="$(mktemp -d)"
CLEANUP_PATHS+=("$workdir")
response_file="$workdir/releases.json"
printf '%s\n' '[]' >"$response_file"
bin_dir="$workdir/bin"
mkdir -p "$bin_dir"
make_mock_curl "$bin_dir" "$response_file"
output_file="$workdir/github_output"
stdout_file="$workdir/stdout"
stderr_file="$workdir/stderr"
if FAKE_CURL_STATUS=22 \
FAKE_CURL_RESPONSE_FILE="$response_file" \
PATH="$bin_dir:$PATH" \
GITEA_SERVER_URL="https://gitea.example.com" \
GITEA_REPOSITORY="org/repo" \
IS_BETA="false" \
GITHUB_OUTPUT="$output_file" \
bash "$ROOT_DIR/entrypoint.sh" >"$stdout_file" 2>"$stderr_file"; then
fail "curl failure: expected failure"
fi
}
test_unit_helpers
test_stable_release_flow
test_beta_release_flow
test_empty_release_list
test_only_beta_releases
test_null_release_payload
test_token_auth_header
test_malformed_release_payload
test_curl_failure
printf '[info] all tests passed\n'