測驗:GitHub Actions CI 實戰 – Monorepo CI 與最佳實踐
共 5 題,點選答案後會立即顯示結果
1. 在 Monorepo 專案中,使用 path filters 的主要目的是什麼?
2. 在以下 YAML 設定中,** 代表什麼意思?
3. 在 GitHub Actions 中,如果多個 jobs 沒有使用 needs 關鍵字連接,它們會如何執行?
4. 什麼是 Reusable Workflow?它的觸發方式是什麼?
5. 當 path filter 讓某個 job 被跳過(skipped)時,如何確保 Branch Protection 的 Required Check 能正常運作?
一句話說明
把前後端放在同一個 Repo,用 path filters 讓修改哪邊就只跑哪邊的 CI。
這篇你會學到
- Monorepo 專案的 CI 架構設計
- 用 path filters 精準觸發 workflow
- 並行執行加速 CI
- Workflow 重用技巧
- CI 除錯與最佳實踐
Monorepo 專案結構
先看典型的前後端 Monorepo 長什麼樣:
my-app/
├── .github/
│ └── workflows/
│ ├── ci.yml # 主要 CI workflow
│ ├── frontend.yml # 前端專用(可選)
│ └── backend.yml # 後端專用(可選)
├── frontend/ # 前端專案
│ ├── package.json
│ ├── src/
│ └── tests/
├── backend/ # 後端專案
│ ├── requirements.txt
│ ├── src/
│ └── tests/
└── README.md
Code language: PHP (php)翻譯:前端和後端各自有獨立的目錄,但共用同一個 Git Repo 和 CI 系統。
Path Filters:只跑需要的 CI
最小範例
name: CI
on:
push:
paths:
- 'frontend/**' # 只有 frontend 目錄變動才觸發
- 'backend/**' # 只有 backend 目錄變動才觸發
Code language: PHP (php)逐行翻譯
on:
push:
paths: # 指定「哪些路徑變動才觸發」
- 'frontend/**' # frontend 目錄下任何檔案(** 表示任意深度)
- 'backend/**' # backend 目錄下任何檔案
Code language: PHP (php)常見變化
變化 1:排除某些檔案
on:
push:
paths:
- 'frontend/**'
paths-ignore:
- 'frontend/**/*.md' # 不理 Markdown 檔案
- 'frontend/docs/**' # 不理文件目錄
Code language: PHP (php)翻譯:前端有變動就跑 CI,但改文件不算。
變化 2:同時監控共用檔案
on:
push:
paths:
- 'frontend/**'
- 'shared/**' # 共用程式碼
- 'package.json' # 根目錄的設定檔
Code language: PHP (php)翻譯:前端或共用部分有變動都要跑 CI。
完整 Monorepo CI 範例
name: Monorepo CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
# ===== 偵測變動 =====
changes:
runs-on: ubuntu-latest
outputs:
frontend: ${{ steps.filter.outputs.frontend }}
backend: ${{ steps.filter.outputs.backend }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
frontend:
- 'frontend/**'
backend:
- 'backend/**'
# ===== 前端 CI =====
frontend:
needs: changes
if: ${{ needs.changes.outputs.frontend == 'true' }}
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install
run: npm ci
- name: Lint
run: npm run lint
- name: Test
run: npm test
- name: Build
run: npm run build
# ===== 後端 CI =====
backend:
needs: changes
if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
cache-dependency-path: backend/requirements.txt
- name: Install
run: pip install -r requirements.txt
- name: Lint
run: ruff check .
- name: Test
run: pytest
Code language: PHP (php)逐段翻譯
jobs:
changes: # 第一個 job:偵測哪些目錄有變動
outputs:
frontend: ... # 輸出:前端有沒有變
backend: ... # 輸出:後端有沒有變
Code language: PHP (php)翻譯:先跑一個「偵測 job」,看看這次 commit 改了什麼。
frontend:
needs: changes # 等 changes job 跑完
if: ${{ needs.changes.outputs.frontend == 'true' }} # 前端有變才跑
Code language: PHP (php)翻譯:如果 changes job 說前端有變動,才執行前端 CI。
defaults:
run:
working-directory: frontend # 所有指令都在 frontend 目錄執行
Code language: PHP (php)翻譯:不用每個 run 都寫 cd frontend,統一設定工作目錄。
並行執行加速 CI
Matrix 策略
jobs:
test:
strategy:
matrix:
project: [frontend, backend] # 同時跑兩個
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Test ${{ matrix.project }}
run: |
cd ${{ matrix.project }}
npm test || pytest
Code language: PHP (php)翻譯:GitHub Actions 會同時啟動兩個 runner,一個跑前端、一個跑後端。
並行 Jobs
jobs:
lint: # 這三個 job 會同時開始
...
test: # 沒有 needs,所以不用等
...
build:
needs: [lint, test] # 這個要等前兩個都完成
...
Code language: PHP (php)翻譯:沒寫 needs 的 jobs 會同時執行,寫了才會等。
Workflow 重用
方法 1:Composite Action
把重複的步驟包成可重用的 action:
# .github/actions/setup-node-project/action.yml
name: Setup Node Project
description: 安裝 Node.js 並執行 npm ci
inputs:
working-directory:
description: 專案目錄
required: true
runs:
using: composite
steps:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: ${{ inputs.working-directory }}/package-lock.json
- name: Install dependencies
shell: bash
run: npm ci
working-directory: ${{ inputs.working-directory }}
Code language: PHP (php)使用方式:
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-node-project
with:
working-directory: frontend
翻譯:把「安裝 Node + npm ci」包成一個動作,以後一行搞定。
方法 2:Reusable Workflow
把整個 workflow 包成可重用的模板:
# .github/workflows/node-ci.yml
name: Node.js CI Template
on:
workflow_call: # 這是關鍵:允許被其他 workflow 呼叫
inputs:
working-directory:
required: true
type: string
jobs:
ci:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ inputs.working-directory }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run lint
- run: npm test
Code language: PHP (php)使用方式:
# .github/workflows/ci.yml
jobs:
frontend:
uses: ./.github/workflows/node-ci.yml
with:
working-directory: frontend
Code language: PHP (php)翻譯:把整套 CI 流程做成模板,不同專案用同一套。
Branch Protection 與 Required Checks
在 GitHub 設定中保護主分支:
Settings > Branches > Add rule
Branch name pattern: main
[v] Require a pull request before merging
[v] Require status checks to pass before merging
- frontend
- backend
[v] Require branches to be up to date before merging
Code language: CSS (css)常見問題:Skipped Jobs 無法滿足 Required Check
當 path filter 讓某個 job 被跳過時,會顯示 skipped 狀態,GitHub 預設不會把它當成「通過」。
解法:加一個總結 job
jobs:
changes:
...
frontend:
needs: changes
if: ${{ needs.changes.outputs.frontend == 'true' }}
...
backend:
needs: changes
if: ${{ needs.changes.outputs.backend == 'true' }}
...
# 總結 job:永遠都會跑
ci-success:
needs: [frontend, backend]
if: always()
runs-on: ubuntu-latest
steps:
- name: Check results
run: |
if [[ "${{ needs.frontend.result }}" == "failure" ]] || \
[[ "${{ needs.backend.result }}" == "failure" ]]; then
echo "CI failed"
exit 1
fi
echo "CI passed"
Code language: PHP (php)翻譯:加一個「收尾 job」,它永遠會跑,然後檢查前面的 jobs 有沒有失敗。把這個 job 設成 required check 就行了。
CI 失敗的除錯技巧
1. 看 Job Summary
每個 workflow run 頁面最上面有 Summary,會顯示:
- 哪些 jobs 成功/失敗
- 執行時間
- 產出的 artifacts
2. 展開失敗的步驟
點進失敗的 job,紅色的步驟就是出錯的地方。展開看完整 log。
3. 常見錯誤對照表
| 你看到的錯誤 | 可能原因 |
|---|---|
Permission denied |
沒有執行權限,加 chmod +x |
Command not found |
套件沒裝或路徑不對 |
ENOENT: no such file |
工作目錄不對或檔案不存在 |
npm ci 失敗 |
package-lock.json 與 package.json 不同步 |
Timeout |
執行太久,考慮拆分或加快 |
4. 本地重現
# 用 act 在本地跑 GitHub Actions
brew install act
act -j frontend
Code language: PHP (php)翻譯:act 工具可以在本地模擬 GitHub Actions 環境。
CI 最佳實踐清單
效能優化
- [x] Cache 依賴:用
cache選項避免每次重新下載 - [x] 並行執行:獨立的 jobs 不要用
needs串起來 - [x] Path filters:只在相關檔案變動時觸發
- [x] 選對 runner:簡單任務用
ubuntu-latest,需要特定環境才用其他
可維護性
- [x] 重用 workflow:用 composite action 或 reusable workflow
- [x] 統一版本:把 Node/Python 版本寫在一個地方
- [x] 有意義的 job 名稱:讓人一眼看出在做什麼
- [x] 加註解:特別是
if條件和複雜邏輯
安全性
- [x] Secrets 管理:敏感資料用
${{ secrets.XXX }} - [x] Pin action 版本:用
@v4而非@main - [x] 限制 permissions:只給需要的權限
permissions:
contents: read # 只能讀,不能寫
pull-requests: write
Code language: PHP (php)Vibe Coder 檢查點
看到 Monorepo CI 設定時確認:
- [ ] Path filters 有設對嗎? 改前端會觸發後端 CI 嗎?
- [ ] 工作目錄對嗎?
working-directory或cd有沒有寫 - [ ] Cache 有設嗎? 看
cache和cache-dependency-path - [ ] 需要 required check 嗎? 是否有總結 job 處理 skipped 狀況
- [ ] Secrets 安全嗎? 有沒有把 API Key 寫死在 yaml 裡
本系列回顧
恭喜你完成 GitHub Actions CI 實戰系列!來回顧一下你學到了什麼:
| 篇數 | 主題 | 你學會的 |
|---|---|---|
| #01 | 第一個 Workflow | on、jobs、steps 基本結構 |
| #02 | 環境與 Secrets | 環境變數、Secrets、環境設定 |
| #03 | 真實 CI 流程 | Lint、Test、Build 完整流程 |
| #04 | Monorepo 整合 | Path filters、並行、重用、最佳實踐 |
下一步建議:
- 在自己的專案套用這些設定
- 嘗試加入 CD(自動部署)流程
- 探索 GitHub Actions Marketplace 找更多好用的 action
總結
這篇教了你:
- Monorepo 結構:前後端各自目錄,共用 CI
- Path filters:只在對應目錄變動時觸發
- 並行執行:用 matrix 或獨立 jobs 加速
- Workflow 重用:composite action 和 reusable workflow
- Required checks:用總結 job 解決 skipped 問題
- 除錯技巧:看 summary、展開 log、本地用 act 測試
現在你已經具備設計和維護 CI 系統的能力了!
進階測驗:GitHub Actions CI 實戰 – Monorepo CI 與最佳實踐
共 5 題,包含情境題與錯誤診斷題。