Skip to content

remove do

remove do #2

Workflow file for this run

name: Deploy SPA CF Workers[Worker一体化部署]
on:
push:
branches: [main, master]
paths:
- "apps/ui/**"
- "apps/worker/**"
- "apps/attempt-worker/**"
workflow_dispatch:
inputs:
from_panel:
description: "是否由部署控制面板触发 / triggered from deployment control panel"
required: false
default: "auto"
deploy_action:
description: "部署动作:update/init(默认 update)"
required: false
default: "update"
deploy_target:
description: "部署目标:frontend/backend/both/auto(默认 auto)"
required: false
default: "auto"
apply_migrations:
description: "数据库迁移:true/false/auto(默认 auto,仅迁移文件变更时执行)"
required: false
default: "auto"
repository_dispatch:
types: [deploy-spa-button]
jobs:
check-config:
runs-on: ubuntu-latest
outputs:
should_deploy: ${{ steps.check.outputs.should_deploy }}
deploy_action: ${{ steps.plan.outputs.deploy_action }}
deploy_target: ${{ steps.plan.outputs.deploy_target }}
apply_migrations: ${{ steps.plan.outputs.apply_migrations }}
deploy_ui: ${{ steps.plan.outputs.deploy_ui }}
deploy_worker: ${{ steps.plan.outputs.deploy_worker }}
should_migrate: ${{ steps.plan.outputs.should_migrate }}
ui_changed: ${{ steps.changes.outputs.ui_changed }}
worker_changed: ${{ steps.changes.outputs.worker_changed }}
migrations_changed: ${{ steps.changes.outputs.migrations_changed }}
steps:
- name: 📥 检出代码
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: 🔍 检查部署配置
id: check
run: |
if [[ "${{ github.event_name }}" == "repository_dispatch" ]]; then
echo "should_deploy=true" >> $GITHUB_OUTPUT
echo "✅ 手动触发(repository_dispatch),忽略开关,允许部署"
exit 0
fi
if [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ github.event.inputs.from_panel }}" != "true" ]]; then
echo "should_deploy=true" >> $GITHUB_OUTPUT
echo "✅ 手动触发(workflow_dispatch),忽略开关,允许部署"
exit 0
fi
SPA_ENABLED="${{ vars.SPA_DEPLOY }}"
[ -z "$SPA_ENABLED" ] && SPA_ENABLED="true"
if [ "$SPA_ENABLED" = "true" ]; then
echo "should_deploy=true" >> $GITHUB_OUTPUT
echo "✅ SPA 自动部署已开启,允许部署"
else
echo "should_deploy=false" >> $GITHUB_OUTPUT
echo "⏸️ SPA 自动部署已关闭,跳过部署"
fi
- name: 🔎 检测变更范围
id: changes
run: |
echo "ui_changed=false" >> $GITHUB_OUTPUT
echo "worker_changed=false" >> $GITHUB_OUTPUT
echo "migrations_changed=false" >> $GITHUB_OUTPUT
if [[ "${{ github.event_name }}" == "push" ]]; then
before="${{ github.event.before }}"
if [[ -z "$before" || "$before" == "0000000000000000000000000000000000000000" ]]; then
echo "ui_changed=true" >> $GITHUB_OUTPUT
echo "worker_changed=true" >> $GITHUB_OUTPUT
echo "migrations_changed=true" >> $GITHUB_OUTPUT
exit 0
fi
if ! git cat-file -e "$before^{commit}" 2>/dev/null; then
echo "⚠️ before 提交不存在,默认视为全量变更"
echo "ui_changed=true" >> $GITHUB_OUTPUT
echo "worker_changed=true" >> $GITHUB_OUTPUT
echo "migrations_changed=true" >> $GITHUB_OUTPUT
exit 0
fi
CHANGED=$(git diff --name-only "$before" "${{ github.sha }}")
if echo "$CHANGED" | grep -q "^apps/ui/"; then
echo "ui_changed=true" >> $GITHUB_OUTPUT
fi
if echo "$CHANGED" | grep -Eq "^apps/(worker|attempt-worker)/"; then
echo "worker_changed=true" >> $GITHUB_OUTPUT
fi
if echo "$CHANGED" | grep -q "^apps/worker/migrations/"; then
echo "migrations_changed=true" >> $GITHUB_OUTPUT
fi
fi
- name: 🧭 生成部署计划
id: plan
run: |
event="${{ github.event_name }}"
deploy_action="${{ github.event.inputs.deploy_action }}"
deploy_target="${{ github.event.inputs.deploy_target }}"
apply_migrations="${{ github.event.inputs.apply_migrations }}"
if [[ "$event" == "repository_dispatch" ]]; then
if [[ -n "${{ github.event.client_payload.deploy_target }}" ]]; then
deploy_target="${{ github.event.client_payload.deploy_target }}"
fi
if [[ -n "${{ github.event.client_payload.apply_migrations }}" ]]; then
apply_migrations="${{ github.event.client_payload.apply_migrations }}"
fi
if [[ -n "${{ github.event.client_payload.deploy_action }}" ]]; then
deploy_action="${{ github.event.client_payload.deploy_action }}"
fi
fi
if [[ -z "$deploy_action" ]]; then
deploy_action="update"
fi
if [[ -z "$deploy_target" ]]; then
deploy_target="auto"
fi
if [[ -z "$apply_migrations" ]]; then
apply_migrations="auto"
fi
if [[ "$deploy_action" == "init" ]]; then
deploy_target="both"
apply_migrations="true"
fi
ui_changed="${{ steps.changes.outputs.ui_changed }}"
worker_changed="${{ steps.changes.outputs.worker_changed }}"
migrations_changed="${{ steps.changes.outputs.migrations_changed }}"
deploy_ui="false"
deploy_worker="false"
case "$deploy_target" in
frontend)
deploy_ui="true"
deploy_worker="true"
;;
backend)
deploy_ui="false"
deploy_worker="true"
;;
both)
deploy_ui="true"
deploy_worker="true"
;;
auto|*)
if [[ "$event" == "push" ]]; then
if [[ "$ui_changed" == "true" ]]; then
deploy_ui="true"
fi
if [[ "$worker_changed" == "true" ]]; then
deploy_worker="true"
fi
else
deploy_ui="true"
deploy_worker="true"
fi
;;
esac
should_migrate="false"
if [[ "$apply_migrations" == "true" ]]; then
should_migrate="true"
elif [[ "$apply_migrations" == "auto" ]]; then
if [[ "$event" == "push" && "$migrations_changed" == "true" ]]; then
should_migrate="true"
fi
fi
echo "deploy_action=$deploy_action" >> $GITHUB_OUTPUT
echo "deploy_target=$deploy_target" >> $GITHUB_OUTPUT
echo "apply_migrations=$apply_migrations" >> $GITHUB_OUTPUT
echo "deploy_ui=$deploy_ui" >> $GITHUB_OUTPUT
echo "deploy_worker=$deploy_worker" >> $GITHUB_OUTPUT
echo "should_migrate=$should_migrate" >> $GITHUB_OUTPUT
init-spa:
needs: check-config
if: needs.check-config.outputs.should_deploy == 'true' && needs.check-config.outputs.deploy_action == 'init'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.9"
no-cache: true
- name: Show init plan
run: |
echo "deploy_action=${{ needs.check-config.outputs.deploy_action }}"
echo "deploy_target=${{ needs.check-config.outputs.deploy_target }}"
echo "apply_migrations=${{ needs.check-config.outputs.apply_migrations }}"
echo "deploy_ui=${{ needs.check-config.outputs.deploy_ui }}"
echo "deploy_worker=${{ needs.check-config.outputs.deploy_worker }}"
echo "should_migrate=${{ needs.check-config.outputs.should_migrate }}"
- name: Check if D1 database exists
id: check-db
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
echo "检查 D1 数据库是否已存在..."
DATABASE_LIST=$(bunx wrangler d1 list --json --config apps/worker/wrangler.toml 2>/dev/null || echo "[]")
EXISTING_DB=$(echo "$DATABASE_LIST" | jq -r '.[] | select(.name=="api-worker") | .uuid')
if [ -n "$EXISTING_DB" ]; then
echo "db_exists=true" >> $GITHUB_OUTPUT
echo "✅ D1 已存在,跳过初始化"
else
echo "db_exists=false" >> $GITHUB_OUTPUT
fi
- name: Install dependencies
if: steps.check-db.outputs.db_exists != 'true'
run: bun install
- name: Build UI
if: steps.check-db.outputs.db_exists != 'true' && (needs.check-config.outputs.deploy_ui == 'true' || needs.check-config.outputs.deploy_worker == 'true')
run: bun run --filter api-worker-ui build
- name: Verify UI build
if: steps.check-db.outputs.db_exists != 'true' && (needs.check-config.outputs.deploy_ui == 'true' || needs.check-config.outputs.deploy_worker == 'true')
run: |
if [ ! -d "apps/ui/dist" ]; then
echo "❌ UI 构建失败:dist 目录不存在"
exit 1
fi
if [ ! -f "apps/ui/dist/index.html" ]; then
echo "❌ UI 构建失败:index.html 不存在"
exit 1
fi
echo "✅ UI 构建产物已就绪"
- name: Disable wrangler telemetry
if: steps.check-db.outputs.db_exists != 'true'
run: bunx wrangler telemetry disable
- name: Create D1 database (init only)
if: steps.check-db.outputs.db_exists != 'true'
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
echo "创建 D1 数据库..."
CREATE_OUTPUT=$(bunx wrangler d1 create api-worker --config apps/worker/wrangler.toml 2>&1)
if echo "$CREATE_OUTPUT" | grep -q "database_id"; then
DATABASE_ID=$(echo "$CREATE_OUTPUT" | grep -oP "database_id = \"\K[^\"]+")
if [ -z "$DATABASE_ID" ]; then
DATABASE_ID=$(echo "$CREATE_OUTPUT" | grep -oP "([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})")
fi
else
LIST_OUTPUT=$(bunx wrangler d1 list --json --config apps/worker/wrangler.toml 2>/dev/null || echo "[]")
DATABASE_ID=$(echo "$LIST_OUTPUT" | jq -r '.[] | select(.name=="api-worker") | .uuid')
fi
if [ -z "$DATABASE_ID" ]; then
echo "❌ 无法创建或找到 D1 数据库"
exit 1
fi
sed -i -E "s/(database_id = \")([^\"]+)(\")/\\1$DATABASE_ID\\3/" apps/worker/wrangler.toml
sed -i -E "s/(database_id = \")([^\"]+)(\")/\\1$DATABASE_ID\\3/" apps/attempt-worker/wrangler.toml
echo "database_id=$DATABASE_ID" >> $GITHUB_ENV
- name: Create or verify KV namespace
if: steps.check-db.outputs.db_exists != 'true'
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
echo "检查并创建 KV Namespace..."
KV_NAMESPACE_TITLE="api-worker-hot"
KV_LIST_RAW=$(bunx wrangler kv namespace list --config apps/worker/wrangler.toml 2>/dev/null || true)
KV_ID=$(KV_LIST_RAW="$KV_LIST_RAW" KV_NAMESPACE_TITLE="$KV_NAMESPACE_TITLE" node -e 'const raw=process.env.KV_LIST_RAW||"";const title=(process.env.KV_NAMESPACE_TITLE||"").toLowerCase();const idPattern=/[0-9a-f]{32}/ig;const lines=raw.split(/\r?\n/);let found="";for(const line of lines){if(!line.toLowerCase().includes(title))continue;const match=line.match(idPattern);if(match&&match[0]){found=match[0].toLowerCase();break;}}if(!found&&title){const escapedTitle=title.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");const nearby=new RegExp(`${escapedTitle}[\\s\\S]{0,120}([0-9a-f]{32})`,"i");const m=raw.toLowerCase().match(nearby);if(m&&m[1])found=m[1].toLowerCase();}process.stdout.write(found);')
if [ -z "$KV_ID" ] || [ "$KV_ID" = "null" ]; then
CREATE_OUTPUT=$(bunx wrangler kv namespace create "$KV_NAMESPACE_TITLE" --config apps/worker/wrangler.toml 2>&1)
KV_ID=$(CREATE_OUTPUT="$CREATE_OUTPUT" node -e 'const raw=process.env.CREATE_OUTPUT||"";const exact=raw.match(/^\s*id\s*=\s*"([0-9a-f]{32})"/im);if(exact&&exact[1]){process.stdout.write(exact[1].toLowerCase());process.exit(0);}const any=raw.match(/\b([0-9a-f]{32})\b/i);if(any&&any[1])process.stdout.write(any[1].toLowerCase());')
if [ -z "$KV_ID" ] || [ "$KV_ID" = "null" ]; then
KV_LIST_RAW=$(bunx wrangler kv namespace list --config apps/worker/wrangler.toml 2>/dev/null || true)
KV_ID=$(KV_LIST_RAW="$KV_LIST_RAW" KV_NAMESPACE_TITLE="$KV_NAMESPACE_TITLE" node -e 'const raw=process.env.KV_LIST_RAW||"";const title=(process.env.KV_NAMESPACE_TITLE||"").toLowerCase();const idPattern=/[0-9a-f]{32}/ig;const lines=raw.split(/\r?\n/);let found="";for(const line of lines){if(!line.toLowerCase().includes(title))continue;const match=line.match(idPattern);if(match&&match[0]){found=match[0].toLowerCase();break;}}if(!found&&title){const escapedTitle=title.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");const nearby=new RegExp(`${escapedTitle}[\\s\\S]{0,120}([0-9a-f]{32})`,"i");const m=raw.toLowerCase().match(nearby);if(m&&m[1])found=m[1].toLowerCase();}process.stdout.write(found);')
fi
fi
if [ -z "$KV_ID" ] || [ "$KV_ID" = "null" ]; then
echo "❌ 无法创建或找到 KV Namespace: $KV_NAMESPACE_TITLE(list/create 输出未解析到有效 id)"
exit 1
fi
sed -i -E '/binding = "KV_HOT"/,/^\[\[/{s/(id = ")[^"]+(")/\1'"$KV_ID"'\2/;}' apps/worker/wrangler.toml
sed -i -E '/binding = "KV_HOT"/,/^\[\[/{s/(id = ")[^"]+(")/\1'"$KV_ID"'\2/;}' apps/attempt-worker/wrangler.toml
KV_CONFIG_ID=$(grep -A4 'binding = "KV_HOT"' apps/worker/wrangler.toml | grep -m1 -oP '(?<=id = ")[^"]+')
if [ "$KV_CONFIG_ID" != "$KV_ID" ]; then
echo "❌ KV_HOT 写回失败:期望 $KV_ID,实际 ${KV_CONFIG_ID:-<empty>}"
echo "当前 KV_HOT 配置片段:"
grep -A4 'binding = "KV_HOT"' apps/worker/wrangler.toml || true
exit 1
fi
ATTEMPT_KV_CONFIG_ID=$(grep -A4 'binding = "KV_HOT"' apps/attempt-worker/wrangler.toml | grep -m1 -oP '(?<=id = ")[^"]+')
if [ "$ATTEMPT_KV_CONFIG_ID" != "$KV_ID" ]; then
echo "❌ attempt-worker KV_HOT 写回失败:期望 $KV_ID,实际 ${ATTEMPT_KV_CONFIG_ID:-<empty>}"
echo "当前 attempt-worker KV_HOT 配置片段:"
grep -A4 'binding = "KV_HOT"' apps/attempt-worker/wrangler.toml || true
exit 1
fi
echo "kv_hot_id=$KV_ID" >> $GITHUB_ENV
echo "✅ KV Namespace 就绪: $KV_ID"
- name: Apply D1 migrations (remote)
if: steps.check-db.outputs.db_exists != 'true'
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: bunx wrangler d1 migrations apply DB --remote --config apps/worker/wrangler.toml
- name: Deploy to Cloudflare Workers (SPA Mode)
if: steps.check-db.outputs.db_exists != 'true' && needs.check-config.outputs.deploy_worker == 'true'
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: bunx wrangler deploy --config apps/attempt-worker/wrangler.toml
- name: Deploy to Cloudflare Workers (API Worker)
if: steps.check-db.outputs.db_exists != 'true' && (needs.check-config.outputs.deploy_worker == 'true' || needs.check-config.outputs.deploy_ui == 'true')
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: bunx wrangler deploy --config apps/worker/wrangler.toml
deploy-spa:
needs: check-config
if: needs.check-config.outputs.should_deploy == 'true' && needs.check-config.outputs.deploy_action != 'init'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.9"
no-cache: true
- name: Show deploy plan
run: |
echo "deploy_action=${{ needs.check-config.outputs.deploy_action }}"
echo "deploy_target=${{ needs.check-config.outputs.deploy_target }}"
echo "apply_migrations=${{ needs.check-config.outputs.apply_migrations }}"
echo "ui_changed=${{ needs.check-config.outputs.ui_changed }}"
echo "worker_changed=${{ needs.check-config.outputs.worker_changed }}"
echo "migrations_changed=${{ needs.check-config.outputs.migrations_changed }}"
echo "deploy_ui=${{ needs.check-config.outputs.deploy_ui }}"
echo "deploy_worker=${{ needs.check-config.outputs.deploy_worker }}"
echo "should_migrate=${{ needs.check-config.outputs.should_migrate }}"
- name: Check if deploy button trigger
id: check-deploy-button
run: |
if [[ "${{ github.event_name }}" == "repository_dispatch" && "${{ github.event.action }}" == "deploy-spa-button" ]]; then
echo "is_deploy_button=true" >> $GITHUB_OUTPUT
else
echo "is_deploy_button=false" >> $GITHUB_OUTPUT
fi
- name: Install dependencies
run: bun install
- name: Build UI
if: needs.check-config.outputs.deploy_ui == 'true' || needs.check-config.outputs.deploy_worker == 'true'
run: bun run --filter api-worker-ui build
- name: Verify UI build
if: needs.check-config.outputs.deploy_ui == 'true' || needs.check-config.outputs.deploy_worker == 'true'
run: |
if [ ! -d "apps/ui/dist" ]; then
echo "❌ UI 构建失败:dist 目录不存在"
exit 1
fi
if [ ! -f "apps/ui/dist/index.html" ]; then
echo "❌ UI 构建失败:index.html 不存在"
exit 1
fi
echo "✅ UI 构建产物已就绪"
- name: Disable wrangler telemetry
run: bunx wrangler telemetry disable
- name: Create or verify D1 Database
if: needs.check-config.outputs.deploy_worker == 'true' || needs.check-config.outputs.deploy_ui == 'true' || needs.check-config.outputs.should_migrate == 'true'
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
echo "检查并创建 D1 数据库..."
DATABASE_LIST=$(bunx wrangler d1 list --json --config apps/worker/wrangler.toml 2>/dev/null || echo "[]")
EXISTING_DB=$(echo "$DATABASE_LIST" | jq -r '.[] | select(.name=="api-worker") | .uuid')
if [ -n "$EXISTING_DB" ]; then
echo "✅ 找到现有 D1 数据库: $EXISTING_DB"
DATABASE_ID=$EXISTING_DB
else
CREATE_OUTPUT=$(bunx wrangler d1 create api-worker --config apps/worker/wrangler.toml 2>&1)
if echo "$CREATE_OUTPUT" | grep -q "database_id"; then
DATABASE_ID=$(echo "$CREATE_OUTPUT" | grep -oP "database_id = \"\K[^\"]+")
if [ -z "$DATABASE_ID" ]; then
DATABASE_ID=$(echo "$CREATE_OUTPUT" | grep -oP "([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})")
fi
else
LIST_OUTPUT=$(bunx wrangler d1 list --json --config apps/worker/wrangler.toml 2>/dev/null || echo "[]")
DATABASE_ID=$(echo "$LIST_OUTPUT" | jq -r '.[] | select(.name=="api-worker") | .uuid')
if [ -z "$DATABASE_ID" ]; then
echo "❌ 无法创建或找到 D1 数据库"
exit 1
fi
fi
fi
sed -i -E "s/(database_id = \")([^\"]+)(\")/\\1$DATABASE_ID\\3/" apps/worker/wrangler.toml
sed -i -E "s/(database_id = \")([^\"]+)(\")/\\1$DATABASE_ID\\3/" apps/attempt-worker/wrangler.toml
echo "database_id=$DATABASE_ID" >> $GITHUB_ENV
- name: Create or verify KV namespace
if: needs.check-config.outputs.deploy_worker == 'true' || needs.check-config.outputs.deploy_ui == 'true'
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
echo "检查并创建 KV Namespace..."
KV_NAMESPACE_TITLE="api-worker-hot"
KV_LIST_RAW=$(bunx wrangler kv namespace list --config apps/worker/wrangler.toml 2>/dev/null || true)
KV_ID=$(KV_LIST_RAW="$KV_LIST_RAW" KV_NAMESPACE_TITLE="$KV_NAMESPACE_TITLE" node -e 'const raw=process.env.KV_LIST_RAW||"";const title=(process.env.KV_NAMESPACE_TITLE||"").toLowerCase();const idPattern=/[0-9a-f]{32}/ig;const lines=raw.split(/\r?\n/);let found="";for(const line of lines){if(!line.toLowerCase().includes(title))continue;const match=line.match(idPattern);if(match&&match[0]){found=match[0].toLowerCase();break;}}if(!found&&title){const escapedTitle=title.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");const nearby=new RegExp(`${escapedTitle}[\\s\\S]{0,120}([0-9a-f]{32})`,"i");const m=raw.toLowerCase().match(nearby);if(m&&m[1])found=m[1].toLowerCase();}process.stdout.write(found);')
if [ -z "$KV_ID" ] || [ "$KV_ID" = "null" ]; then
CREATE_OUTPUT=$(bunx wrangler kv namespace create "$KV_NAMESPACE_TITLE" --config apps/worker/wrangler.toml 2>&1)
KV_ID=$(CREATE_OUTPUT="$CREATE_OUTPUT" node -e 'const raw=process.env.CREATE_OUTPUT||"";const exact=raw.match(/^\s*id\s*=\s*"([0-9a-f]{32})"/im);if(exact&&exact[1]){process.stdout.write(exact[1].toLowerCase());process.exit(0);}const any=raw.match(/\b([0-9a-f]{32})\b/i);if(any&&any[1])process.stdout.write(any[1].toLowerCase());')
if [ -z "$KV_ID" ] || [ "$KV_ID" = "null" ]; then
KV_LIST_RAW=$(bunx wrangler kv namespace list --config apps/worker/wrangler.toml 2>/dev/null || true)
KV_ID=$(KV_LIST_RAW="$KV_LIST_RAW" KV_NAMESPACE_TITLE="$KV_NAMESPACE_TITLE" node -e 'const raw=process.env.KV_LIST_RAW||"";const title=(process.env.KV_NAMESPACE_TITLE||"").toLowerCase();const idPattern=/[0-9a-f]{32}/ig;const lines=raw.split(/\r?\n/);let found="";for(const line of lines){if(!line.toLowerCase().includes(title))continue;const match=line.match(idPattern);if(match&&match[0]){found=match[0].toLowerCase();break;}}if(!found&&title){const escapedTitle=title.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");const nearby=new RegExp(`${escapedTitle}[\\s\\S]{0,120}([0-9a-f]{32})`,"i");const m=raw.toLowerCase().match(nearby);if(m&&m[1])found=m[1].toLowerCase();}process.stdout.write(found);')
fi
fi
if [ -z "$KV_ID" ] || [ "$KV_ID" = "null" ]; then
echo "❌ 无法创建或找到 KV Namespace: $KV_NAMESPACE_TITLE(list/create 输出未解析到有效 id)"
exit 1
fi
sed -i -E '/binding = "KV_HOT"/,/^\[\[/{s/(id = ")[^"]+(")/\1'"$KV_ID"'\2/;}' apps/worker/wrangler.toml
sed -i -E '/binding = "KV_HOT"/,/^\[\[/{s/(id = ")[^"]+(")/\1'"$KV_ID"'\2/;}' apps/attempt-worker/wrangler.toml
KV_CONFIG_ID=$(grep -A4 'binding = "KV_HOT"' apps/worker/wrangler.toml | grep -m1 -oP '(?<=id = ")[^"]+')
if [ "$KV_CONFIG_ID" != "$KV_ID" ]; then
echo "❌ KV_HOT 写回失败:期望 $KV_ID,实际 ${KV_CONFIG_ID:-<empty>}"
echo "当前 KV_HOT 配置片段:"
grep -A4 'binding = "KV_HOT"' apps/worker/wrangler.toml || true
exit 1
fi
ATTEMPT_KV_CONFIG_ID=$(grep -A4 'binding = "KV_HOT"' apps/attempt-worker/wrangler.toml | grep -m1 -oP '(?<=id = ")[^"]+')
if [ "$ATTEMPT_KV_CONFIG_ID" != "$KV_ID" ]; then
echo "❌ attempt-worker KV_HOT 写回失败:期望 $KV_ID,实际 ${ATTEMPT_KV_CONFIG_ID:-<empty>}"
echo "当前 attempt-worker KV_HOT 配置片段:"
grep -A4 'binding = "KV_HOT"' apps/attempt-worker/wrangler.toml || true
exit 1
fi
echo "kv_hot_id=$KV_ID" >> $GITHUB_ENV
echo "✅ KV Namespace 就绪: $KV_ID"
- name: Apply D1 migrations (remote)
if: needs.check-config.outputs.should_migrate == 'true'
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: bunx wrangler d1 migrations apply DB --remote --config apps/worker/wrangler.toml
- name: Deploy to Cloudflare Workers (Attempt Worker)
if: needs.check-config.outputs.deploy_worker == 'true'
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: bunx wrangler deploy --config apps/attempt-worker/wrangler.toml
- name: Deploy to Cloudflare Workers (API Worker)
if: needs.check-config.outputs.deploy_worker == 'true' || needs.check-config.outputs.deploy_ui == 'true'
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: bunx wrangler deploy --config apps/worker/wrangler.toml
- name: Display Success Information
if: steps.check-deploy-button.outputs.is_deploy_button == 'true' && success()
run: |
echo "===================================================="
echo "🎉 SPA Worker 已成功部署到 Cloudflare Workers!"
echo "===================================================="
echo "后续步骤:"
echo "1. 访问您的 Worker URL"
echo "2. 登录后立刻修改管理员密码"
echo "3. 配置相关系统参数"
echo "===================================================="
- name: Notify deployment status
if: always()
run: |
if [ "${{ job.status }}" == "success" ]; then
echo "✅ SPA Worker 部署成功!"
else
echo "❌ SPA Worker 部署失败!"
fi