feat(cloudflare): scaffold Response Store Wrangler config #12999
Workflow file for this run
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 Examples | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| types: [opened, synchronize, reopened, closed] | |
| concurrency: | |
| group: deploy-${{ github.event_name }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: {} | |
| jobs: | |
| response_store: | |
| # Skip entirely for fork PRs — they don't have access to deploy secrets. | |
| if: github.event_name == 'push' || (github.event.action != 'closed' && github.event.pull_request.head.repo.full_name == github.repository) | |
| name: Deploy and test Workers Response Store | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| deployments: write | |
| outputs: | |
| service_name: ${{ steps.names.outputs.service_name }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - uses: ./.github/actions/setup | |
| - name: Build Workers Response Store | |
| run: vp run @cloudflare/workers-response-store#build | |
| - name: Select per-run Worker names | |
| id: names | |
| shell: bash | |
| run: | | |
| if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then | |
| prefix="pr-${{ github.event.pull_request.number }}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" | |
| service_name="${prefix}-rs-service" | |
| else | |
| prefix="main" | |
| service_name="vinext-workers-response-store-service-poc" | |
| fi | |
| { | |
| echo "service_name=$service_name" | |
| echo "client_name=${prefix}-rs-client" | |
| echo "inline_name=${prefix}-rs-inline" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Configure live client Worker | |
| working-directory: packages/workers-response-store | |
| env: | |
| CLIENT_NAME: ${{ steps.names.outputs.client_name }} | |
| SERVICE_NAME: ${{ steps.names.outputs.service_name }} | |
| run: | | |
| # Node reads the values from process.env. | |
| # shellcheck disable=SC2016 | |
| node -e ' | |
| const fs = require("node:fs"); | |
| const source = fs.readFileSync("example/service-binding/wrangler.user.jsonc", "utf8"); | |
| const config = source | |
| .replace(/"name": "[^"]+"/, `"name": "${process.env.CLIENT_NAME}"`) | |
| .replace(/"main": "user-worker\.ts"/, `"main": "example/service-binding/user-worker.ts"`) | |
| .replace(/"service": "[^"]+"/, `"service": "${process.env.SERVICE_NAME}"`); | |
| fs.writeFileSync(".wrangler-e2e.user.jsonc", config); | |
| ' | |
| - name: Deploy live Response Store Workers | |
| working-directory: packages/workers-response-store | |
| env: | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| SERVICE_NAME: ${{ steps.names.outputs.service_name }} | |
| INLINE_NAME: ${{ steps.names.outputs.inline_name }} | |
| run: | | |
| node "$GITHUB_WORKSPACE/.github/scripts/wrangler-deploy.mjs" --config example/service-binding/wrangler.cache.jsonc --name "$SERVICE_NAME" | |
| node "$GITHUB_WORKSPACE/.github/scripts/wrangler-deploy.mjs" --config .wrangler-e2e.user.jsonc | |
| node "$GITHUB_WORKSPACE/.github/scripts/wrangler-deploy.mjs" --config wrangler.jsonc --name "$INLINE_NAME" | |
| - name: Run live Response Store E2E | |
| working-directory: packages/workers-response-store | |
| env: | |
| LIVE_CACHE_BASE: https://${{ steps.names.outputs.inline_name }}.vinext.workers.dev | |
| LIVE_RESPONSE_STORE_SERVICE_BASE: https://${{ steps.names.outputs.client_name }}.vinext.workers.dev | |
| run: | | |
| for url in "$LIVE_CACHE_BASE" "$LIVE_RESPONSE_STORE_SERVICE_BASE"; do | |
| ready=false | |
| consecutive=0 | |
| for _ in {1..60}; do | |
| if node -e 'fetch(process.argv[1]).then(async response => process.exit(response.ok && (await response.text()).includes("\"status\":\"ready\"") ? 0 : 1)).catch(() => process.exit(1))' "$url"; then | |
| consecutive=$((consecutive + 1)) | |
| if [[ "$consecutive" -eq 5 ]]; then | |
| ready=true | |
| break | |
| fi | |
| else | |
| consecutive=0 | |
| fi | |
| sleep 2 | |
| done | |
| if [[ "$ready" != true ]]; then | |
| echo "$url did not become ready" | |
| exit 1 | |
| fi | |
| done | |
| vp run test:live | |
| - name: Clean up live Response Store Workers | |
| if: always() && github.event_name == 'pull_request' | |
| continue-on-error: true | |
| working-directory: packages/workers-response-store | |
| env: | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| CLIENT_NAME: ${{ steps.names.outputs.client_name }} | |
| INLINE_NAME: ${{ steps.names.outputs.inline_name }} | |
| run: | | |
| set +e | |
| for worker in "$CLIENT_NAME" "$INLINE_NAME"; do | |
| vp exec wrangler delete "$worker" --force | |
| done | |
| deploy: | |
| # Skip entirely for fork PRs — they don't have access to deploy secrets. | |
| if: github.event_name == 'push' || (github.event.action != 'closed' && github.event.pull_request.head.repo.full_name == github.repository) | |
| name: Deploy ${{ matrix.example.name }} | |
| needs: response_store | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| deployments: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| example: | |
| - name: app-router-cloudflare | |
| project: app-router-cloudflare | |
| working_directory: examples/app-router-cloudflare | |
| wrangler_config: dist/server/wrangler.json | |
| - name: pages-router-cloudflare | |
| project: pages-router-cloudflare | |
| working_directory: examples/pages-router-cloudflare | |
| wrangler_config: dist/pages_router_cloudflare/wrangler.json | |
| - name: app-router-playground | |
| project: app-router-playground | |
| working_directory: examples/app-router-playground | |
| wrangler_config: dist/server/wrangler.json | |
| - name: realworld-api-rest | |
| project: realworld-api-rest | |
| working_directory: examples/realworld-api-rest | |
| wrangler_config: dist/realworld_api_rest/wrangler.json | |
| - name: nextra-docs-template | |
| project: nextra-docs-template | |
| working_directory: examples/nextra-docs-template | |
| wrangler_config: dist/server/wrangler.json | |
| - name: benchmarks | |
| project: benchmarks | |
| working_directory: examples/benchmarks | |
| wrangler_config: dist/server/wrangler.json | |
| - name: hackernews | |
| project: hackernews | |
| working_directory: examples/hackernews | |
| wrangler_config: dist/server/wrangler.json | |
| - name: response-store-demo | |
| project: response-store-demo | |
| working_directory: examples/response-store-demo | |
| wrangler_config: dist/server/wrangler.json | |
| warm_cdn_cache: true | |
| - name: static-export | |
| project: static-export | |
| working_directory: examples/static-export | |
| wrangler_config: deploy/wrangler.jsonc | |
| static_export: true | |
| - name: web | |
| project: vinext-web | |
| working_directory: apps/web | |
| wrangler_config: dist/server/wrangler.json | |
| warm_cdn_cache: true | |
| warm_cdn_target: https://vinext.dev | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - uses: ./.github/actions/setup | |
| - name: Build vinext plugin | |
| run: vp run build | |
| - name: Configure Response Store demo preview | |
| if: matrix.example.name == 'response-store-demo' && github.event_name == 'pull_request' | |
| working-directory: ${{ matrix.example.working_directory }} | |
| env: | |
| SERVICE_NAME: pr-${{ github.event.pull_request.number }}-response-store-demo-response-store | |
| run: | | |
| # Node reads the value from process.env. | |
| # shellcheck disable=SC2016 | |
| node -e ' | |
| const fs = require("node:fs"); | |
| const appPath = "wrangler.jsonc"; | |
| const servicePath = "wrangler.response-store.jsonc"; | |
| const app = fs.readFileSync(appPath, "utf8").replace( | |
| `"service": "response-store-demo-response-store"`, | |
| `"service": "${process.env.SERVICE_NAME}"`, | |
| ); | |
| const service = fs.readFileSync(servicePath, "utf8").replace( | |
| `"name": "response-store-demo-response-store"`, | |
| `"name": "${process.env.SERVICE_NAME}"`, | |
| ); | |
| if (!app.includes(process.env.SERVICE_NAME) || !service.includes(process.env.SERVICE_NAME)) { | |
| throw new Error("Could not configure the Response Store preview Worker name"); | |
| } | |
| fs.writeFileSync(appPath, app); | |
| fs.writeFileSync(servicePath, service); | |
| ' | |
| - name: Deploy configured Response Store service | |
| if: matrix.example.name == 'response-store-demo' || (matrix.example.name == 'web' && github.event_name == 'push' && github.ref == 'refs/heads/main') | |
| working-directory: ${{ matrix.example.working_directory }} | |
| env: | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| run: node "$GITHUB_WORKSPACE/.github/scripts/wrangler-deploy.mjs" --config wrangler.response-store.jsonc | |
| - name: Build example | |
| if: matrix.example.static_export != true | |
| run: vp build | |
| working-directory: ${{ matrix.example.working_directory }} | |
| - name: Build static export | |
| if: matrix.example.static_export == true | |
| run: vp exec vinext build | |
| working-directory: ${{ matrix.example.working_directory }} | |
| - name: Verify static export artifact | |
| if: matrix.example.static_export == true | |
| run: node scripts/verify-output.mjs | |
| working-directory: ${{ matrix.example.working_directory }} | |
| - name: Apply D1 migrations (benchmarks only) | |
| if: matrix.example.name == 'benchmarks' && github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4.0.0 | |
| with: | |
| apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| workingDirectory: examples/benchmarks | |
| command: d1 migrations apply vinext-benchmarks --remote | |
| - name: Apply D1 migrations (web only) | |
| if: matrix.example.name == 'web' && github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4.0.0 | |
| with: | |
| apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| workingDirectory: apps/web | |
| command: d1 migrations apply vinext-metrics --remote | |
| - name: Deploy to Cloudflare Workers (Production) | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.example.warm_cdn_cache != true | |
| uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4.0.0 | |
| with: | |
| apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| workingDirectory: ${{ matrix.example.working_directory }} | |
| command: deploy --config ${{ matrix.example.wrangler_config }} | |
| - name: Deploy to Cloudflare Workers with CDN warmup (Production) | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.example.warm_cdn_cache == true | |
| working-directory: ${{ matrix.example.working_directory }} | |
| env: | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| WARM_CDN_TARGET: ${{ matrix.example.warm_cdn_target }} | |
| run: | | |
| args=(--skip-build --config "${{ matrix.example.wrangler_config }}" --experimental-warm-cdn-cache) | |
| if [[ -n "$WARM_CDN_TARGET" ]]; then | |
| args+=(--warm-cdn-target "$WARM_CDN_TARGET") | |
| fi | |
| vp exec vinext-cloudflare deploy "${args[@]}" | |
| - name: Deploy Preview Version | |
| if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository | |
| uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4.0.0 | |
| with: | |
| apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| workingDirectory: ${{ matrix.example.working_directory }} | |
| command: versions upload --config ${{ matrix.example.wrangler_config }} --preview-alias pr-${{ github.event.pull_request.number }} | |
| # `versions upload` does not apply non-versioned workers.dev/preview URL | |
| # settings. A newly added assets-only Worker has no routes until this runs. | |
| - name: Apply Static Export Preview Routes | |
| if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && matrix.example.static_export == true | |
| uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4.0.0 | |
| with: | |
| apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| workingDirectory: ${{ matrix.example.working_directory }} | |
| command: triggers deploy --config ${{ matrix.example.wrangler_config }} | |
| smoke-test: | |
| name: Smoke Test Deployments | |
| runs-on: ubuntu-latest | |
| needs: deploy | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Smoke test (production) | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| run: ./scripts/smoke-test.sh | |
| - name: Smoke test (preview) | |
| if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository | |
| run: ./scripts/smoke-test.sh --preview pr-${{ github.event.pull_request.number }} | |
| deployed-e2e: | |
| name: E2E Deployed Example | |
| runs-on: ubuntu-latest | |
| needs: deploy | |
| if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - uses: ./.github/actions/setup | |
| - name: Cache Playwright browsers | |
| id: playwright-cache | |
| uses: ./.github/actions/playwright-cache | |
| with: | |
| browser: chromium | |
| mode: cache | |
| restore-keys: | | |
| playwright-chromium-${{ runner.os }}- | |
| - name: Install Playwright Chromium | |
| if: steps.playwright-cache.outputs.cache-hit != 'true' | |
| run: vp exec playwright install chromium | |
| - name: Test deployed Web Worker | |
| env: | |
| PLAYWRIGHT_PROJECT: cloudflare-workers | |
| VINEXT_E2E_BASE_URL: ${{ github.event_name == 'pull_request' && format('https://pr-{0}-app-router-cloudflare.vinext.workers.dev', github.event.pull_request.number) || 'https://app-router-cloudflare.vinext.workers.dev' }} | |
| run: >- | |
| vp exec playwright test | |
| tests/e2e/cloudflare-workers/web-worker.spec.ts | |
| tests/e2e/cloudflare-workers/optimistic-search-navigation.spec.ts | |
| cleanup-response-store: | |
| name: Clean up Workers Response Store services | |
| if: always() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository | |
| needs: [response_store, deploy, smoke-test, deployed-e2e] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - uses: ./.github/actions/setup | |
| - name: Delete cache Worker | |
| continue-on-error: true | |
| working-directory: packages/workers-response-store | |
| env: | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| SERVICE_NAME: ${{ needs.response_store.outputs.service_name }} | |
| DEMO_SERVICE_NAME: pr-${{ github.event.pull_request.number }}-response-store-demo-response-store | |
| PR_ACTION: ${{ github.event.action }} | |
| run: | | |
| if [[ "$PR_ACTION" == "closed" ]]; then | |
| vp exec wrangler delete "$DEMO_SERVICE_NAME" --force | |
| elif [[ -n "$SERVICE_NAME" ]]; then | |
| vp exec wrangler delete "$SERVICE_NAME" --force | |
| fi | |
| comment: | |
| name: Comment Preview URLs | |
| runs-on: ubuntu-latest | |
| needs: deploy | |
| if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository | |
| permissions: | |
| pull-requests: write | |
| steps: | |
| - name: Comment PR with preview URLs | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: | | |
| const prNumber = context.issue.number; | |
| const alias = `pr-${prNumber}`; | |
| const examples = [ | |
| { name: 'app-router-cloudflare', project: 'app-router-cloudflare' }, | |
| { name: 'pages-router-cloudflare', project: 'pages-router-cloudflare' }, | |
| { name: 'app-router-playground', project: 'app-router-playground', original: 'https://app-router.vercel.app' }, | |
| { name: 'realworld-api-rest', project: 'realworld-api-rest' }, | |
| { name: 'nextra-docs-template', project: 'nextra-docs-template' }, | |
| { name: 'benchmarks', project: 'benchmarks' }, | |
| { name: 'hackernews', project: 'hackernews', original: 'https://next-react-server-components.vercel.app' }, | |
| { name: 'response-store-demo', project: 'response-store-demo' }, | |
| { name: 'static-export', project: 'static-export' }, | |
| { name: 'web', project: 'vinext-web', production: 'https://vinext.dev' }, | |
| ]; | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| }); | |
| const marker = '<!-- vinext-deploy-previews -->'; | |
| const existingComment = comments.find(c => c.body?.includes(marker)); | |
| const rows = examples | |
| .map(e => { | |
| const orig = e.original ? `[original](${e.original})` : ''; | |
| const production = e.production || `https://${e.project}.vinext.workers.dev`; | |
| return `| ${e.name} | [preview](https://${alias}-${e.project}.vinext.workers.dev) | [production](${production}) | ${orig} |`; | |
| }) | |
| .join('\n'); | |
| const body = [ | |
| marker, | |
| '| Example | Preview | Production | Original |', | |
| '|---------|---------|------------|----------|', | |
| rows, | |
| ].join('\n'); | |
| if (existingComment) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existingComment.id, | |
| body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| body, | |
| }); | |
| } |