Files
Arcrun/.github/workflows/deploy.yml
T
Leo 10834ef9bb ci: 加 paths-ignore + landing concurrency cancel (省 GH Actions minutes)
對應 leo 2026-05-16 Actions 配額用爆 → 全面瘦身 CI trigger。

deploy.yml (Workers):
  既有:worker-level git diff filter(只 deploy 改到的 worker)
  新增:workflow-level paths-ignore — doc-only commit 完全不啟動 workflow
        排除:**.md / AGENTS.md / .agents/** / docs/** / registry/examples/** /
              registry/skills/** / .gitignore / LICENSE
  之前每個 doc push 都觸發 discover job 吃 ~1 min。
  本輪 LI 開發 ~10 doc-only commit = 省 ~10 min

deploy-landing.yml (Pages):
  既有:paths filter ('landing/**') 
  新增:concurrency cancel-in-progress (原 false → true)
  landing build ~3 min 是大頭,連續 push 取消舊 build 省最多
2026-05-16 16:46:40 +08:00

351 lines
13 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: Deploy Workers
# 通用 deploy workflow:掃描 repo 內所有含 wrangler.toml 的目錄 → matrix fanout 部署
# 新增 Worker = 新目錄 + wrangler.toml + (src/index.ts | component.wasm 等) + pnpm-lock.yaml
# 不用改本檔。詳見 .claude/rules/05-deploy-convention.md。
on:
push:
branches: [main]
# 2026-05-16 leo Actions 配額用爆後加 paths-ignore:純文件 commit 完全不觸發 workflow
# (之前每個 push 都會跑 discover job 吃 ~1 mindoc-only commit 多時積很多)
# worker code 變動仍由 discover job 用 git diff 過濾
paths-ignore:
- '**/*.md'
- 'AGENTS.md'
- '.agents/**'
- 'docs/**'
- 'registry/examples/**'
- 'registry/skills/**'
- '.gitignore'
- 'LICENSE'
workflow_dispatch:
inputs:
force_all:
description: "Deploy all Workers regardless of diff"
type: boolean
default: false
only:
description: "Comma-separated Worker dirs to deploy (overrides diff; empty = auto)"
type: string
default: ""
concurrency:
group: deploy-${{ github.ref }}
# 連續 push 時取消舊跑(節省 GH Actions minutes 配額;M5 之前 LI 開發頻率高)
# 2026-05-16 leo 帳號 Actions 用爆配額被 disable 一次,改用此設定降風險
cancel-in-progress: true
jobs:
# ── Job 1:掃描所有 wrangler.toml 目錄,輸出 deploy matrix ──────────────
# 分兩層:
# tier1 = .component-builds/*(零件 Worker,互不相依,全部平行)
# tier2 = 其他(cypher-executor/registry/builtins,可能透過 service binding 相依於 tier1)
# tier1 全綠後才啟動 tier2,避免 service binding target 未存在造成首次部署失敗。
discover:
name: Discover Workers
runs-on: ubuntu-latest
outputs:
tier1: ${{ steps.emit.outputs.tier1 }}
tier2: ${{ steps.emit.outputs.tier2 }}
tier1_count: ${{ steps.emit.outputs.tier1_count }}
tier2_count: ${{ steps.emit.outputs.tier2_count }}
steps:
- uses: actions/checkout@v5
with:
# 抓全部 history 以確保 github.event.before 可及
# (fetch-depth: 2 會在大批 commit push / force-push 時失效)
fetch-depth: 0
- name: Enumerate & filter
id: emit
env:
FORCE_ALL: ${{ github.event.inputs.force_all }}
ONLY: ${{ github.event.inputs.only }}
EVENT: ${{ github.event_name }}
run: |
set -euo pipefail
# 所有含 wrangler.toml 的 Worker 目錄,排除:
# - node_modules/
# - wrangler.test.toml(測試用)
# - Pages 專案(含 pages_build_output_dir,另有 build pipeline,不適用本 workflow)
mapfile -t all_dirs < <(
find . -type f -name 'wrangler.toml' \
-not -path '*/node_modules/*' \
-not -name 'wrangler.test.toml' \
| while read f; do
# 排除 Pages 專案(wrangler pages deploy 與 wrangler deploy 流程不同)
if grep -q 'pages_build_output_dir' "$f"; then
continue
fi
dirname "$f"
done \
| sort -u \
| sed 's|^\./||'
)
echo "Found ${#all_dirs[@]} worker dirs"
for d in "${all_dirs[@]}"; do echo " - $d"; done
# 決定要部署哪些
declare -a targets=()
if [[ "$EVENT" == "workflow_dispatch" && -n "$ONLY" ]]; then
# 手動觸發 + 指定清單
IFS=',' read -ra req <<< "$ONLY"
for r in "${req[@]}"; do
r="${r// /}"
for d in "${all_dirs[@]}"; do
if [[ "$d" == "$r" ]]; then
targets+=("$d")
fi
done
done
elif [[ "$EVENT" == "workflow_dispatch" && "$FORCE_ALL" == "true" ]]; then
targets=("${all_dirs[@]}")
elif [[ "$EVENT" == "push" ]]; then
# diff 過濾:哪些 worker 目錄有變動?
# 也要連動 registry/components/{name}/ — 改 main.go 應該 redeploy .component-builds/{name}/
base_sha="${{ github.event.before }}"
head_sha="${{ github.sha }}"
# 若 base 為 0000...(首次 push)或 base 在本地不可及,fallback 為全部
if [[ -z "$base_sha" || "$base_sha" == "0000000000000000000000000000000000000000" ]]; then
echo "No base sha, deploy all"
targets=("${all_dirs[@]}")
elif ! git cat-file -e "$base_sha" 2>/dev/null; then
echo "Base sha $base_sha unreachable locally, deploy all"
targets=("${all_dirs[@]}")
else
# 取得 diff 的檔案路徑
mapfile -t changed < <(git diff --name-only "$base_sha" "$head_sha" || true)
echo "Changed files:"
for f in "${changed[@]}"; do echo " $f"; done
for d in "${all_dirs[@]}"; do
# 判斷:若 d 下任何檔案變動,或 d 是 .component-builds/{name} 且 registry/components/{name}/ 下變動
hit=0
for f in "${changed[@]}"; do
if [[ "$f" == "$d"/* ]]; then hit=1; break; fi
done
if [[ $hit -eq 0 && "$d" == .component-builds/* ]]; then
name="${d#.component-builds/}"
for f in "${changed[@]}"; do
if [[ "$f" == "registry/components/$name"/* ]]; then hit=1; break; fi
done
fi
if [[ $hit -eq 1 ]]; then targets+=("$d"); fi
done
fi
else
targets=("${all_dirs[@]}")
fi
echo "Deploying ${#targets[@]} workers (will be split into 2 tiers):"
for t in "${targets[@]}"; do echo " - $t"; done
# 分成兩層:
# tier1 = .component-builds/*(零件 Worker,需要 WASM build)
# tier2 = 其他(orchestration Worker,可能有 service binding 相依於 tier1)
emit_json() {
local -n arr=$1
local out="["
local first=1
for t in "${arr[@]}"; do
local name needs_wasm
name="$(basename "$t")"
needs_wasm="false"
if [[ "$t" == .component-builds/* ]]; then
needs_wasm="true"
name="${t#.component-builds/}"
fi
if [[ $first -eq 0 ]]; then out+=","; fi
out+="{\"name\":\"$name\",\"path\":\"$t\",\"needsWasm\":$needs_wasm}"
first=0
done
out+="]"
echo "$out"
}
declare -a tier1=() tier2=()
for t in "${targets[@]}"; do
if [[ "$t" == .component-builds/* ]]; then
tier1+=("$t")
else
tier2+=("$t")
fi
done
tier1_json=$(emit_json tier1)
tier2_json=$(emit_json tier2)
echo "Tier 1 (${#tier1[@]}):"
for t in "${tier1[@]}"; do echo " - $t"; done
echo "Tier 2 (${#tier2[@]}):"
for t in "${tier2[@]}"; do echo " - $t"; done
echo "tier1=$tier1_json" >> "$GITHUB_OUTPUT"
echo "tier2=$tier2_json" >> "$GITHUB_OUTPUT"
echo "tier1_count=${#tier1[@]}" >> "$GITHUB_OUTPUT"
echo "tier2_count=${#tier2[@]}" >> "$GITHUB_OUTPUT"
# ── Job 2a:Tier 1 並行部署(零件 Worker,需要 WASM build) ───────────────
# tier1 所有 Worker 互不相依,全部平行;tier1 全綠後才啟動 tier2。
deploy-tier1:
name: Deploy tier1/${{ matrix.worker.name }}
needs: discover
if: needs.discover.outputs.tier1_count != '0'
runs-on: ubuntu-latest
strategy:
fail-fast: false
max-parallel: 5
matrix:
worker: ${{ fromJson(needs.discover.outputs.tier1) }}
steps:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v5
with:
node-version: '22'
# 不用 setup-node 的 pnpm cache:
# 部分歷史 Worker 只有 package-lock.json,指 pnpm-lock.yaml 會讓
# setup-node 直接報 "Some specified paths were not resolved" 並 fail。
# deploy 本身 ~30s,cache 省不了多少;穩定性優先。
- name: Setup TinyGo
uses: acifani/setup-tinygo@v2
with:
tinygo-version: '0.40.1'
binaryen-version: '116'
- name: Rebuild component.wasm from source
working-directory: registry/components/${{ matrix.worker.name }}
run: |
set -euo pipefail
if [[ ! -f main.go ]]; then
echo "no main.go at registry/components/${{ matrix.worker.name }}/ — skipping rebuild"
exit 0
fi
tinygo build -target=wasi -o "${{ matrix.worker.name }}.wasm" main.go
ls -lh "${{ matrix.worker.name }}.wasm"
- name: Copy .wasm into Worker build dir
run: |
set -euo pipefail
src="registry/components/${{ matrix.worker.name }}/${{ matrix.worker.name }}.wasm"
dst="${{ matrix.worker.path }}/component.wasm"
if [[ -f "$src" ]]; then
cp "$src" "$dst"
echo "Copied $src → $dst"
else
echo "WARNING: $src not found, using existing $dst"
fi
- name: Install deps
working-directory: ${{ matrix.worker.path }}
run: |
if [[ -f pnpm-lock.yaml ]]; then
pnpm install --frozen-lockfile
else
echo "no pnpm-lock.yaml at ${{ matrix.worker.path }} — running pnpm install (no lock)"
pnpm install --no-frozen-lockfile
fi
- name: Deploy
working-directory: ${{ matrix.worker.path }}
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: pnpm exec wrangler deploy
# 部署成功後自動把 contract 灌進 component-registry index
# SDD: matrix/arcrun/.agents/specs/component-registry-canon/design.md Phase 2
# degraded mode:失敗只 warning 不擋部署
# 邏輯實作在 registry/scripts/register-component.sh(本地+CI 共用 SSOT
- name: Register component in registry
if: success()
run: |
set -uo pipefail
python3 -c "import yaml" 2>/dev/null || pip install --quiet pyyaml
bash registry/scripts/register-component.sh "${{ matrix.worker.name }}" || \
echo "::warning::Registry 註冊失敗(degraded mode"
# ── Job 2b:Tier 2 並行部署(orchestration Worker,可能有 service binding 相依於 tier1) ─
# needs: deploy-tier1 → tier1 全綠才開始;首次部署時避免 service binding target 未存在。
deploy-tier2:
name: Deploy tier2/${{ matrix.worker.name }}
needs: [discover, deploy-tier1]
# tier2 也要跑:即使 tier1 沒東西(tier1_count=0)也要跑 tier2
if: |
always() &&
needs.discover.outputs.tier2_count != '0' &&
(needs.deploy-tier1.result == 'success' || needs.deploy-tier1.result == 'skipped')
runs-on: ubuntu-latest
strategy:
fail-fast: false
max-parallel: 5
matrix:
worker: ${{ fromJson(needs.discover.outputs.tier2) }}
steps:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v5
with:
node-version: '22'
# 不用 setup-node 的 pnpm cache:
# 部分歷史 Worker 只有 package-lock.json,指 pnpm-lock.yaml 會讓
# setup-node 直接報 "Some specified paths were not resolved" 並 fail。
# deploy 本身 ~30s,cache 省不了多少;穩定性優先。
- name: Install deps
working-directory: ${{ matrix.worker.path }}
run: |
if [[ -f pnpm-lock.yaml ]]; then
pnpm install --frozen-lockfile
else
echo "no pnpm-lock.yaml at ${{ matrix.worker.path }} — running pnpm install (no lock)"
pnpm install --no-frozen-lockfile
fi
- name: Deploy
working-directory: ${{ matrix.worker.path }}
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: pnpm exec wrangler deploy
# ── Job 3:彙總結果 ────────────────────────────────────────────────────
summary:
name: Summary
needs: [discover, deploy-tier1, deploy-tier2]
if: always()
runs-on: ubuntu-latest
steps:
- name: Report
run: |
{
echo "## Deploy Summary"
echo "- Tier 1 count: ${{ needs.discover.outputs.tier1_count }}"
echo "- Tier 1 result: ${{ needs.deploy-tier1.result }}"
echo "- Tier 2 count: ${{ needs.discover.outputs.tier2_count }}"
echo "- Tier 2 result: ${{ needs.deploy-tier2.result }}"
echo ""
echo "### Tier 1 (WASM components)"
echo '```json'
echo '${{ needs.discover.outputs.tier1 }}'
echo '```'
echo ""
echo "### Tier 2 (orchestration)"
echo '```json'
echo '${{ needs.discover.outputs.tier2 }}'
echo '```'
} >> "$GITHUB_STEP_SUMMARY"