Files
Arcrun/scripts/local-deploy.sh
Leo 37379b71bc feat(scripts): 本機 CI/CD + registry-to-KBDB 同步 (跳過 GH Actions)
對應 leo 2026-05-16 GH Actions 配額被停 + LI SDD M3.4。

scripts/local-deploy.sh — 本機 deploy 取代 GH Actions
  - 複製 .github/workflows/deploy.yml 的 discover 邏輯
  - git diff 過濾,只 deploy 改到的 worker
  - 連動:registry/components/{X} 改了,重 build .component-builds/{X}/component.wasm
  - 分 tier1 (components) / tier2 (orchestration),依序 deploy
  - 支援:--all / --dry-run / --base <ref> / 指定 target 名稱
  - 部署前 wrangler whoami 健康檢查
  - 失敗清單彙整不中斷其他 deploy

scripts/sync-registry-to-kbdb.py — LI SDD M3.4
  - registry/examples/ → KBDB type=workflow-example (page_name=example-{slug})
  - registry/skills/ → KBDB type=agent-skill (page_name=skill-{slug})
  - 走 kbdb-upsert-block.arcrun.dev (idempotent,page_name 為 key)
  - 從 ARCRUN_API_KEY env var 或 polaris/mira/.env 取金鑰
  - 支援:--dry-run / --examples-only / --skills-only

實測:
  - sync 第一次:10 examples + 5 skills 全 created
  - 第二次:全 updated (idempotent ✓)
  - KBDB type=workflow-example / agent-skill 都看得到完整 content + tags

意義:
  - 申訴 GH Actions 期間,leo / Claude Code 可本機 deploy 不卡
  - 即使 GH 永久不通也能 ship
  - sync script 之後也能加進 cron 或 git hook 自動化

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 17:28:16 +08:00

