Files
Arcrun/scripts/local-deploy.sh
T
uncle6me-web 3e65e22775 feat: 薄殼原則落地 + seed 下沉 API + MCP 進主庫 + 部署一致性
壓測四橫向問題修正(docs 壓測報告):

① 薄殼原則成鐵律:能力長在 API,CLI/MCP/lib 只暴露
   - seed 下沉成 API 行為:cypher-executor POST /init/seed(一次灌 API+auth recipe),
     種子資料移到 server src/lib/api-recipe-seeds.ts,CLI 改薄殼一次呼叫
   - 解除 deployFullyOk 連坐 + init 補 seed auth recipe + update 補 seed/全 KV
   - registry SUBMISSIONS_KV 補進 REQUIRED_KV_NAMESPACES(修 20/21)

② MCP 統一帳號來源(單一 remote MCP + .env 切 MCP URL)
   - MCP 從 sibling repo 搬進 arcrun/mcp/(remote Worker,route 改 mcp.arcrun.dev)
   - config 加 mcp_url 三層解析 + getMcpUrl + DEFAULT_MCP_URL
   - 新增 acr mcp-setup:依 config 寫專案 .mcp.json(接案切資料夾自動切 MCP)
   - acr --version 改動態讀 package.json(根治漂移)

③ Deploy 一致性
   - tests/release.feature + scripts/check-release.sh
   - local-deploy.sh:CLI npm publish + auto patch bump + CHANGELOG
   - local-deploy.sh bash 3.2 相容修正(mapfile / 空陣列 set -u)
   - builtins/pnpm-lock.yaml

④ README self-hosted 同步現況(移除 R2 殘留、加 flag/env、多帳號)

CLI bump → 1.3.0

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 15:45:35 +08:00

289 lines
10 KiB
Bash
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.
#!/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..."
# bash 3.2macOS 內建)沒有 mapfile → 用 while read 相容寫法
ALL_DIRS=()
while IFS= read -r d; do
[ -n "$d" ] && ALL_DIRS+=("$d")
done < <(
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
CHANGED=()
while IFS= read -r f; do
[ -n "$f" ] && CHANGED+=("$f")
done < <(git diff --name-only "$BASE_REF" HEAD)
echo "Changed files (${#CHANGED[@]}):"
for f in ${CHANGED[@]+"${CHANGED[@]}"}; do echo " $f"; done
for d in "${ALL_DIRS[@]}"; do
hit=0
for f in ${CHANGED[@]+"${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[@]+"${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[@]+"${TIER1[@]}"}; do echo " - $t"; done
echo " Tier 2 (orchestration, ${#TIER2[@]}):"
for t in ${TIER2[@]+"${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[@]+"${TIER1[@]}"}; do
deploy_one "$d" || FAILED+=("$d")
done
for d in ${TIER2[@]+"${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[@]+"${FAILED[@]}"}; do echo " ❌ $f"; done
fi
# ── 6. CLI npm publish(壓測報告第 3 點:deploy 不只推 workernpm CLI 也要同步)──
# 「推送 = 全部 publish target 到位」(tests/release.feature)。worker 走 wrangler
# CLI 走 npm。只有 cli/ 在本次 diff 內 + 版本比 npm 新時才 publish(同版跳過,不假失敗)。
# 不在 --dry-run 時跑。需 npm loginnpm whoami 確認)。
if [[ "${DRY_RUN:-false}" != "true" ]]; then
CLI_CHANGED=false
for t in "${TARGETS[@]:-}"; do [[ "$t" == "cli" || "$t" == "cli/" ]] && CLI_CHANGED=true; done
# --all 或 diff 含 cli/ 都算
if git diff --name-only "${BASE_REF}"..HEAD 2>/dev/null | grep -q '^cli/'; then CLI_CHANGED=true; fi
if [[ "${DEPLOY_ALL:-false}" == "true" ]]; then CLI_CHANGED=true; fi
if [[ "$CLI_CHANGED" == "true" ]]; then
echo ""
echo "▶ CLI npm publish ..."
LOCAL_V=$(node -p "require('./cli/package.json').version" 2>/dev/null || echo "?")
REMOTE_V=$(npm view arcrun version 2>/dev/null || echo "none")
# 自動昇版(richblackdeploy 時自動 bump,避免忘了改):
# 若本機版本 == npm 上版本(= 改了 cli 但沒 bump)→ 自動 patch +1。
# 留版本記錄:把本次 commit subject 寫進 cli/CHANGELOG.md。
if [[ "$LOCAL_V" == "$REMOTE_V" ]]; then
echo " · 版本未 bump$LOCAL_V 已在 npm),自動 patch +1 ..."
NEW_V=$(cd cli && npm version patch --no-git-tag-version 2>/dev/null | tr -d 'v')
LOCAL_V="$NEW_V"
# CHANGELOGprepend 新版本 + 本次 commit subject(無 commit 則標 manual deploy
COMMIT_SUBJ=$(git log -1 --format='%s' 2>/dev/null || echo 'manual deploy')
COMMIT_DATE=$(git log -1 --format='%ad' --date=short 2>/dev/null || echo '')
CHANGELOG="cli/CHANGELOG.md"
TMP_CL=$(mktemp)
{
echo "# arcrun CLI Changelog"
echo ""
echo "## $NEW_V$COMMIT_DATE"
echo "- $COMMIT_SUBJ"
echo ""
if [[ -f "$CHANGELOG" ]]; then tail -n +2 "$CHANGELOG"; fi
} > "$TMP_CL"
mv "$TMP_CL" "$CHANGELOG"
echo " · 已 bump → $NEW_V,並記錄進 $CHANGELOG(記得 commit 這兩個檔)"
fi
if ! npm whoami >/dev/null 2>&1; then
echo " ⚠ 未 npm loginnpm whoami 失敗),跳過 publish。手動:cd cli && npm publish"
FAILED+=("cli:npm-publish(未登入)")
else
echo " 📦 publish arcrun $REMOTE_V$LOCAL_V ..."
if (cd cli && npm run build >/dev/null 2>&1 && npm publish --access public 2>&1 | tail -3); then
echo " ✅ npm publish 完成(arcrun@$LOCAL_V"
else
echo " ❌ npm publish 失敗"
FAILED+=("cli:npm-publish")
fi
fi
fi
fi
echo ""
echo "════════════════════════════════════════════════════════════"
if [[ ${#FAILED[@]} -eq 0 ]]; then
echo "✅ 全部 publish target 到位(worker + 必要時 CLI npm"
else
echo "⚠️ ${#FAILED[@]} 項失敗(誠實回報,未假綠):"
for f in ${FAILED[@]+"${FAILED[@]}"}; do echo " ❌ $f"; done
exit 1
fi