Files
Arcrun/scripts/local-deploy.sh
uncle6me-web 558e80b4da chore(wiki): wiki-init 補骨架 + system-dev-template 安裝/更新腳本
wiki 已初始化過(push 檔活躍維護),本次補從沒建的 pull 層 + arcrun 化範本:
- cards/decisions/ 14 張決策原子卡(含 gloss/實體/typed-edge 三元組):
  從 decisions-summary 全量改寫 13 + 新增「薄殼規則晚於實作-MCP漂移是歷史債」1
- TAXONOMY 從 PKM 範本換成 arcrun 軸(子系統 零件架構/cypher/credential/recipe/kbdb/
  薄殼/部署/平台原則 + 形態 架構決策/踩坑/機制說明/禁令/案例經驗)
- principles 填 13 條跨全局原則(從 rules/ + mindset 蒸餾)
- INDEX 真實視圖(子系統角度 + 決策角度,指向 cards)
- system-dev/scripts/ + scripts/ install/update 安裝腳本(template 接入)

純基建/文檔,無業務 code(功能 code 見前一 commit)。
raw source(docs/)0 異動、wiki 卡際連結無斷鏈。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 17:53:37 +08:00

318 lines
12 KiB
Bash
Executable File
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..."
# 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
# ── 3.5 Git 同步前置閘(壓測階段 6 教訓:deploy-from-committed)────────────────
# self-hosted `acr init` 從 origin/main codeload 抓 worker 源。若本機領先 main 未 push,
# 部署 prod 用的是新碼但 self-hosted 用戶抓到舊碼 → 薄殼打不存在的 API(seed 404)。
# ∴ 實際 deploy 前強制 git 同步檢查;未過用 --skip-git-check 可強制略過(自負風險)。
if [[ "${SKIP_GIT_CHECK:-false}" != "true" ]]; then
echo ""
echo "🔎 Git 同步前置檢查(deploy 前必過;要略過設 SKIP_GIT_CHECK=true..."
if ! bash scripts/check-release.sh >/tmp/arcrun-release-check.txt 2>&1; then
grep -E "✗|⚠|領先|未 commit|Git 未同步" /tmp/arcrun-release-check.txt | sed 's/^/ /'
echo "❌ Git 未同步 → 先 commit + git push 再 deploy(見 RELEASE-CHECKLIST.md)。"
echo " (確定要在未 push 狀態 deploy SKIP_GIT_CHECK=true bash scripts/local-deploy.sh ..."
exit 1
fi
echo " ✓ Git 已同步,繼續 deploy"
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
# 優先用 .env 的 NPM_API_TOKENauthToken)——互動 npm login 常因 publish 政策 403。
# 無 token 才退回 npm whoami(互動登入)。token 不入 log。
NPM_TOK=""
if [[ -f .env ]]; then
NPM_TOK=$(grep '^NPM_API_TOKEN=' .env 2>/dev/null | head -1 | cut -d= -f2- | tr -d '"'"'"' \r\n')
fi
PUB_RC=""
if [[ -n "$NPM_TOK" ]]; then
PUB_RC=$(mktemp)
printf '//registry.npmjs.org/:_authToken=%s\n' "$NPM_TOK" > "$PUB_RC"
fi
if [[ -z "$NPM_TOK" ]] && ! npm whoami >/dev/null 2>&1; then
echo " ⚠ 無 .env NPM_API_TOKEN 且未 npm login,跳過 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_CONFIG_USERCONFIG="${PUB_RC:-$HOME/.npmrc}" 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
[[ -n "$PUB_RC" ]] && rm -f "$PUB_RC"
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