217 lines
7.0 KiB
Bash
Raw Permalink 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.
#!/usr/bin/env bash
# scripts/local-deploy.sh — 本機 deploy 取代 GH Actions
#
# 對應 LI SDD M5.6 / 2026-05-16 leo GH Actions 被停用後的 fallback
#
# 用法:
# bash scripts/local-deploy.sh # 偵測 git diffdeploy 改到的 worker
# bash scripts/local-deploy.sh --all # 全部 worker deploy
# bash scripts/local-deploy.sh cypher-executor registry # 指定 worker
# bash scripts/local-deploy.sh --base HEAD~3 # 改 diff base(預設 HEAD~1
# bash scripts/local-deploy.sh --dry-run # 只 list 不 deploy
#
# 邏輯模擬 .github/workflows/deploy.yml discover job
# - 掃所有 wrangler.toml 目錄
# - git diff base..HEAD 找改動
# - 對改到的 worker 跑 pnpm exec wrangler deploy
# - 若改到 registry/components/{name}/,連動 deploy .component-builds/{name}/
#
# 要求:
# - 已 wrangler loginpnpm exec wrangler whoami 確認)
# - 在 arcrun/ 根目錄執行
set -euo pipefail
ARCRUN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ARCRUN_ROOT"
# ── Parse args ────────────────────────────────────────────────────────────────
BASE_REF="HEAD~1"
DRY_RUN=false
DEPLOY_ALL=false
EXPLICIT_TARGETS=()
while [[ $# -gt 0 ]]; do
case "$1" in
--all) DEPLOY_ALL=true; shift ;;
--base) BASE_REF="$2"; shift 2 ;;
--dry-run) DRY_RUN=true; shift ;;
-h|--help)
sed -n '2,20p' "$0"
exit 0
;;
*) EXPLICIT_TARGETS+=("$1"); shift ;;
esac
done
# ── 1. 列所有可 deploy 的 worker 目錄 ─────────────────────────────────────────
# 排除 node_modules、wrangler.test.toml、Pages 專案
echo "🔍 Scanning worker directories..."
mapfile -t ALL_DIRS < <(
find . -type f -name 'wrangler.toml' \
-not -path '*/node_modules/*' \
-not -name 'wrangler.test.toml' \
| while read f; do
if grep -q 'pages_build_output_dir' "$f"; then continue; fi
dirname "$f"
done \
| sort -u \
| sed 's|^\./||'
)
echo "Found ${#ALL_DIRS[@]} worker dirs"
# ── 2. 決定 targets ──────────────────────────────────────────────────────────
declare -a TARGETS=()
if [[ ${#EXPLICIT_TARGETS[@]} -gt 0 ]]; then
echo "🎯 Explicit targets: ${EXPLICIT_TARGETS[*]}"
for req in "${EXPLICIT_TARGETS[@]}"; do
matched=false
for d in "${ALL_DIRS[@]}"; do
if [[ "$d" == "$req" || "$(basename "$d")" == "$req" ]]; then
TARGETS+=("$d")
matched=true
fi
done
if [[ "$matched" == false ]]; then
echo "⚠️ '$req' not found in worker list (跳過)"
fi
done
elif [[ "$DEPLOY_ALL" == true ]]; then
echo "🎯 Mode: deploy all"
TARGETS=("${ALL_DIRS[@]}")
else
# git diff 過濾
echo "🎯 Mode: git diff $BASE_REF..HEAD"
if ! git rev-parse "$BASE_REF" >/dev/null 2>&1; then
echo "❌ base ref '$BASE_REF' not found. 改用 --all 或 --base <valid-ref>"
exit 1
fi
mapfile -t CHANGED < <(git diff --name-only "$BASE_REF" HEAD)
echo "Changed files (${#CHANGED[@]}):"
for f in "${CHANGED[@]}"; do echo " $f"; done
for d in "${ALL_DIRS[@]}"; do
hit=0
for f in "${CHANGED[@]}"; do
if [[ "$f" == "$d"/* ]]; then hit=1; break; fi
done
# 連動:改 registry/components/{name}/ 也要 deploy .component-builds/{name}/
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
if [[ ${#TARGETS[@]} -eq 0 ]]; then
echo "✨ Nothing to deploy. All up-to-date."
exit 0
fi
# ── 3. 分 tier (component builds 先,orchestration 後) ────────────────────────
declare -a TIER1=() TIER2=()
for t in "${TARGETS[@]}"; do
if [[ "$t" == .component-builds/* ]]; then
TIER1+=("$t")
else
TIER2+=("$t")
fi
done
echo ""
echo "📦 Deploy plan:"
echo " Tier 1 (components, ${#TIER1[@]}):"
for t in "${TIER1[@]}"; do echo " - $t"; done
echo " Tier 2 (orchestration, ${#TIER2[@]}):"
for t in "${TIER2[@]}"; do echo " - $t"; done
echo ""
if [[ "$DRY_RUN" == true ]]; then
echo "(dry-run,不實際 deploy)"
exit 0
fi
# ── 4. wrangler whoami 健康檢查 ──────────────────────────────────────────────
# 隨便挑一個 worker dir 跑 whoamipnpm 需要在 npm package 內)
SAMPLE_DIR=""
for d in "${TARGETS[@]}"; do
if [[ -f "$d/package.json" ]]; then SAMPLE_DIR="$d"; break; fi
done
if [[ -z "$SAMPLE_DIR" ]]; then
echo "❌ 找不到任何 target 有 package.json (wrangler whoami 需要)"
exit 1
fi
echo "🔑 wrangler whoami:"
(cd "$SAMPLE_DIR" && pnpm exec wrangler whoami 2>&1 | grep -E "logged in|email" | head -2) || {
echo "❌ wrangler 沒登入。先跑:cd $SAMPLE_DIR && pnpm exec wrangler login"
exit 1
}
echo ""
# ── 5. 依序 deploy ──────────────────────────────────────────────────────────
deploy_one() {
local dir="$1"
echo ""
echo "▶ Deploying $dir ..."
pushd "$dir" >/dev/null
# 若是 component build,先確認有 component.wasm (從 registry build 出來)
if [[ "$dir" == .component-builds/* ]]; then
name="${dir#.component-builds/}"
if [[ ! -f "component.wasm" ]] && [[ -d "../../registry/components/$name" ]]; then
echo " ⚙️ rebuild WASM from registry/components/$name ..."
(cd "../../registry/components/$name" && tinygo build -target=wasi -o "$name.wasm" main.go && cp "$name.wasm" "../../../.component-builds/$name/component.wasm") || {
echo " ❌ TinyGo build failed for $name"
popd >/dev/null
return 1
}
fi
fi
# pnpm install 若沒 node_modules(首次)
if [[ ! -d "node_modules" ]] && [[ -f "package.json" ]]; then
echo " 📥 pnpm install (first time)..."
pnpm install --frozen-lockfile 2>&1 | tail -3
fi
if pnpm exec wrangler deploy 2>&1 | tail -4; then
echo " ✅ Done"
else
echo " ❌ Failed"
popd >/dev/null
return 1
fi
popd >/dev/null
}
FAILED=()
for d in "${TIER1[@]}"; do
deploy_one "$d" || FAILED+=("$d")
done
for d in "${TIER2[@]}"; do
deploy_one "$d" || FAILED+=("$d")
done
echo ""
echo "════════════════════════════════════════════════════════════"
if [[ ${#FAILED[@]} -eq 0 ]]; then
echo "✅ All ${#TARGETS[@]} workers deployed successfully"
else
echo "⚠️ ${#FAILED[@]}/${#TARGETS[@]} workers failed:"
for f in "${FAILED[@]}"; do echo " ❌ $f"; done
exit 1
fi