Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1832151e0e | ||
|
|
a4fa7fec85 | ||
|
|
0ab90be3bb | ||
|
|
65ab58bf8c | ||
|
|
d4911ddd9a | ||
|
|
9378ea8d1b | ||
|
|
9d05ddf1ff | ||
|
|
805c88d0d5 |
@@ -1,7 +1,9 @@
|
|||||||
|
# ===================================================================
|
||||||
# 用途:本檔為 Gitea CD(持續部署)工作流程設定。
|
# 用途:本檔為 Gitea CD(持續部署)工作流程設定。
|
||||||
# 當 master 分支有 push 時自動觸發,使用公司 Gitea 上的
|
# 當 master 分支有 push 時自動觸發,使用公司 Gitea 上的
|
||||||
# composite action「release-tag-version」對建置成品進行釋出與版本標註(tag)。
|
# composite action「release-tag-version」對建置成品進行釋出與版本標註(tag)。
|
||||||
# 更新日期(台灣時區 Asia/Taipei):2026/06/30 16:44:09
|
# 更新日期(台灣時區 Asia/Taipei):2026/07/01 09:14:10
|
||||||
|
# ===================================================================
|
||||||
|
|
||||||
# 工作流程名稱,顯示於 Gitea Actions 介面,用以辨識此 CD 流程
|
# 工作流程名稱,顯示於 Gitea Actions 介面,用以辨識此 CD 流程
|
||||||
name: CD
|
name: CD
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# 用途:Gitea CI workflow,於 Pull Request 開啟(opened)或同步更新(synchronize)時觸發(master 分支除外)。
|
# 用途:Gitea CI workflow,於 Pull Request 開啟(opened)或同步更新(synchronize)時觸發(master 分支除外)。
|
||||||
# 流程先呼叫 composite action `release-tag-version`(is_beta: true)標註一個 beta 成品版本並輸出版本號,
|
# 流程先呼叫 composite action `release-tag-version`(is_beta: true)標註一個 beta 成品版本並輸出版本號,
|
||||||
# 再以該 beta 版本(@v${version})執行本專案的 `release-cleanup` action,對自身成品做清理(dogfooding 自我清理驗證)。
|
# 再以該 beta 版本(@v${version})執行本專案的 `release-cleanup` action,對自身成品做清理(dogfooding 自我清理驗證)。
|
||||||
# 更新日期(台灣時區 Asia/Taipei):2026/06/30 18:19:59
|
# 更新日期(台灣時區 Asia/Taipei):2026/07/01 09:14:10
|
||||||
# ===================================================================
|
# ===================================================================
|
||||||
|
|
||||||
# workflow 名稱,顯示於 Gitea Actions 介面
|
# workflow 名稱,顯示於 Gitea Actions 介面
|
||||||
|
|||||||
+2
-2
@@ -5,7 +5,7 @@
|
|||||||
# 建置 Release Cleanup docker container action 的映像。
|
# 建置 Release Cleanup docker container action 的映像。
|
||||||
# 以 node alpine 為基底,複製 app/ 主程式與 entrypoint.sh,設定入口。
|
# 以 node alpine 為基底,複製 app/ 主程式與 entrypoint.sh,設定入口。
|
||||||
#
|
#
|
||||||
# 更新日期:2026/06/30 12:02:55(台灣時區)
|
# 更新日期(台灣時區 Asia/Taipei):2026/07/01 09:14:10
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -29,7 +29,7 @@ WORKDIR /app
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# 3. 複製檔案:主程式(app/)與啟動腳本
|
# 3. 複製檔案:主程式(app/)與啟動腳本
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# 複製 app/ 目錄全部內容到映像內 /app/(主程式,含 index.js 進入點)
|
# 複製 app/ 目錄全部內容到映像內 /app/(主程式,含 index.js 進入點;須與 entrypoint.sh 的 /app/index.js 一致)
|
||||||
COPY app/ /app/
|
COPY app/ /app/
|
||||||
# 複製啟動腳本到映像根目錄 /entrypoint.sh(供 ENTRYPOINT 使用)
|
# 複製啟動腳本到映像根目錄 /entrypoint.sh(供 ENTRYPOINT 使用)
|
||||||
COPY entrypoint.sh /entrypoint.sh
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Release Cleanup
|
# Release Cleanup
|
||||||
|
|
||||||
> 更新時間:2026/07/01 09:00:53(台灣時區 Asia/Taipei)
|
> 更新時間:2026/07/01 09:20:52(台灣時區 Asia/Taipei)
|
||||||
|
|
||||||
清理舊成品的 **Gitea / GitHub docker container action**。依保留數量清理 repo 的舊 release,並刪除沒有對應 release 的 tag;正式版與 beta 版各自獨立保留指定數量。
|
清理舊成品的 **Gitea / GitHub docker container action**。依保留數量清理 repo 的舊 release,並刪除沒有對應 release 的 tag;正式版與 beta 版各自獨立保留指定數量。
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
2. **取得舊版本**:取得所有 release,依建立時間由新到舊排序,並以 tag 名稱含 `-beta.` 與否分為正式版與 beta 版。
|
2. **取得舊版本**:取得所有 release,依建立時間由新到舊排序,並以 tag 名稱含 `-beta.` 與否分為正式版與 beta 版。
|
||||||
3. **刪除舊版本**:正式版與 beta 版**各自**保留最新 `KEEP_COUNT` 筆,刪除其餘 release(單筆刪除失敗只記錄錯誤,不中斷);接著重新取得 release 後,刪除所有未被任何 release 指定的 tag。
|
3. **刪除舊版本**:正式版與 beta 版**各自**保留最新 `KEEP_COUNT` 筆,刪除其餘 release(單筆刪除失敗只記錄錯誤,不中斷);接著重新取得 release 後,刪除所有未被任何 release 指定的 tag。
|
||||||
|
|
||||||
`GITEA_TOKEN` 為空時以匿名身分呼叫 API。取得分頁失敗或參數不合法則直接以 exit code 1 結束程序。
|
`GITEA_TOKEN` 為空時以匿名身分呼叫 API。取得分頁失敗或參數不合法則直接以 exit code 1 結束程序;底層 `fetch` 網路錯誤或回應非 JSON 造成的解析失敗會上拋,由檔尾 `main().catch` 統一捕捉後以 exit code 1 結束。
|
||||||
|
|
||||||
## 日誌輸出格式
|
## 日誌輸出格式
|
||||||
|
|
||||||
@@ -27,10 +27,10 @@
|
|||||||
範例:
|
範例:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
[參數檢查][INF][2026/07/01 09:00:53]: GITEA_SERVER_URL=https://gitea.jsc.idv.tw
|
[參數檢查][INF][2026/07/01 09:20:52]: GITEA_SERVER_URL=https://gitea.jsc.idv.tw
|
||||||
[取得舊版本][INF][2026/07/01 09:00:53]: RELEASE_COUNT=3
|
[取得舊版本][INF][2026/07/01 09:20:52]: RELEASE_COUNT=3
|
||||||
[取得舊版本][INF][2026/07/01 09:00:53]: 沒有需要清理的舊版本成品
|
[取得舊版本][INF][2026/07/01 09:20:52]: 沒有需要清理的舊版本成品
|
||||||
[刪除舊版本][ERR][2026/07/01 09:00:53]: 刪除 tag 失敗: v0.0.1, HTTP 500
|
[刪除舊版本][ERR][2026/07/01 09:20:52]: 刪除 tag 失敗: v0.0.1, HTTP 500
|
||||||
```
|
```
|
||||||
|
|
||||||
> 容器啟動橫幅(名稱/用途/更新時間)由 `entrypoint.sh` 輸出,不帶等級與階段前綴。
|
> 容器啟動橫幅(名稱/用途/更新時間)由 `entrypoint.sh` 輸出,不帶等級與階段前綴。
|
||||||
@@ -83,7 +83,7 @@ jobs:
|
|||||||
|
|
||||||
### release-cleanup
|
### release-cleanup
|
||||||
|
|
||||||
> 說明:本專案為單一 Node 模組(`app/index.js`),下列為模組內可文件化的頂層函式(不含 `main` 內部的巢狀私有函式 `fetchAllPages` / `deleteUrl`)。
|
> 說明:本專案為單一 Node 模組(`app/index.js`),未 `module.exports` 任何函式,下列為模組內可文件化的頂層函式(不含 `main` 內部的巢狀私有函式 `fetchAllPages` / `deleteUrl`)。
|
||||||
|
|
||||||
| 功能名稱 | 功能描述 |
|
| 功能名稱 | 功能描述 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
@@ -110,7 +110,7 @@ jobs:
|
|||||||
```js
|
```js
|
||||||
stage('取得舊版本');
|
stage('取得舊版本');
|
||||||
info('RELEASE_COUNT=3');
|
info('RELEASE_COUNT=3');
|
||||||
// 輸出:[取得舊版本][INF][2026/07/01 09:00:53]: RELEASE_COUNT=3
|
// 輸出:[取得舊版本][INF][2026/07/01 09:20:52]: RELEASE_COUNT=3
|
||||||
```
|
```
|
||||||
|
|
||||||
> 前置條件:無。結果:更新模組層級的目前階段字串;本身不輸出任何內容。
|
> 前置條件:無。結果:更新模組層級的目前階段字串;本身不輸出任何內容。
|
||||||
@@ -122,7 +122,7 @@ info('RELEASE_COUNT=3');
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
timestamp();
|
timestamp();
|
||||||
// 例如回傳: '2026/07/01 09:00:53'
|
// 例如回傳: '2026/07/01 09:20:52'
|
||||||
```
|
```
|
||||||
|
|
||||||
> 前置條件:執行環境須支援 `Intl`(Node 18+ 內建)。結果:回傳當下台灣時間字串,無副作用。
|
> 前置條件:執行環境須支援 `Intl`(Node 18+ 內建)。結果:回傳當下台灣時間字串,無副作用。
|
||||||
@@ -135,7 +135,7 @@ timestamp();
|
|||||||
```js
|
```js
|
||||||
stage('刪除舊版本');
|
stage('刪除舊版本');
|
||||||
formatLog('ERR', '刪除 tag 失敗: v0.0.1, HTTP 500');
|
formatLog('ERR', '刪除 tag 失敗: v0.0.1, HTTP 500');
|
||||||
// 回傳: '[刪除舊版本][ERR][2026/07/01 09:00:53]: 刪除 tag 失敗: v0.0.1, HTTP 500'
|
// 回傳: '[刪除舊版本][ERR][2026/07/01 09:20:52]: 刪除 tag 失敗: v0.0.1, HTTP 500'
|
||||||
```
|
```
|
||||||
|
|
||||||
<a id="info"></a>
|
<a id="info"></a>
|
||||||
@@ -146,7 +146,7 @@ formatLog('ERR', '刪除 tag 失敗: v0.0.1, HTTP 500');
|
|||||||
```js
|
```js
|
||||||
stage('取得舊版本');
|
stage('取得舊版本');
|
||||||
info('RELEASE_COUNT=10');
|
info('RELEASE_COUNT=10');
|
||||||
// 輸出:[取得舊版本][INF][2026/07/01 09:00:53]: RELEASE_COUNT=10
|
// 輸出:[取得舊版本][INF][2026/07/01 09:20:52]: RELEASE_COUNT=10
|
||||||
```
|
```
|
||||||
|
|
||||||
<a id="success"></a>
|
<a id="success"></a>
|
||||||
@@ -157,7 +157,7 @@ info('RELEASE_COUNT=10');
|
|||||||
```js
|
```js
|
||||||
stage('取得舊版本');
|
stage('取得舊版本');
|
||||||
success('沒有需要清理的舊版本成品');
|
success('沒有需要清理的舊版本成品');
|
||||||
// 輸出:[取得舊版本][INF][2026/07/01 09:00:53]: 沒有需要清理的舊版本成品
|
// 輸出:[取得舊版本][INF][2026/07/01 09:20:52]: 沒有需要清理的舊版本成品
|
||||||
```
|
```
|
||||||
|
|
||||||
<a id="warn"></a>
|
<a id="warn"></a>
|
||||||
@@ -168,7 +168,7 @@ success('沒有需要清理的舊版本成品');
|
|||||||
```js
|
```js
|
||||||
stage('參數檢查');
|
stage('參數檢查');
|
||||||
warn('GITEA_TOKEN is empty; release API calls will be anonymous');
|
warn('GITEA_TOKEN is empty; release API calls will be anonymous');
|
||||||
// 輸出:[參數檢查][WRN][2026/07/01 09:00:53]: GITEA_TOKEN is empty; release API calls will be anonymous
|
// 輸出:[參數檢查][WRN][2026/07/01 09:20:52]: GITEA_TOKEN is empty; release API calls will be anonymous
|
||||||
```
|
```
|
||||||
|
|
||||||
<a id="fail"></a>
|
<a id="fail"></a>
|
||||||
@@ -179,19 +179,20 @@ warn('GITEA_TOKEN is empty; release API calls will be anonymous');
|
|||||||
```js
|
```js
|
||||||
stage('刪除舊版本');
|
stage('刪除舊版本');
|
||||||
fail('刪除 tag 失敗: v0.0.1, HTTP 500');
|
fail('刪除 tag 失敗: v0.0.1, HTTP 500');
|
||||||
// 輸出(stderr):[刪除舊版本][ERR][2026/07/01 09:00:53]: 刪除 tag 失敗: v0.0.1, HTTP 500
|
// 輸出(stderr):[刪除舊版本][ERR][2026/07/01 09:20:52]: 刪除 tag 失敗: v0.0.1, HTTP 500
|
||||||
process.exit(1); // 終止由呼叫端負責
|
process.exit(1); // 終止由呼叫端負責
|
||||||
```
|
```
|
||||||
|
|
||||||
<a id="isemptyornull"></a>
|
<a id="isemptyornull"></a>
|
||||||
### isEmptyOrNull
|
### isEmptyOrNull
|
||||||
|
|
||||||
判斷值是否視為「空 / 未設定」,涵蓋 `undefined`、`null`、空字串,以及字面字串 `'null'`(用於處理 CI/環境變數把未設定值帶成字串 `"null"` 的情況)。
|
判斷值是否視為「空 / 未設定」,涵蓋 `undefined`、`null`、空字串,以及字面字串 `'null'`(用於處理 CI/環境變數把未設定值帶成字串 `"null"` 的情況)。以嚴格相等比對,故 `0`、`false`、`'0'`、純空白字串**不**視為空。
|
||||||
|
|
||||||
```js
|
```js
|
||||||
isEmptyOrNull(''); // true
|
isEmptyOrNull(''); // true
|
||||||
isEmptyOrNull('null'); // true(字面字串)
|
isEmptyOrNull('null'); // true(字面字串)
|
||||||
isEmptyOrNull('abc'); // false
|
isEmptyOrNull('abc'); // false
|
||||||
|
isEmptyOrNull('0'); // false(非空字串)
|
||||||
```
|
```
|
||||||
|
|
||||||
<a id="requirevalue"></a>
|
<a id="requirevalue"></a>
|
||||||
@@ -210,7 +211,7 @@ requireValue('GITEA_SERVER_URL', process.env.GITEA_SERVER_URL);
|
|||||||
<a id="requireinteger"></a>
|
<a id="requireinteger"></a>
|
||||||
### requireInteger
|
### requireInteger
|
||||||
|
|
||||||
驗證值是否為非負整數字串(正則 `/^[0-9]+$/`,允許前導零、不允許負號或小數),不符合時以 `[ERR]` 輸出並以 exit code 1 結束程序。
|
驗證值是否為非負整數字串(正則 `/^[0-9]+$/`,允許前導零、不允許負號或小數),不符合時以 `[ERR]` 輸出並以 exit code 1 結束程序。本函式不處理空值語意,需搭配 `requireValue` 先攔缺值。
|
||||||
|
|
||||||
```js
|
```js
|
||||||
requireInteger('KEEP_COUNT', '2'); // 通過
|
requireInteger('KEEP_COUNT', '2'); // 通過
|
||||||
@@ -220,7 +221,7 @@ requireInteger('KEEP_COUNT', '-1'); // 輸出 [ERR] 訊息並 process.exit(1)
|
|||||||
<a id="isbeta"></a>
|
<a id="isbeta"></a>
|
||||||
### isBeta
|
### isBeta
|
||||||
|
|
||||||
判斷 release / tag 名稱是否為 beta 版本,規則為名稱中含有子字串 `-beta.`。
|
判斷 release / tag 名稱是否為 beta 版本,規則為名稱中含有子字串 `-beta.`(區分大小寫)。
|
||||||
|
|
||||||
```js
|
```js
|
||||||
isBeta('v1.2.3-beta.1'); // true
|
isBeta('v1.2.3-beta.1'); // true
|
||||||
@@ -231,7 +232,7 @@ isBeta('v1.2.3-beta'); // false(無結尾的點,不符合 -beta.)
|
|||||||
<a id="main"></a>
|
<a id="main"></a>
|
||||||
### main
|
### main
|
||||||
|
|
||||||
Action 主流程(`async`)。讀取環境變數後依序執行參數檢查、取得 release、刪除舊版本(正式版與 beta 版各自保留 `KEEP_COUNT` 筆)、刪除未指定 release 的 tag;各階段以 `stage()` 標示,log 皆帶 `[階段]` 前綴。由檔尾 `main().catch(...)` 立即執行。
|
Action 主流程(`async`)。讀取環境變數後依序執行參數檢查、取得 release、刪除舊版本(正式版與 beta 版各自保留 `KEEP_COUNT` 筆)、刪除未指定 release 的 tag;各階段以 `stage()` 標示,log 皆帶 `[階段]` 前綴。由檔尾 `main().catch(...)` 立即執行並收斂未攔截的例外。
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// 容器啟動時由 entrypoint.sh 執行:exec node /app/index.js
|
// 容器啟動時由 entrypoint.sh 執行:exec node /app/index.js
|
||||||
|
|||||||
+4
-1
@@ -196,7 +196,9 @@ async function main() {
|
|||||||
* @async
|
* @async
|
||||||
* @param {string} baseUrl 不含分頁參數的 API 端點(例如 .../releases 或 .../tags)。
|
* @param {string} baseUrl 不含分頁參數的 API 端點(例如 .../releases 或 .../tags)。
|
||||||
* @returns {Promise<Array<object>>} 所有頁面項目合併後的陣列。
|
* @returns {Promise<Array<object>>} 所有頁面項目合併後的陣列。
|
||||||
* @throws 不擲出例外;HTTP 失敗時直接呼叫 process.exit(1) 結束程序。
|
* @throws HTTP 非 2xx 時不擲例外,直接呼叫 process.exit(1) 結束程序;
|
||||||
|
* 但底層 fetch 網路錯誤或回應非 JSON 造成的 res.json() 解析失敗仍會上拋,
|
||||||
|
* 最終由檔尾 main().catch 統一捕捉後 process.exit(1)。
|
||||||
*/
|
*/
|
||||||
async function fetchAllPages(baseUrl) {
|
async function fetchAllPages(baseUrl) {
|
||||||
const all = [];
|
const all = [];
|
||||||
@@ -228,6 +230,7 @@ async function main() {
|
|||||||
* @async
|
* @async
|
||||||
* @param {string} url 要刪除之資源的完整 URL(release 或 tag 端點)。
|
* @param {string} url 要刪除之資源的完整 URL(release 或 tag 端點)。
|
||||||
* @returns {Promise<number>} 回應的 HTTP 狀態碼。
|
* @returns {Promise<number>} 回應的 HTTP 狀態碼。
|
||||||
|
* @throws 不主動擲例外;但底層 fetch 網路錯誤會上拋,由 main().catch 統一捕捉後 process.exit(1)。
|
||||||
*/
|
*/
|
||||||
async function deleteUrl(url) {
|
async function deleteUrl(url) {
|
||||||
const res = await fetch(url, { method: 'DELETE', headers: authHeaders });
|
const res = await fetch(url, { method: 'DELETE', headers: authHeaders });
|
||||||
|
|||||||
+4
-2
@@ -7,7 +7,7 @@
|
|||||||
# 中辨識正在執行哪一個 action 及其版本;接著以 exec 啟動 node 主程式
|
# 中辨識正在執行哪一個 action 及其版本;接著以 exec 啟動 node 主程式
|
||||||
# /app/index.js,並把所有外部傳入的參數原樣交給它。
|
# /app/index.js,並把所有外部傳入的參數原樣交給它。
|
||||||
#
|
#
|
||||||
# 更新日期(台灣時區):2026/06/30 18:19:59
|
# 更新日期(台灣時區 Asia/Taipei):2026/07/01 09:14:10
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
# 啟用「遇到任何指令失敗(非 0 結束碼)即立刻中止腳本」的模式。
|
# 啟用「遇到任何指令失敗(非 0 結束碼)即立刻中止腳本」的模式。
|
||||||
@@ -19,7 +19,7 @@ ACTION_NAME='Release Cleanup'
|
|||||||
# action 用途說明,供下方橫幅輸出使用。
|
# action 用途說明,供下方橫幅輸出使用。
|
||||||
ACTION_DESC='清理舊成品'
|
ACTION_DESC='清理舊成品'
|
||||||
# action 更新時間(固定字串,台灣時區),供下方橫幅輸出使用。
|
# action 更新時間(固定字串,台灣時區),供下方橫幅輸出使用。
|
||||||
ACTION_UPDATED='2026/06/30 18:19:59'
|
ACTION_UPDATED='2026/07/01 09:14:10'
|
||||||
|
|
||||||
# 輸出一個空行 + 分隔線,作為橫幅的上邊框,讓 log 中的資訊區塊更易閱讀。
|
# 輸出一個空行 + 分隔線,作為橫幅的上邊框,讓 log 中的資訊區塊更易閱讀。
|
||||||
printf '\n%s\n' '=================================================='
|
printf '\n%s\n' '=================================================='
|
||||||
@@ -36,5 +36,7 @@ printf '%s\n' '=================================================='
|
|||||||
# 而非另開子行程。如此可讓 node 成為 PID 1(或直接承接原 shell 的 PID),
|
# 而非另開子行程。如此可讓 node 成為 PID 1(或直接承接原 shell 的 PID),
|
||||||
# 正確接收容器傳來的訊號(例如 SIGTERM/SIGINT)以利優雅關閉,
|
# 正確接收容器傳來的訊號(例如 SIGTERM/SIGINT)以利優雅關閉,
|
||||||
# 並讓 node 的結束碼直接成為容器的結束碼。
|
# 並讓 node 的結束碼直接成為容器的結束碼。
|
||||||
|
# 路徑 /app/index.js 必須與 Dockerfile 的 COPY app/ /app/ 一致:主程式進入點被複製到 /app/ 下,
|
||||||
|
# 兩處若不同步(例如改了 WORKDIR 或複製目的地)會導致此處找不到檔案而啟動失敗。
|
||||||
# "$@" 將腳本收到的所有參數原樣(逐一保留分隔,避免被重新分詞)轉交給 node 主程式。
|
# "$@" 將腳本收到的所有參數原樣(逐一保留分隔,避免被重新分詞)轉交給 node 主程式。
|
||||||
exec node /app/index.js "$@"
|
exec node /app/index.js "$@"
|
||||||
|
|||||||
Reference in New Issue
Block a user