arcrun — AI workflow execution engine (clean history)
Self-hosted 開源:WASM 零件 + recipe + cypher-executor,跑在你自己的 Cloudflare。 此為重建的乾淨歷史起點(移除曾誤 commit 的 GCP SA 金鑰,舊歷史保留在 richblack/arcrun 與本地 backup 分支)。含: - acr init --self-hosted installer(建 KV/R2 + codeload 拉預編譯 wasm + wrangler deploy + seed recipe) - recipe push 把關(資料外流提醒 + 打通檢查) - 19 個正當零件預編譯 wasm(claude_api/km_writer/kbdb_upsert_block 排除:違反 DECISIONS §1) - CLI / cypher-executor / registry / 完整 SDD Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
#!/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 diff,deploy 改到的 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 login(pnpm 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 跑 whoami(pnpm 需要在 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
|
||||
Reference in New Issue
Block a user