Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcf6b0838b | ||
|
|
c37b33f397 |
@@ -1,6 +1,6 @@
|
|||||||
# Release Cleanup
|
# Release Cleanup
|
||||||
|
|
||||||
> 更新時間:2026/06/30 17:58:12(台灣時區 Asia/Taipei)
|
> 更新時間:2026/07/01 09:00:53(台灣時區 Asia/Taipei)
|
||||||
|
|
||||||
清理舊成品的 **Gitea / GitHub docker container action**。依保留數量清理 repo 的舊 release,並刪除沒有對應 release 的 tag;正式版與 beta 版各自獨立保留指定數量。
|
清理舊成品的 **Gitea / GitHub docker container action**。依保留數量清理 repo 的舊 release,並刪除沒有對應 release 的 tag;正式版與 beta 版各自獨立保留指定數量。
|
||||||
|
|
||||||
@@ -10,23 +10,30 @@
|
|||||||
## 行為總覽
|
## 行為總覽
|
||||||
|
|
||||||
1. **參數檢查**:驗證 `GITEA_SERVER_URL`、`GITEA_REPOSITORY`、`KEEP_COUNT`(缺值或 `KEEP_COUNT` 非整數時以 exit code 1 結束)。
|
1. **參數檢查**:驗證 `GITEA_SERVER_URL`、`GITEA_REPOSITORY`、`KEEP_COUNT`(缺值或 `KEEP_COUNT` 非整數時以 exit code 1 結束)。
|
||||||
2. **取得成品資訊**:取得所有 release,依建立時間由新到舊排序,並以 tag 名稱含 `-beta.` 與否分為正式版與 beta 版。
|
2. **取得舊版本**:取得所有 release,依建立時間由新到舊排序,並以 tag 名稱含 `-beta.` 與否分為正式版與 beta 版。
|
||||||
3. **刪除舊版本成品**:正式版與 beta 版**各自**保留最新 `KEEP_COUNT` 筆,刪除其餘 release(單筆刪除失敗只記錄錯誤,不中斷)。
|
3. **刪除舊版本**:正式版與 beta 版**各自**保留最新 `KEEP_COUNT` 筆,刪除其餘 release(單筆刪除失敗只記錄錯誤,不中斷);接著重新取得 release 後,刪除所有未被任何 release 指定的 tag。
|
||||||
4. **刪除未指定 release 的 tag**:重新取得 release 後,刪除所有未被任何 release 指定的 tag。
|
|
||||||
|
|
||||||
`GITEA_TOKEN` 為空時以匿名身分呼叫 API。取得分頁失敗或參數不合法則直接以 exit code 1 結束程序。
|
`GITEA_TOKEN` 為空時以匿名身分呼叫 API。取得分頁失敗或參數不合法則直接以 exit code 1 結束程序。
|
||||||
|
|
||||||
## 日誌輸出格式
|
## 日誌輸出格式
|
||||||
|
|
||||||
所有等級式輸出統一為 `[{等級}][{時間}]: {訊息}`(等級為 `INF`/`WRN`/`ERR`,時間為 Asia/Taipei 的 `yyyy/MM/dd HH:mm:ss`);`fail` 走 stderr,其餘走 stdout。例如:
|
所有等級式輸出統一為 `[{階段}][{等級}][{時間}]: {訊息}`:
|
||||||
|
|
||||||
|
- `階段`:目前流程階段(`參數檢查` / `取得舊版本` / `刪除舊版本`),由 `stage()` 設定;未設定時省略整個 `[階段]` 區塊。
|
||||||
|
- `等級`:`INF`(含成功訊息)/ `WRN` / `ERR`。
|
||||||
|
- `時間`:Asia/Taipei 的 `yyyy/MM/dd HH:mm:ss`。
|
||||||
|
- `fail` 走 stderr,其餘走 stdout。
|
||||||
|
|
||||||
|
範例:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
[INF][2026/06/30 17:58:12]: RELEASE_COUNT=42
|
[參數檢查][INF][2026/07/01 09:00:53]: GITEA_SERVER_URL=https://gitea.jsc.idv.tw
|
||||||
[WRN][2026/06/30 17:58:12]: GITEA_TOKEN is empty; release API calls will be anonymous
|
[取得舊版本][INF][2026/07/01 09:00:53]: RELEASE_COUNT=3
|
||||||
[ERR][2026/06/30 17:58:12]: 刪除失敗: v1.0.0 (Release 1.0.0), HTTP 500
|
[取得舊版本][INF][2026/07/01 09:00:53]: 沒有需要清理的舊版本成品
|
||||||
|
[刪除舊版本][ERR][2026/07/01 09:00:53]: 刪除 tag 失敗: v0.0.1, HTTP 500
|
||||||
```
|
```
|
||||||
|
|
||||||
> 註:`separator` / `section` 為純分隔線/標題輸出,不帶等級與時間戳。
|
> 容器啟動橫幅(名稱/用途/更新時間)由 `entrypoint.sh` 輸出,不帶等級與階段前綴。
|
||||||
|
|
||||||
## Action 輸入與環境變數
|
## Action 輸入與環境變數
|
||||||
|
|
||||||
@@ -56,7 +63,7 @@ jobs:
|
|||||||
|
|
||||||
| 專案名稱 | 專案描述 |
|
| 專案名稱 | 專案描述 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| [release-cleanup](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app) | 清理 Gitea repo 舊 release 與孤兒 tag 的 docker container action;正式版與 beta 版各自保留指定數量,並以統一等級式日誌輸出處理過程 |
|
| [release-cleanup](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app) | 清理 Gitea repo 舊 release 與孤兒 tag 的 docker container action;正式版與 beta 版各自保留指定數量,並以統一的 `[階段][等級][時間]` 日誌輸出處理過程 |
|
||||||
|
|
||||||
### 參考專案
|
### 參考專案
|
||||||
|
|
||||||
@@ -80,98 +87,99 @@ jobs:
|
|||||||
|
|
||||||
| 功能名稱 | 功能描述 |
|
| 功能名稱 | 功能描述 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| [separator](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L10) | [在 stdout 輸出視覺分隔線](#separator) |
|
| [stage](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L18) | [設定目前流程階段名稱(log 的 \[階段\] 前綴)](#stage) |
|
||||||
| [section](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L21) | [輸出區段標題區塊](#section) |
|
| [timestamp](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L29) | [取得 Asia/Taipei 時間字串](#timestamp) |
|
||||||
| [timestamp](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L34) | [取得 Asia/Taipei 時間字串](#timestamp) |
|
| [formatLog](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L44) | [組出統一格式的 log 字串](#formatlog) |
|
||||||
| [info](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L46) | [以 \[INF\] 等級輸出資訊訊息](#info) |
|
| [info](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L55) | [以 \[INF\] 等級輸出資訊訊息](#info) |
|
||||||
| [success](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L57) | [以 \[INF\] 等級輸出成功訊息](#success) |
|
| [success](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L66) | [以 \[INF\] 等級輸出成功訊息](#success) |
|
||||||
| [warn](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L67) | [以 \[WRN\] 等級輸出警告訊息](#warn) |
|
| [warn](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L76) | [以 \[WRN\] 等級輸出警告訊息](#warn) |
|
||||||
| [fail](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L78) | [以 \[ERR\] 等級輸出錯誤訊息到 stderr](#fail) |
|
| [fail](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L87) | [以 \[ERR\] 等級輸出錯誤訊息到 stderr](#fail) |
|
||||||
| [isEmptyOrNull](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L90) | [判斷值是否視為空 / 未設定](#isemptyornull) |
|
| [isEmptyOrNull](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L99) | [判斷值是否視為空 / 未設定](#isemptyornull) |
|
||||||
| [requireValue](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L103) | [檢查必填值,空值時結束程序](#requirevalue) |
|
| [requireValue](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L112) | [檢查必填值,空值時結束程序](#requirevalue) |
|
||||||
| [requireInteger](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L120) | [驗證非負整數,不符時結束程序](#requireinteger) |
|
| [requireInteger](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L129) | [驗證非負整數,不符時結束程序](#requireinteger) |
|
||||||
| [isBeta](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L134) | [判斷名稱是否為 beta 版本](#isbeta) |
|
| [isBeta](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L143) | [判斷名稱是否為 beta 版本](#isbeta) |
|
||||||
| [main](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L154) | [Action 主流程:清理舊 release 與孤兒 tag](#main) |
|
| [main](https://gitea.jsc.idv.tw/docker-actions/release-cleanup/src/branch/develop/app/index.js#L163) | [Action 主流程:清理舊 release 與孤兒 tag](#main) |
|
||||||
|
|
||||||
## 使用範例
|
## 使用範例
|
||||||
|
|
||||||
<a id="separator"></a>
|
<a id="stage"></a>
|
||||||
### separator
|
### stage
|
||||||
|
|
||||||
在 stdout 輸出一條前後帶換行、由 50 個等號組成的視覺分隔線,用於在 log 中切割區段。無參數、無回傳值。
|
設定目前流程階段名稱,之後 `info`/`success`/`warn`/`fail` 輸出的每行 log 會以 `[階段]` 前綴標示所屬階段;傳入空字串可清除前綴。
|
||||||
|
|
||||||
```js
|
```js
|
||||||
separator();
|
stage('取得舊版本');
|
||||||
// 輸出:
|
info('RELEASE_COUNT=3');
|
||||||
//
|
// 輸出:[取得舊版本][INF][2026/07/01 09:00:53]: RELEASE_COUNT=3
|
||||||
// ==================================================
|
|
||||||
```
|
```
|
||||||
|
|
||||||
<a id="section"></a>
|
> 前置條件:無。結果:更新模組層級的目前階段字串;本身不輸出任何內容。
|
||||||
### section
|
|
||||||
|
|
||||||
輸出一個區段標題區塊:上緣等號分隔線、標題文字、下緣虛線,用於標示流程進入新階段。
|
|
||||||
|
|
||||||
```js
|
|
||||||
section('參數檢查');
|
|
||||||
// 輸出:
|
|
||||||
// ==================================================
|
|
||||||
// 參數檢查
|
|
||||||
// --------------------------------------------------
|
|
||||||
```
|
|
||||||
|
|
||||||
<a id="timestamp"></a>
|
<a id="timestamp"></a>
|
||||||
### timestamp
|
### timestamp
|
||||||
|
|
||||||
取得目前 **Asia/Taipei** 時區的時間字串,格式為 `yyyy/MM/dd HH:mm:ss`。使用 Node 內建 `Intl`(`toLocaleString('sv-SE')` 輸出 24 小時制再把 `-` 換成 `/`),不需額外相依。供各等級式 log helper 組裝統一格式前綴使用。
|
取得目前 **Asia/Taipei** 時區的時間字串,格式為 `yyyy/MM/dd HH:mm:ss`。使用 Node 內建 `Intl`(`toLocaleString('sv-SE')` 輸出 24 小時制再把 `-` 換成 `/`),不需額外相依。供 `formatLog` 組裝統一格式前綴使用。
|
||||||
|
|
||||||
```js
|
```js
|
||||||
timestamp();
|
timestamp();
|
||||||
// 例如回傳: '2026/06/30 17:58:12'
|
// 例如回傳: '2026/07/01 09:00:53'
|
||||||
info(`RELEASE_COUNT=42`);
|
|
||||||
// 內部即以 timestamp() 組出: [INF][2026/06/30 17:58:12]: RELEASE_COUNT=42
|
|
||||||
```
|
```
|
||||||
|
|
||||||
> 前置條件:執行環境須支援 `Intl`(Node 18+ 內建)。結果:回傳當下台灣時間字串,無副作用。
|
> 前置條件:執行環境須支援 `Intl`(Node 18+ 內建)。結果:回傳當下台灣時間字串,無副作用。
|
||||||
|
|
||||||
|
<a id="formatlog"></a>
|
||||||
|
### formatLog
|
||||||
|
|
||||||
|
依統一格式組出一行 log 字串:`[階段?][等級][時間]: 訊息`。階段取自目前 `currentStage`(為空字串時省略整個 `[階段]` 區塊),時間為 Asia/Taipei。為 `info`/`success`/`warn`/`fail` 共用的內部格式化函式,本身不輸出。
|
||||||
|
|
||||||
|
```js
|
||||||
|
stage('刪除舊版本');
|
||||||
|
formatLog('ERR', '刪除 tag 失敗: v0.0.1, HTTP 500');
|
||||||
|
// 回傳: '[刪除舊版本][ERR][2026/07/01 09:00:53]: 刪除 tag 失敗: v0.0.1, HTTP 500'
|
||||||
|
```
|
||||||
|
|
||||||
<a id="info"></a>
|
<a id="info"></a>
|
||||||
### info
|
### info
|
||||||
|
|
||||||
以 `[INF][時間]:` 前綴將一般資訊訊息輸出到 stdout(`console.log`,時間為 Asia/Taipei)。
|
以 `[階段?][INF][時間]:` 前綴將一般資訊訊息輸出到 stdout(`console.log`)。
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
stage('取得舊版本');
|
||||||
info('RELEASE_COUNT=10');
|
info('RELEASE_COUNT=10');
|
||||||
// 輸出:[INF][2026/06/30 17:58:12]: RELEASE_COUNT=10
|
// 輸出:[取得舊版本][INF][2026/07/01 09:00:53]: RELEASE_COUNT=10
|
||||||
```
|
```
|
||||||
|
|
||||||
<a id="success"></a>
|
<a id="success"></a>
|
||||||
### success
|
### success
|
||||||
|
|
||||||
以 `[INF][時間]:` 前綴將成功訊息輸出到 stdout。規範等級碼無「成功」一項,故等級採 `INF`,成功語意保留於訊息文字。
|
以 `[階段?][INF][時間]:` 前綴將成功訊息輸出到 stdout。規範等級碼無「成功」一項,故等級採 `INF`,成功語意保留於訊息文字。
|
||||||
|
|
||||||
```js
|
```js
|
||||||
success('成功刪除: v1.0.0 (Release 1.0.0)');
|
stage('取得舊版本');
|
||||||
// 輸出:[INF][2026/06/30 17:58:12]: 成功刪除: v1.0.0 (Release 1.0.0)
|
success('沒有需要清理的舊版本成品');
|
||||||
|
// 輸出:[取得舊版本][INF][2026/07/01 09:00:53]: 沒有需要清理的舊版本成品
|
||||||
```
|
```
|
||||||
|
|
||||||
<a id="warn"></a>
|
<a id="warn"></a>
|
||||||
### warn
|
### warn
|
||||||
|
|
||||||
以 `[WRN][時間]:` 前綴將警告訊息輸出到 stdout(非致命情況,程式會繼續執行)。
|
以 `[階段?][WRN][時間]:` 前綴將警告訊息輸出到 stdout(非致命情況,程式會繼續執行)。
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
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/06/30 17:58:12]: 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
|
||||||
```
|
```
|
||||||
|
|
||||||
<a id="fail"></a>
|
<a id="fail"></a>
|
||||||
### fail
|
### fail
|
||||||
|
|
||||||
以 `[ERR][時間]:` 前綴將錯誤訊息輸出到 **stderr**(`console.error`)。僅負責輸出,不會結束程序;是否離開由呼叫端決定。
|
以 `[階段?][ERR][時間]:` 前綴將錯誤訊息輸出到 **stderr**(`console.error`)。僅負責輸出,不會結束程序;是否離開由呼叫端決定。
|
||||||
|
|
||||||
```js
|
```js
|
||||||
fail('刪除失敗: v0.1.0 (Release 0.1.0), HTTP 500');
|
stage('刪除舊版本');
|
||||||
// 輸出(stderr):[ERR][2026/06/30 17:58:12]: 刪除失敗: v0.1.0 (Release 0.1.0), HTTP 500
|
fail('刪除 tag 失敗: v0.0.1, HTTP 500');
|
||||||
|
// 輸出(stderr):[刪除舊版本][ERR][2026/07/01 09:00:53]: 刪除 tag 失敗: v0.0.1, HTTP 500
|
||||||
process.exit(1); // 終止由呼叫端負責
|
process.exit(1); // 終止由呼叫端負責
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -189,11 +197,12 @@ isEmptyOrNull('abc'); // false
|
|||||||
<a id="requirevalue"></a>
|
<a id="requirevalue"></a>
|
||||||
### requireValue
|
### requireValue
|
||||||
|
|
||||||
檢查必填值。先以 `[INF]` 印出 `name=value`,若值被判定為空則以 `[ERR]` 輸出並以 exit code 1 結束程序。
|
檢查必填值。先以 `info`(`[INF]`)印出 `name=value`,若值被判定為空則以 `fail`(`[ERR]`)輸出並以 exit code 1 結束程序。
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
stage('參數檢查');
|
||||||
requireValue('GITEA_SERVER_URL', process.env.GITEA_SERVER_URL);
|
requireValue('GITEA_SERVER_URL', process.env.GITEA_SERVER_URL);
|
||||||
// 值為空 → 輸出 [ERR][時間]: GITEA_SERVER_URL is required,並 process.exit(1)
|
// 值為空 → 輸出 [參數檢查][ERR][時間]: GITEA_SERVER_URL is required,並 process.exit(1)
|
||||||
```
|
```
|
||||||
|
|
||||||
> 前置條件:應在程式啟動早期呼叫。注意此函式會將值原樣印到 stdout,故不用於機密值(程式對 `GITEA_TOKEN` 改以 redacted 方式輸出)。
|
> 前置條件:應在程式啟動早期呼叫。注意此函式會將值原樣印到 stdout,故不用於機密值(程式對 `GITEA_TOKEN` 改以 redacted 方式輸出)。
|
||||||
@@ -205,7 +214,7 @@ requireValue('GITEA_SERVER_URL', process.env.GITEA_SERVER_URL);
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
requireInteger('KEEP_COUNT', '2'); // 通過
|
requireInteger('KEEP_COUNT', '2'); // 通過
|
||||||
requireInteger('KEEP_COUNT', '-1'); // 輸出 [ERR][時間]: ... 並 process.exit(1)
|
requireInteger('KEEP_COUNT', '-1'); // 輸出 [ERR] 訊息並 process.exit(1)
|
||||||
```
|
```
|
||||||
|
|
||||||
<a id="isbeta"></a>
|
<a id="isbeta"></a>
|
||||||
@@ -222,7 +231,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。由檔尾 `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
|
||||||
@@ -234,4 +243,4 @@ main().catch((err) => {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
> 前置條件:執行環境須提供必要環境變數,且可連線到 Gitea API。結果:清理符合條件的舊 release 與孤兒 tag,並以統一等級式日誌輸出處理過程。
|
> 前置條件:執行環境須提供必要環境變數,且可連線到 Gitea API。結果:清理符合條件的舊 release 與孤兒 tag,並以統一的 `[階段][等級][時間]` 日誌輸出處理過程。
|
||||||
|
|||||||
+39
-30
@@ -2,26 +2,21 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在 stdout 輸出一條視覺分隔線(前後各一個換行 + 50 個等號),
|
* 目前流程階段名稱,作為每行 log 的 `[階段]` 前綴使用;
|
||||||
* 用於在 log 中切割不同區段。
|
* 為空字串時省略整個 `[階段]` 區塊。
|
||||||
*
|
* @type {string}
|
||||||
* @returns {void}
|
|
||||||
*/
|
*/
|
||||||
function separator() {
|
let currentStage = '';
|
||||||
process.stdout.write('\n==================================================\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 輸出一個區段標題區塊:上緣等號分隔線、標題文字、下緣虛線,
|
* 設定目前流程階段名稱,之後輸出的 log 會以 `[階段]` 前綴標示所屬階段。
|
||||||
* 用於在 log 中標示流程進入新階段。
|
* 傳入空字串可清除階段前綴。
|
||||||
*
|
*
|
||||||
* @param {string} title 區段標題文字。
|
* @param {string} name 階段名稱(顯示於每行 log 開頭的 `[階段]` 區塊)。
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
function section(title) {
|
function stage(name) {
|
||||||
separator();
|
currentStage = name;
|
||||||
process.stdout.write(`${title}\n`);
|
|
||||||
process.stdout.write('--------------------------------------------------\n');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,45 +33,59 @@ function timestamp() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 以 `[INF][時間]:` 前綴將一般資訊訊息輸出到 stdout(時間為 Asia/Taipei)。
|
* 依統一格式組出一行 log:`[階段?][等級][時間]: 訊息`。
|
||||||
|
* 階段取自目前 currentStage(為空字串時省略整個 `[階段]` 區塊);
|
||||||
|
* 時間為 Asia/Taipei 的 `yyyy/MM/dd HH:mm:ss`。
|
||||||
|
*
|
||||||
|
* @param {string} level 等級碼(INF/WRN/ERR)。
|
||||||
|
* @param {string} msg 訊息內容。
|
||||||
|
* @returns {string} 組裝後的 log 字串。
|
||||||
|
*/
|
||||||
|
function formatLog(level, msg) {
|
||||||
|
const stagePart = currentStage ? `[${currentStage}]` : '';
|
||||||
|
return `${stagePart}[${level}][${timestamp()}]: ${msg}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 以 `[階段?][INF][時間]:` 前綴將一般資訊訊息輸出到 stdout(時間為 Asia/Taipei)。
|
||||||
*
|
*
|
||||||
* @param {string} msg 要輸出的訊息內容。
|
* @param {string} msg 要輸出的訊息內容。
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
function info(msg) {
|
function info(msg) {
|
||||||
console.log(`[INF][${timestamp()}]: ${msg}`);
|
console.log(formatLog('INF', msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 以 `[INF][時間]:` 前綴將成功訊息輸出到 stdout(時間為 Asia/Taipei)。
|
* 以 `[階段?][INF][時間]:` 前綴將成功訊息輸出到 stdout(時間為 Asia/Taipei)。
|
||||||
* 規範等級碼無「成功」,故等級採 INF,成功語意保留於訊息文字。
|
* 規範等級碼無「成功」,故等級採 INF,成功語意保留於訊息文字。
|
||||||
*
|
*
|
||||||
* @param {string} msg 要輸出的訊息內容。
|
* @param {string} msg 要輸出的訊息內容。
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
function success(msg) {
|
function success(msg) {
|
||||||
console.log(`[INF][${timestamp()}]: ${msg}`);
|
console.log(formatLog('INF', msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 以 `[WRN][時間]:` 前綴將警告訊息輸出到 stdout(時間為 Asia/Taipei)。
|
* 以 `[階段?][WRN][時間]:` 前綴將警告訊息輸出到 stdout(時間為 Asia/Taipei)。
|
||||||
*
|
*
|
||||||
* @param {string} msg 要輸出的訊息內容。
|
* @param {string} msg 要輸出的訊息內容。
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
function warn(msg) {
|
function warn(msg) {
|
||||||
console.log(`[WRN][${timestamp()}]: ${msg}`);
|
console.log(formatLog('WRN', msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 以 `[ERR][時間]:` 前綴將錯誤訊息輸出到 stderr(時間為 Asia/Taipei)。
|
* 以 `[階段?][ERR][時間]:` 前綴將錯誤訊息輸出到 stderr(時間為 Asia/Taipei)。
|
||||||
* 僅負責輸出,不會結束程序(是否離開由呼叫端決定)。
|
* 僅負責輸出,不會結束程序(是否離開由呼叫端決定)。
|
||||||
*
|
*
|
||||||
* @param {string} msg 要輸出的錯誤訊息內容。
|
* @param {string} msg 要輸出的錯誤訊息內容。
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
function fail(msg) {
|
function fail(msg) {
|
||||||
console.error(`[ERR][${timestamp()}]: ${msg}`);
|
console.error(formatLog('ERR', msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,8 +101,8 @@ function isEmptyOrNull(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 檢查必填值。先以 [INFO] 印出 name=value,
|
* 檢查必填值。先以 info([INF])印出 name=value,
|
||||||
* 若值被判定為空(見 isEmptyOrNull),印出 [ERR] 並以 exit code 1 結束程序。
|
* 若值被判定為空(見 isEmptyOrNull),以 fail([ERR])輸出並以 exit code 1 結束程序。
|
||||||
*
|
*
|
||||||
* @param {string} name 參數名稱(用於 log 與錯誤訊息)。
|
* @param {string} name 參數名稱(用於 log 與錯誤訊息)。
|
||||||
* @param {*} value 要驗證的參數值。
|
* @param {*} value 要驗證的參數值。
|
||||||
@@ -157,7 +166,9 @@ async function main() {
|
|||||||
const GITEA_TOKEN = process.env.GITEA_TOKEN;
|
const GITEA_TOKEN = process.env.GITEA_TOKEN;
|
||||||
const KEEP_COUNT_RAW = process.env.KEEP_COUNT;
|
const KEEP_COUNT_RAW = process.env.KEEP_COUNT;
|
||||||
|
|
||||||
section('參數檢查');
|
// 於容器啟動橫幅與後續 log 之間留一空行,方便在 CI log 中閱讀
|
||||||
|
process.stdout.write('\n');
|
||||||
|
stage('參數檢查');
|
||||||
requireValue('GITEA_SERVER_URL', GITEA_SERVER_URL);
|
requireValue('GITEA_SERVER_URL', GITEA_SERVER_URL);
|
||||||
requireValue('GITEA_REPOSITORY', GITEA_REPOSITORY);
|
requireValue('GITEA_REPOSITORY', GITEA_REPOSITORY);
|
||||||
requireValue('KEEP_COUNT', KEEP_COUNT_RAW);
|
requireValue('KEEP_COUNT', KEEP_COUNT_RAW);
|
||||||
@@ -225,7 +236,7 @@ async function main() {
|
|||||||
|
|
||||||
const releaseApiUrl = `${GITEA_SERVER_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases`;
|
const releaseApiUrl = `${GITEA_SERVER_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases`;
|
||||||
|
|
||||||
section('取得成品資訊');
|
stage('取得舊版本');
|
||||||
info(`GET ${releaseApiUrl}`);
|
info(`GET ${releaseApiUrl}`);
|
||||||
|
|
||||||
let releases = await fetchAllPages(releaseApiUrl);
|
let releases = await fetchAllPages(releaseApiUrl);
|
||||||
@@ -251,7 +262,7 @@ async function main() {
|
|||||||
if (releasesToDelete.length === 0) {
|
if (releasesToDelete.length === 0) {
|
||||||
success('沒有需要清理的舊版本成品');
|
success('沒有需要清理的舊版本成品');
|
||||||
} else {
|
} else {
|
||||||
section('刪除舊版本成品');
|
stage('刪除舊版本');
|
||||||
info(`DELETE_RELEASE_COUNT=${releasesToDelete.length}`);
|
info(`DELETE_RELEASE_COUNT=${releasesToDelete.length}`);
|
||||||
|
|
||||||
for (const release of releasesToDelete) {
|
for (const release of releasesToDelete) {
|
||||||
@@ -276,7 +287,7 @@ async function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
section('刪除未指定 release 的 tag');
|
stage('刪除舊版本');
|
||||||
|
|
||||||
// 重新取得 release 清單,得到刪除舊版本後仍指定 tag 的成品
|
// 重新取得 release 清單,得到刪除舊版本後仍指定 tag 的成品
|
||||||
const currentReleases = await fetchAllPages(releaseApiUrl);
|
const currentReleases = await fetchAllPages(releaseApiUrl);
|
||||||
@@ -311,8 +322,6 @@ async function main() {
|
|||||||
fail(`刪除 tag 失敗: ${tagName}, HTTP ${code}`);
|
fail(`刪除 tag 失敗: ${tagName}, HTTP ${code}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
separator();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch((err) => {
|
main().catch((err) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user