remove do #2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | |