Compare commits

...
55 Commits
Author SHA1 Message Date
admin d7ea601b13 Merge pull request 'feat(清理成品): 刪除舊版本成品後清理未指定 release 的 tag' (#1) from develop into master
CD / 計算版本號 (push) Successful in 1s
CD / 發布專案 (push) Successful in 1s
Reviewed-on: actions/cleanup-release#1
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-06-23 08:20:31 +00:00
JefferyandClaude Opus 4.8 e567759c06 chore(AI 助理設定): 移除 AI 助理設定與指示檔
移除 CLAUDE.md、GEMINI.md 與 .claude/.codex/.gemini/.amazonq 及 Copilot 的設定與指示檔。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 16:18:41 +08:00
JefferyandClaude Opus 4.8 6cad836ce3 test(清理成品): 新增 tag 清理測試並抽出共用 jq mock
新增 entrypoint_tag_cleanup.sh 驗證孤兒 tag 刪除、保留指定 release 的 tag;分頁測試改為端點感知並共用 jq mock。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 16:14:56 +08:00
JefferyandClaude Opus 4.8 30f5dce8dc feat(清理成品): 新增刪除未指定 release 的 tag 步驟並抽出分頁共用函式
刪除舊版本成品後,重新取得 release 清單並刪除所有未指定到 release 的 tag。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 16:14:51 +08:00
jiantw83 c5fadfb99b Merge pull request '版本 0.4.2' (#17) from develop into master
Reviewed-on: actions/cleanup-release#17
Reviewed-by: 系統管理員 <admin@noreply.localhost>
2026-05-16 12:49:01 +00:00
jiantw83 c9c92c160c Merge pull request 'fix: clean up release workflows' (#16) from feat/ai_code_review into develop
Reviewed-on: actions/cleanup-release#16
2026-05-16 12:45:51 +00:00
jiantw83 d23910c487 chore: refresh triage exclusions 2026-05-16 12:43:42 +00:00
AI Review Bot c67a5676ec chore: update ai-review findings [ai-review-bot][success] 2026-05-16 12:40:07 +00:00
jiantw83 ec66233a2d chore: refresh triage exclusions 2026-05-16 12:39:01 +00:00
AI Review Bot d1cd0ba644 chore: update ai-review findings [ai-review-bot][success] 2026-05-16 12:37:43 +00:00
jiantw83 10ad862c83 chore: refresh triage exclusions 2026-05-16 12:36:22 +00:00
AI Review Bot 0f4ad188a8 chore: update ai-review findings [ai-review-bot][failure] 2026-05-16 12:34:06 +00:00
jiantw83 d1ee8a2b84 chore: triage review findings 2026-05-16 12:32:41 +00:00
AI Review Bot 20053504ef chore: update ai-review findings [ai-review-bot][failure] 2026-05-16 12:24:52 +00:00
jiantw83 869250588b refactor: tidy cleanup release logs 2026-05-16 12:18:56 +00:00
jiantw83 86fd1119a5 fix: clean up release workflows 2026-05-16 12:17:19 +00:00
jiantw83 0ea080d95e Merge pull request 'feat: update master.yaml job names for clarity and add review.yaml for AI code review' (#15) from develop into master
Reviewed-on: actions/cleanup-release#15
2026-05-13 03:20:42 +00:00
jiantw83 c3b1823603 feat: update master.yaml job names for clarity and add review.yaml for AI code review 2026-05-13 03:19:52 +00:00
jiantw83 a2b53a9f84 Merge pull request '更新 action.yml' (#14) from develop into master
Reviewed-on: actions/cleanup-release#14
2026-03-25 06:40:45 +00:00
jiantw83 e5f97f6ae2 更新 action.yml 2026-03-25 06:40:25 +00:00
jiantw83 982d30a0d5 Merge pull request '更新 Dockerfile' (#13) from develop into master
Reviewed-on: actions/cleanup-release#13
2026-03-25 05:43:47 +00:00
jiantw83 47acc7ce83 Merge pull request '更新 action.yml' (#12) from develop into master
Reviewed-on: actions/cleanup-release#12
2026-03-25 02:45:45 +00:00
jiantw83 3f717330cc Merge pull request 'feat: 將清理成品的行為合併到發布專案' (#11) from develop into master
Reviewed-on: actions/cleanup-release#11
2026-03-21 11:48:52 +00:00
jiantw83 be812d40f8 Merge pull request '更新 .gitea/workflows/master.yaml' (#10) from develop into master
Reviewed-on: actions/cleanup-release#10
2026-03-21 09:38:43 +00:00
jiantw83 08a5282891 Merge pull request 'feat: 修改工作流的名稱' (#9) from develop into master
Reviewed-on: actions/cleanup-release#9
Reviewed-by: 系統管理員 <admin@noreply.localhost>
2026-03-21 02:51:22 +00:00
admin 4c841fd937 Merge pull request 'feat: 修改工作流的名稱' (#8) from develop into master
Reviewed-on: actions/cleanup-release#8
2026-03-21 02:49:59 +00:00
jiantw83 dade5e1316 Merge pull request 'feat: 新增 input 參數,可以讓工具使用者自訂 token' (#7) from develop into master
Reviewed-on: actions/cleanup-release#7
2026-03-21 02:46:05 +00:00
jiantw83 61c9013fe0 Merge pull request 'feat: 重構清理舊版成品' (#6) from develop into master
Reviewed-on: actions/cleanup-release#6
2026-03-21 02:33:37 +00:00
jiantw83 7730f56c49 Merge pull request 'test: 嘗試修正 INPUT_GITEA_SERVER: unbound variable 問題' (#5) from develop into master
Reviewed-on: actions/cleanup-release#5
2026-03-20 06:26:24 +00:00
jiantw83 e706ad7c5a Merge pull request 'feat: 改成 docker action' (#4) from develop into master
Reviewed-on: actions/cleanup-release#4
2026-03-20 06:20:01 +00:00
jiantw83 ae8127d5c2 Merge pull request '更新 .gitea/workflows/master.yaml' (#3) from develop into master
Reviewed-on: actions/cleanup-release#3
2026-03-05 08:08:09 +00:00
jiantw83 9df8b8dcaa Merge pull request '更新 .gitea/workflows/master.yaml' (#2) from develop into master
Reviewed-on: actions/cleanup-release#2
2026-03-05 08:05:35 +00:00
jiantw83 4a6a3c159d Merge pull request '更新 .gitea/workflows/master.yaml' (#1) from develop into master
Reviewed-on: actions/cleanup-release#1
2026-03-05 08:02:41 +00:00
jiantw83 8cbe2e3e4d Merge pull request 'fix: 修正刪除 API 的參數' (#22) from develop into master
Reviewed-on: jiantw83/cleanup-release-action#22
2025-12-01 07:15:08 +00:00
jiantw83 075bc62138 Merge pull request 'feat: 刪除失敗,不要讓任務成功' (#21) from develop into master
Reviewed-on: jiantw83/cleanup-release-action#21
2025-12-01 07:13:12 +00:00
jiantw83 4a2c9d6064 Merge pull request 'feat: 移除多餘的變數' (#20) from develop into master
Reviewed-on: jiantw83/cleanup-release-action#20
2025-12-01 06:53:34 +00:00
jiantw83 17721ae9d0 Merge pull request 'feat: 優化程序' (#19) from develop into master
Reviewed-on: jiantw83/cleanup-release-action#19
2025-12-01 06:35:58 +00:00
jiantw83 9929c05469 Merge pull request 'feat: 修正刪除的 API' (#18) from develop into master
Reviewed-on: jiantw83/cleanup-release-action#18
2025-12-01 06:12:01 +00:00
jiantw83 a078f2d984 Merge pull request 'fix: 修正 get 的 api 解析' (#17) from develop into master
Reviewed-on: jiantw83/cleanup-release-action#17
2025-12-01 06:03:47 +00:00
jiantw83 9068f5e5b6 Merge pull request 'Revert "feat: 修改指令執行格式"' (#16) from develop into master
Reviewed-on: jiantw83/cleanup-release-action#16
2025-12-01 05:38:45 +00:00
jiantw83 e40a7adad6 Merge pull request 'feat: 修改指令執行格式' (#15) from develop into master
Reviewed-on: jiantw83/cleanup-release-action#15
2025-12-01 04:00:18 +00:00
jiantw83 214ec5f7bd Merge pull request 'feat: 修正 if 語法' (#14) from develop into master
Reviewed-on: jiantw83/cleanup-release-action#14
2025-12-01 03:58:53 +00:00
jiantw83 829c119657 Merge pull request 'feat: 改為 script 執行' (#13) from develop into master
Reviewed-on: jiantw83/cleanup-release-action#13
2025-12-01 03:57:05 +00:00
jiantw83 2ac7efe907 Merge pull request 'feat: 改為 script 執行' (#12) from develop into master
Reviewed-on: jiantw83/cleanup-release-action#12
2025-12-01 03:43:23 +00:00
jiantw83 74e3e758c9 Merge pull request 'fix: 修正 jq 指令' (#11) from develop into master
Reviewed-on: jiantw83/cleanup-release-action#11
2025-12-01 03:39:07 +00:00
jiantw83 3e32b71abe Merge pull request 'fix: 修正 jq 指令' (#10) from develop into master
Reviewed-on: jiantw83/cleanup-release-action#10
2025-12-01 03:37:45 +00:00
jiantw83 a8eaa2a293 Merge pull request 'fix: 調整指令參數' (#9) from develop into master
Reviewed-on: jiantw83/cleanup-release-action#9
2025-12-01 03:15:11 +00:00
jiantw83 f6925cc5e7 Merge pull request 'fix: 排除實際刪除數量為空的問題' (#8) from develop into master
Reviewed-on: jiantw83/cleanup-release-action#8
2025-12-01 03:04:22 +00:00
jiantw83 2daf39673a Merge pull request 'feat: 如果沒有 package-name 套過任務' (#7) from develop into master
Reviewed-on: jiantw83/cleanup-release-action#7
2025-12-01 03:02:30 +00:00
jiantw83 f3afba6e49 Merge pull request 'fix: 移除參數輸出' (#6) from develop into master
Reviewed-on: jiantw83/cleanup-release-action#6
2025-12-01 02:56:52 +00:00
jiantw83 c5b69ac380 Merge pull request 'feat: 移除 if 判斷' (#5) from develop into master
Reviewed-on: jiantw83/cleanup-release-action#5
2025-12-01 02:45:21 +00:00
jiantw83 963fa24ce0 Merge pull request 'fix: 修正錯誤' (#4) from develop into master
Reviewed-on: jiantw83/cleanup-release-action#4
2025-12-01 02:37:18 +00:00
jiantw83 93402e99ee Merge pull request 'fix: 修正錯誤' (#3) from develop into master
Reviewed-on: jiantw83/cleanup-release-action#3
2025-12-01 02:26:32 +00:00
jiantw83 ac72d18016 Merge pull request 'fix: 修正錯誤' (#2) from develop into master
Reviewed-on: jiantw83/cleanup-release-action#2
2025-12-01 02:21:20 +00:00
jiantw83 70cac9e740 Merge pull request 'feat: 清理舊映像' (#1) from develop into master
Reviewed-on: jiantw83/cleanup-release-action#1
2025-12-01 02:06:52 +00:00
10 changed files with 643 additions and 61 deletions
+177
View File
@@ -0,0 +1,177 @@
[
{
"level": "critical",
"role": "Rex",
"location": "Dockerfile:4",
"suggestion": "移除 `apk add` 命令中的 `--no-check-certificate` 旗標。禁用憑證檢查會使 Docker 映像檔的建置過程容易受到中間人攻擊,導致惡意套件注入。請確保套件來源的信任鏈完整性。",
"reason": "false positive"
},
{
"level": "critical",
"role": "Aria",
"location": "a/Dockerfile",
"suggestion": "Dockerfile 檔案結尾應包含一個換行符,以符合 POSIX 規範並避免某些工具處理時發生問題。",
"reason": "false positive"
},
{
"level": "critical",
"role": "Aria",
"location": "a/entrypoint.sh",
"suggestion": "Shell 腳本檔案結尾應包含一個換行符,以符合 POSIX 規範並避免某些工具處理時發生問題。",
"reason": "false positive"
},
{
"level": "critical",
"role": "Maya",
"location": "entrypoint.sh",
"suggestion": "此腳本缺少單元測試。建議引入一個 shell 腳本測試框架(例如 `bats` 或 `shunit2`),並為 `is_empty_or_null`、`require_value`、`require_integer` 等輔助函數以及主要邏輯流程編寫單元測試,以確保其行為符合預期。",
"reason": "false positive"
},
{
"level": "critical",
"role": "Maya",
"location": "entrypoint.sh",
"suggestion": "此腳本與外部 Gitea API 互動,但缺少整合測試。建議建立整合測試,使用模擬 Gitea 伺服器(或測試環境)來驗證腳本的端到端流程,包括成功刪除、錯誤處理(例如無效的 RUNNER_TOKEN、API 錯誤響應)以及邊界條件。",
"reason": "false positive"
},
{
"level": "warning",
"role": "Rex",
"location": "entrypoint.sh:100",
"suggestion": "環境變數 `GITEA_SERVER_URL` 被直接用於 `curl` 命令中,以構建 API 請求。如果此變數可被攻擊者控制,可能導致伺服器端請求偽造 (SSRF) 漏洞,使程式向任意內部或外部主機發送請求。建議對 `GITEA_SERVER_URL` 進行嚴格的驗證,確保其指向預期且受信任的 Gitea 實例,例如使用白名單限制允許的網域或 IP 範圍。",
"reason": "false positive"
},
{
"level": "warning",
"role": "Aria",
"location": "a/Dockerfile:6",
"suggestion": "`COPY` 指令的縮排不一致,建議保持統一的縮排風格(例如,與 `RUN` 指令對齊),以提高程式碼可讀性。",
"reason": "false positive"
},
{
"level": "warning",
"role": "Aria",
"location": "a/entrypoint.sh",
"suggestion": "腳本中存在不一致的縮排風格,建議統一使用 2 或 4 個空格進行縮排,以提高程式碼可讀性。",
"reason": "false positive"
},
{
"level": "warning",
"role": "Aria",
"location": "a/entrypoint.sh:4-20",
"suggestion": "參數檢查和錯誤處理邏輯重複且分散,建議將常見的驗證和日誌輸出邏輯抽象為輔助函數,以提高程式碼的模組化和可維護性。",
"reason": "false positive"
},
{
"level": "warning",
"role": "Aria",
"location": "a/entrypoint.sh",
"suggestion": "腳本中所有變數都使用大寫命名,這可能導致環境變數與腳本內部變數難以區分。建議對腳本內部使用的變數採用小寫加底線 (snake_case) 命名,而環境變數則保持大寫,以增強命名語義的清晰度。",
"reason": "false positive"
},
{
"level": "warning",
"role": "Maya",
"location": "entrypoint.sh:75",
"suggestion": "在取得成品資訊的 `curl` 請求中,雖然使用了 `-fsS`,但並未明確檢查 HTTP 狀態碼。如果 Gitea API 返回 401/403 等認證或權限錯誤,腳本可能會因為 `jq` 處理空或錯誤 JSON 而失敗,但錯誤訊息不夠明確。建議在 `curl` 請求後檢查 HTTP 狀態碼,特別是對於認證相關的錯誤,提供更清晰的錯誤提示。",
"reason": "false positive"
},
{
"level": "warning",
"role": "Maya",
"location": "entrypoint.sh:58",
"suggestion": "對於 `KEEP_COUNT` 參數,雖然 `require_integer` 確保了非負整數,但建議在整合測試中特別包含 `KEEP_COUNT=0`(應刪除所有成品)和 `KEEP_COUNT=1`(應保留最新一個成品)的測試案例,以確保這些邊界條件下的刪除邏輯正確無誤。",
"reason": "false positive"
},
{
"level": "warning",
"role": "Maya",
"location": "entrypoint.sh:75, 76, 100",
"suggestion": "腳本中有多處 `curl` 和 `jq` 的調用,雖然 `set -Eeuo pipefail` 有助於錯誤處理,但對於網路瞬時錯誤或 API 服務不穩定,腳本會直接退出。建議考慮為 `curl` 請求添加重試機制,並為 `curl` 和 `jq` 的失敗提供更具體的錯誤處理邏輯,例如捕獲錯誤並輸出詳細訊息,而不是僅依賴 `pipefail`。",
"reason": "false positive"
},
{
"level": "info",
"role": "Leo",
"location": "entrypoint.sh",
"suggestion": "雖然程式碼的可讀性已大幅提升,但考慮為新引入的輔助函式(如 `separator`, `section`, `info`, `success`, `warn`, `fail`, `is_empty_or_null`, `require_value`, `require_integer`)添加簡要的註解,說明其用途,這將有助於新開發者快速理解。",
"reason": "false positive"
},
{
"level": "info",
"role": "Rex",
"location": "entrypoint.sh:116",
"suggestion": "從 JSON 回應中提取的值(如 `release_id`、`release_tag`、`release_name`)被用於構建 URL 和日誌訊息。儘管 `jq` 和 shell 引用提供了一定保護,但如果 JSON 回應本身不可信或被惡意篡改,這些值仍可能被用於注入惡意資料。建議對這些提取的值進行額外的驗證或淨化(例如,確保 `release_id` 是整數,`release_tag` 和 `release_name` 符合預期模式),尤其是在將它們用於關鍵操作或日誌記錄之前。",
"reason": "false positive"
},
{
"level": "info",
"role": "Maya",
"location": "entrypoint.sh:54",
"suggestion": "對於 `GITEA_SERVER_URL` 參數,除了檢查是否為空或 'null',建議增加基本的 URL 格式驗證,以確保其為有效的 URL 格式,避免 `curl` 在無效 URL 時產生非預期的行為。",
"reason": "false positive"
},
{
"level": "info",
"role": "Maya",
"location": "entrypoint.sh:55",
"suggestion": "對於 `GITEA_REPOSITORY` 參數,建議增加格式驗證,確保其符合 `owner/repo` 的預期格式,避免因格式錯誤導致 API 請求失敗。",
"reason": "false positive"
},
{
"level": "warning",
"role": "Zara",
"location": "entrypoint.sh:67",
"suggestion": "儘管已引入分頁機制,但腳本仍將所有分頁的發布資訊(`release_json`)聚合到記憶體中。對於擁有極大量發布(例如數萬個)的儲存庫,這可能導致記憶體使用量過高,進而影響效能或導致記憶體不足。建議考慮在處理大量資料時,避免將所有資料一次性載入記憶體,例如只提取必要的欄位(如ID和時間戳)進行排序和篩選,或在可能的情況下,利用API的伺服器端排序和過濾功能來減少客戶端處理的負擔。",
"reason": "false positive"
},
{
"level": "warning",
"role": "Aria",
"location": "Dockerfile",
"suggestion": "檔案結尾應包含一個空行 (newline),以符合 POSIX 規範並避免某些工具處理時發生問題。",
"reason": "false positive"
},
{
"level": "warning",
"role": "Aria",
"location": "entrypoint.sh",
"suggestion": "檔案結尾應包含一個空行 (newline),以符合 POSIX 規範並避免某些工具處理時發生問題。",
"reason": "false positive"
},
{
"level": "warning",
"role": "Aria",
"location": "tests/entrypoint_pagination.sh",
"suggestion": "檔案結尾應包含一個空行 (newline),以符合 POSIX 規範並避免某些工具處理時發生問題。",
"reason": "false positive"
},
{
"level": "info",
"role": "Leo",
"location": "tests/entrypoint_pagination.sh",
"suggestion": "測試中 `jq` 的模擬實作(`jq.py`)雖然有效,但它重新實作了 `jq` 的部分功能。如果 `entrypoint.sh` 中 `jq` 的使用方式變得更複雜,這個模擬可能需要頻繁更新或變得脆弱。未來可以考慮使用更通用的方式來模擬 `jq` 的行為,例如透過環境變數或檔案輸入/輸出來控制其行為,而不是重新實作其邏輯。",
"reason": "false positive"
},
{
"level": "info",
"role": "Zara",
"location": "entrypoint.sh",
"suggestion": "腳本中頻繁呼叫 `jq` 命令(例如在循環中或多次處理同一變數時),每次呼叫都會啟動一個新的 `jq` 處理程序,這會產生一定的處理程序啟動開銷。雖然對於一般情況影響不大,但在處理大量資料或需要極致效能時,可以考慮減少 `jq` 的呼叫次數,例如將多個 `jq` 操作合併為一個更複雜的表達式,或使用其他語言(如Python)來處理JSON,以減少處理程序間的上下文切換成本。",
"reason": "false positive"
},
{
"level": "info",
"role": "Maya",
"location": "tests/entrypoint_pagination.sh:137",
"suggestion": "腳本執行時將標準輸出重定向到 `/dev/null`。為了更全面地驗證錯誤處理邏輯,應捕獲標準錯誤輸出 (stderr) 並檢查 `fail` 函數輸出的錯誤訊息是否符合預期,同時驗證腳本在錯誤情況下是否以非零狀態碼退出。",
"reason": "false positive"
},
{
"level": "info",
"role": "Maya",
"location": "Dockerfile",
"suggestion": "考慮新增一個整合測試,該測試會建置 Docker 映像檔,並在容器內部執行 `entrypoint.sh`。這將有助於驗證所有必要的依賴項(如 bash, curl, jq)是否已正確安裝,並且腳本在目標容器環境中能正常執行,確保部署的穩定性。",
"reason": "false positive"
}
]
+1
View File
@@ -0,0 +1 @@
[]
+4 -3
View File
@@ -1,10 +1,11 @@
name: CD
on: on:
push: push:
branches: branches:
- master - master
jobs: jobs:
version: version:
name: "CD > 計算版本號" name: 計算版本號
runs-on: ubuntu runs-on: ubuntu
outputs: outputs:
version: ${{ steps.version.outputs.version }} version: ${{ steps.version.outputs.version }}
@@ -13,14 +14,14 @@ jobs:
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 }}
release: release:
name: "CD > 發布專案" name: 發布專案
runs-on: ubuntu runs-on: ubuntu
needs: version needs: version
steps: 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${{ needs.version.outputs.version }}" tag_name: v${{ needs.version.outputs.version }}
- name: 清理成品 - 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:
+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
+2 -4
View File
@@ -1,10 +1,8 @@
FROM alpine:latest FROM alpine:latest
# 安裝必要的工具
RUN apk add --no-cache --no-check-certificate bash curl jq RUN apk add --no-cache --no-check-certificate bash curl jq
COPY entrypoint.sh /entrypoint.sh
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"] ENTRYPOINT ["/entrypoint.sh"]
+1 -1
View File
@@ -13,5 +13,5 @@ runs:
env: env:
GITEA_SERVER_URL: ${{ gitea.server_url }} GITEA_SERVER_URL: ${{ gitea.server_url }}
GITEA_REPOSITORY: ${{ gitea.repository }} GITEA_REPOSITORY: ${{ gitea.repository }}
RUNNER_TOKEN: ${{ inputs.RUNNER_TOKEN || ${{ secrets.GITEA_TOKEN }} || secrets.RUNNER_TOKEN }} RUNNER_TOKEN: ${{ inputs.RUNNER_TOKEN || secrets.GITEA_TOKEN || secrets.RUNNER_TOKEN }}
KEEP_COUNT: ${{ inputs.KEEP_COUNT }} KEEP_COUNT: ${{ inputs.KEEP_COUNT }}
+149 -53
View File
@@ -1,81 +1,177 @@
#!/bin/bash #!/usr/bin/env bash
set -Eeuo pipefail
echo "==================================================" separator() {
printf '\n%s\n' '=================================================='
}
echo "參數檢查" section() {
separator
printf '%s\n' "$1"
printf '%s\n' '--------------------------------------------------'
}
echo "--------------------------------------------------" info() {
printf '[INFO] %s\n' "$1"
}
# 顯示 GITEA_SERVER_URL 參數,並檢查是否為空或 "null",如果是則輸出錯誤訊息並退出 success() {
echo "GITEA_SERVER_URL=$GITEA_SERVER_URL" && ([ -z "$GITEA_SERVER_URL" ] || [ "$GITEA_SERVER_URL" = "null" ]) && exit 1 printf '[OK] %s\n' "$1"
}
# 顯示 GITEA_REPOSITORY 參數,並檢查是否為空或 "null",如果是則輸出錯誤訊息並退出 warn() {
echo "GITEA_REPOSITORY=$GITEA_REPOSITORY" && ([ -z "$GITEA_REPOSITORY" ] || [ "$GITEA_REPOSITORY" = "null" ]) && exit 1 printf '[WARN] %s\n' "$1"
}
# 顯示 RUNNER_TOKEN 參數 fail() {
echo "RUNNER_TOKEN=$RUNNER_TOKEN" printf '[ERR] %s\n' "$1" >&2
}
# 顯示 KEEP_COUNT 參數,並檢查是否為空或 "null",如果是則輸出錯誤訊息並退出 is_empty_or_null() {
echo "KEEP_COUNT=$KEEP_COUNT" && ([ -z "$KEEP_COUNT" ] || [ "$KEEP_COUNT" = "null" ]) && exit 1 [ -z "${1:-}" ] || [ "${1:-}" = "null" ]
}
echo "==================================================" require_value() {
local name="$1"
local value="$2"
echo "取得成品資訊" info "$name=$value"
if is_empty_or_null "$value"; then
fail "$name is required"
exit 1
fi
}
echo "--------------------------------------------------" require_integer() {
local name="$1"
local value="$2"
RELEASE_URL="$GITEA_SERVER_URL/api/v1/repos/$GITEA_REPOSITORY/releases" if ! [[ "$value" =~ ^[0-9]+$ ]]; then
fail "$name must be a non-negative integer"
exit 1
fi
}
# 檢查是否為空或 "null" fetch_all_pages() {
if [ -n "$RUNNER_TOKEN" ] && [ "$RUNNER_TOKEN" != "null" ]; then local base_url="$1"
# 組合 RELEASE_HEADER 參數,並顯示出來 local all_json='[]'
RELEASE_HEADER="Authorization: token $RUNNER_TOKEN" && echo "RELEASE_HEADER=$RELEASE_HEADER" local page=1
local page_url page_json
# 取得成品資訊 while :; do
RELEASE_JSON="$(curl -s -H "$RELEASE_HEADER" "$RELEASE_URL")" page_url="$base_url?page=$page"
page_json="$(curl -fsS "${auth_header[@]}" "$page_url")"
if [ "$(jq 'length' <<<"$page_json")" -eq 0 ]; then
break
fi
all_json="$(jq -s 'add' <<<"$all_json"$'\n'"$page_json")"
page=$((page + 1))
done
printf '%s' "$all_json"
}
section "參數檢查"
require_value "GITEA_SERVER_URL" "$GITEA_SERVER_URL"
require_value "GITEA_REPOSITORY" "$GITEA_REPOSITORY"
require_value "KEEP_COUNT" "$KEEP_COUNT"
require_integer "KEEP_COUNT" "$KEEP_COUNT"
if is_empty_or_null "${RUNNER_TOKEN:-}"; then
warn "RUNNER_TOKEN is empty; release API calls will be anonymous"
else else
# 取得成品資訊 info "RUNNER_TOKEN=[redacted]"
RELEASE_JSON="$(curl -s "$RELEASE_URL")"
fi fi
# 計算成品數量並依照時間排序 release_api_url="$GITEA_SERVER_URL/api/v1/repos/$GITEA_REPOSITORY/releases"
RELEASE_JSON=$(echo "$RELEASE_JSON" | jq -e 'sort_by(.created_at) | reverse') auth_header=()
RELEASE_COUNT=$(echo "$RELEASE_JSON" | jq 'length') && echo "RELEASE_COUNT=$RELEASE_COUNT"
# 檢查成品數量是否需要清除 if ! is_empty_or_null "${RUNNER_TOKEN:-}"; then
[ $RELEASE_COUNT -le $KEEP_COUNT ] && exit 0 auth_header=(-H "Authorization: token $RUNNER_TOKEN")
fi
echo "==================================================" section "取得成品資訊"
info "GET $release_api_url"
echo "刪除舊版本的成品" release_json="$(fetch_all_pages "$release_api_url")"
release_json="$(jq -e 'sort_by(.created_at) | reverse' <<<"$release_json")"
release_count="$(jq 'length' <<<"$release_json")"
echo "--------------------------------------------------" info "RELEASE_COUNT=$release_count"
info "KEEP_COUNT=$KEEP_COUNT"
RELEASE_TO_DELETE=$(echo "$RELEASE_JSON" | jq -c ".[$KEEP_COUNT:]") if [ "$release_count" -le "$KEEP_COUNT" ]; then
success "沒有需要清理的舊版本成品"
else
section "刪除舊版本成品"
echo "$RELEASE_TO_DELETE" | jq -c '.[]' | while IFS= read -r RELEASE_URL; do release_to_delete="$(jq -c ".[$KEEP_COUNT:]" <<<"$release_json")"
# 檢查是否有成品連結
([ -z "$RELEASE_URL" ] || [ "$RELEASE_URL" == "null" ]) && continue
# 取得成品資訊
RELEASE_ID=$(echo "$RELEASE_URL" | jq -r '.id')
RELEASE_TAG=$(echo "$RELEASE_URL" | jq -r '.tag_name')
RELEASE_NAME=$(echo "$RELEASE_URL" | jq -r '.name')
# 檢查成品編號是否正確 while IFS= read -r release_item; do
([ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" == "null" ]) && continue [ -z "$release_item" ] && continue
# 刪除成品 release_id="$(jq -r '.id' <<<"$release_item")"
RELEASE_URL="$GITEA_SERVER_URL/api/v1/repos/$GITEA_REPOSITORY/releases/$RELEASE_ID" release_tag="$(jq -r '.tag_name' <<<"$release_item")"
release_name="$(jq -r '.name' <<<"$release_item")"
RELEASE_DELETE_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE -H "$RELEASE_HEADER" "$RELEASE_URL") if is_empty_or_null "$release_id"; then
warn "略過沒有 id 的成品: $release_tag ($release_name)"
continue
fi
# 檢查刪除成品是否成功 delete_url="$GITEA_SERVER_URL/api/v1/repos/$GITEA_REPOSITORY/releases/$release_id"
if [ "$RELEASE_DELETE_CODE" -eq 204 ]; then info "DELETE $release_tag ($release_name)"
echo "✅ 成功刪除成品: $RELEASE_TAG ($RELEASE_NAME)"
else delete_code="$(curl -sS -o /dev/null -w "%{http_code}" -X DELETE "${auth_header[@]}" "$delete_url")"
echo "❌ 刪除成品失敗: $RELEASE_TAG ($RELEASE_NAME), HTTP 狀態碼: $RELEASE_DELETE_CODE"
if [ "$delete_code" -eq 204 ]; then
success "成功刪除: $release_tag ($release_name)"
else
fail "刪除失敗: $release_tag ($release_name), HTTP $delete_code"
fi
done < <(jq -c '.[]' <<<"$release_to_delete")
fi
section "刪除未指定 release 的 tag"
# 重新取得 release 清單,得到刪除舊版本後仍指定 tag 的成品
current_release_json="$(fetch_all_pages "$release_api_url")"
release_tags_json="$(jq -c '[.[].tag_name]' <<<"$current_release_json")"
tag_api_url="$GITEA_SERVER_URL/api/v1/repos/$GITEA_REPOSITORY/tags"
info "GET $tag_api_url"
tag_json="$(fetch_all_pages "$tag_api_url")"
tag_count="$(jq 'length' <<<"$tag_json")"
info "TAG_COUNT=$tag_count"
while IFS= read -r tag_item; do
[ -z "$tag_item" ] && continue
tag_name="$(jq -r '.name' <<<"$tag_item")"
if is_empty_or_null "$tag_name"; then
warn "略過沒有名稱的 tag"
continue
fi fi
done
echo "==================================================" if [ "$(jq --arg name "$tag_name" 'any(.[]; . == $name)' <<<"$release_tags_json")" = "true" ]; then
info "保留指定 release 的 tag: $tag_name"
continue
fi
delete_url="$tag_api_url/$tag_name"
info "DELETE tag $tag_name"
delete_code="$(curl -sS -o /dev/null -w "%{http_code}" -X DELETE "${auth_header[@]}" "$delete_url")"
if [ "$delete_code" -eq 204 ]; then
success "成功刪除未指定 release 的 tag: $tag_name"
else
fail "刪除 tag 失敗: $tag_name, HTTP $delete_code"
fi
done < <(jq -c '.[]' <<<"$tag_json")
separator
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env bash
set -Eeuo pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd "$script_dir/.." && pwd)"
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
curl_log="$tmpdir/curl.log"
bin_dir="$tmpdir/bin"
mkdir -p "$bin_dir"
cat >"$bin_dir/curl" <<'EOF'
#!/usr/bin/env bash
set -Eeuo pipefail
log_file="${CURL_LOG:?}"
printf '%s\n' "$*" >>"$log_file"
last_arg="${!#}"
if [[ " $* " == *" -X DELETE "* ]]; then
printf '204'
exit 0
fi
# tags 端點沒有需要清理的 tag
if [[ "$last_arg" == */tags?* ]]; then
printf '%s' '[]'
exit 0
fi
case "$last_arg" in
*/releases?*page=1)
printf '%s' '[{"id":4,"tag_name":"v4","name":"release-4","created_at":"2024-04-01T00:00:00Z"},{"id":3,"tag_name":"v3","name":"release-3","created_at":"2024-03-01T00:00:00Z"}]'
;;
*/releases?*page=2)
printf '%s' '[{"id":2,"tag_name":"v2","name":"release-2","created_at":"2024-02-01T00:00:00Z"},{"id":1,"tag_name":"v1","name":"release-1","created_at":"2024-01-01T00:00:00Z"}]'
;;
*/releases?*page=3)
printf '%s' '[]'
;;
*)
printf 'unexpected request: %s\n' "$last_arg" >&2
exit 1
;;
esac
EOF
chmod +x "$bin_dir/curl"
cp "$script_dir/lib/jq_mock.py" "$bin_dir/jq.py"
chmod +x "$bin_dir/jq.py"
cat >"$bin_dir/jq" <<'EOF'
#!/usr/bin/env bash
set -Eeuo pipefail
exec python3 "$0.py" "$@"
EOF
chmod +x "$bin_dir/jq"
PATH="$bin_dir:$PATH" \
CURL_LOG="$curl_log" \
GITEA_SERVER_URL="https://gitea.example.test" \
GITEA_REPOSITORY="owner/repo" \
KEEP_COUNT="2" \
RUNNER_TOKEN="" \
bash "$repo_root/entrypoint.sh" >/dev/null
grep -q 'page=1' "$curl_log"
grep -q 'page=2' "$curl_log"
grep -q 'page=3' "$curl_log"
grep -q '/releases/2' "$curl_log"
grep -q '/releases/1' "$curl_log"
if grep -q '/releases/3' "$curl_log" || grep -q '/releases/4' "$curl_log"; then
printf 'unexpected release deletion request in %s\n' "$curl_log" >&2
exit 1
fi
if grep -q '/tags/' "$curl_log"; then
printf 'unexpected tag deletion request in %s\n' "$curl_log" >&2
exit 1
fi
+104
View File
@@ -0,0 +1,104 @@
#!/usr/bin/env bash
set -Eeuo pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd "$script_dir/.." && pwd)"
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
curl_log="$tmpdir/curl.log"
bin_dir="$tmpdir/bin"
mkdir -p "$bin_dir"
cat >"$bin_dir/curl" <<'EOF'
#!/usr/bin/env bash
set -Eeuo pipefail
log_file="${CURL_LOG:?}"
printf '%s\n' "$*" >>"$log_file"
last_arg="${!#}"
if [[ " $* " == *" -X DELETE "* ]]; then
printf '204'
exit 0
fi
# 倉庫所有 tagv1~v4 加上兩個未指定 release 的孤兒 tag
if [[ "$last_arg" == */tags?* ]]; then
case "$last_arg" in
*page=1)
printf '%s' '[{"name":"v4"},{"name":"v3"},{"name":"v2"},{"name":"v1"},{"name":"tmp-build"},{"name":"v0"}]'
;;
*)
printf '%s' '[]'
;;
esac
exit 0
fi
# /releases GET:刪除舊版本後重新取得時,只回傳仍保留的 release
if grep -Eq -- '-X DELETE .*/releases/[0-9]' "$log_file"; then
case "$last_arg" in
*page=1)
printf '%s' '[{"id":4,"tag_name":"v4","name":"release-4","created_at":"2024-04-01T00:00:00Z"},{"id":3,"tag_name":"v3","name":"release-3","created_at":"2024-03-01T00:00:00Z"}]'
;;
*)
printf '%s' '[]'
;;
esac
exit 0
fi
case "$last_arg" in
*page=1)
printf '%s' '[{"id":4,"tag_name":"v4","name":"release-4","created_at":"2024-04-01T00:00:00Z"},{"id":3,"tag_name":"v3","name":"release-3","created_at":"2024-03-01T00:00:00Z"}]'
;;
*page=2)
printf '%s' '[{"id":2,"tag_name":"v2","name":"release-2","created_at":"2024-02-01T00:00:00Z"},{"id":1,"tag_name":"v1","name":"release-1","created_at":"2024-01-01T00:00:00Z"}]'
;;
*)
printf '%s' '[]'
;;
esac
EOF
chmod +x "$bin_dir/curl"
cp "$script_dir/lib/jq_mock.py" "$bin_dir/jq.py"
chmod +x "$bin_dir/jq.py"
cat >"$bin_dir/jq" <<'EOF'
#!/usr/bin/env bash
set -Eeuo pipefail
exec python3 "$0.py" "$@"
EOF
chmod +x "$bin_dir/jq"
PATH="$bin_dir:$PATH" \
CURL_LOG="$curl_log" \
GITEA_SERVER_URL="https://gitea.example.test" \
GITEA_REPOSITORY="owner/repo" \
KEEP_COUNT="2" \
RUNNER_TOKEN="" \
bash "$repo_root/entrypoint.sh" >/dev/null
# 舊版本 release 仍被刪除
grep -q '/releases/2' "$curl_log"
grep -q '/releases/1' "$curl_log"
# 未指定 release 的 tag(含被刪除 release 留下的 tag 與其他孤兒 tag)被刪除
for tag in v2 v1 tmp-build v0; do
if ! grep -q "/tags/$tag" "$curl_log"; then
printf 'expected tag deletion /tags/%s not found in %s\n' "$tag" "$curl_log" >&2
exit 1
fi
done
# 仍指定 release 的 tag 不可被刪除
for tag in v4 v3; do
if grep -q "/tags/$tag" "$curl_log"; then
printf 'unexpected deletion of release-assigned tag /tags/%s in %s\n' "$tag" "$curl_log" >&2
exit 1
fi
done
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""Minimal jq stand-in for entrypoint.sh tests.
Only the jq expressions used by entrypoint.sh are implemented. Unknown
expressions fail loudly so new jq usage is noticed instead of silently
producing wrong results.
"""
import json
import re
import sys
expr = ""
flags = set()
named_args = {}
args = sys.argv[1:]
while args:
current = args.pop(0)
if current in {"-e", "-c", "-r", "-s"}:
flags.add(current)
continue
if current == "--arg":
key = args.pop(0)
value = args.pop(0)
named_args[key] = value
continue
if current == "--":
if not args:
raise SystemExit("missing jq expression")
expr = args.pop(0)
break
if current.startswith("-"):
flags.add(current)
continue
expr = current
break
raw = sys.stdin.read()
def dump(value):
if "-r" in flags and isinstance(value, (str, int, float)) and not isinstance(value, bool):
sys.stdout.write(str(value))
else:
sys.stdout.write(json.dumps(value, separators=(",", ":")))
if expr == "length":
data = json.loads(raw or "null")
print(len(data))
raise SystemExit(0)
if expr == "add":
arrays = [json.loads(line) for line in raw.splitlines() if line.strip()]
merged = []
for item in arrays:
merged.extend(item)
dump(merged)
raise SystemExit(0)
if expr == "sort_by(.created_at) | reverse":
data = json.loads(raw or "[]")
dump(sorted(data, key=lambda item: item["created_at"], reverse=True))
raise SystemExit(0)
if expr == "[.[].tag_name]":
data = json.loads(raw or "[]")
dump([item["tag_name"] for item in data])
raise SystemExit(0)
match = re.fullmatch(r"any\(\.\[\]; \. == \$(\w+)\)", expr)
if match:
data = json.loads(raw or "[]")
needle = named_args[match.group(1)]
sys.stdout.write("true" if any(item == needle for item in data) else "false")
raise SystemExit(0)
match = re.fullmatch(r"\.\[(\d+):\]", expr)
if match:
data = json.loads(raw or "[]")
dump(data[int(match.group(1)):])
raise SystemExit(0)
if expr == ".[]":
data = json.loads(raw or "[]")
for item in data:
dump(item)
sys.stdout.write("\n")
raise SystemExit(0)
match = re.fullmatch(r"\.(id|tag_name|name)", expr)
if match:
data = json.loads(raw or "null")
value = data.get(match.group(1))
dump(value)
raise SystemExit(0)
raise SystemExit(f"unsupported jq expression: {expr}")