From a8eca3e2a1f35b7ce1a8a3dc6c851642b01bac3e Mon Sep 17 00:00:00 2001 From: Pierre Bertet Date: Tue, 20 Jan 2026 19:47:12 +0000 Subject: [PATCH 1/8] refactor: consolidate deploy workflows into single workflow with shared action - Merge deploy-preview-moderato and deploy-preview-presto into single deploy-preview workflow - Merge deploy-moderato and deploy-presto into single deploy workflow - Create reusable deploy action for both preview and prod - Both envs now deploy in parallel within same workflow - Fix PR comment race condition (was showing only one env) - Improve deployment table format with commit hash and relative time Co-Authored-By: Claude Opus 4.5 --- .github/actions/deploy/action.yml | 56 +++++++++ .../actions/post-deployment-table/action.yml | 118 ----------------- .github/workflows/deploy-moderato.yml | 48 ------- .github/workflows/deploy-presto.yml | 48 ------- .github/workflows/deploy-preview-moderato.yml | 110 ---------------- .github/workflows/deploy-preview-presto.yml | 110 ---------------- .github/workflows/deploy-preview.yml | 119 ++++++++++++++++++ .github/workflows/deploy.yml | 51 ++++++++ 8 files changed, 226 insertions(+), 434 deletions(-) create mode 100644 .github/actions/deploy/action.yml delete mode 100644 .github/actions/post-deployment-table/action.yml delete mode 100644 .github/workflows/deploy-moderato.yml delete mode 100644 .github/workflows/deploy-presto.yml delete mode 100644 .github/workflows/deploy-preview-moderato.yml delete mode 100644 .github/workflows/deploy-preview-presto.yml create mode 100644 .github/workflows/deploy-preview.yml create mode 100644 .github/workflows/deploy.yml diff --git a/.github/actions/deploy/action.yml b/.github/actions/deploy/action.yml new file mode 100644 index 0000000..7e07f85 --- /dev/null +++ b/.github/actions/deploy/action.yml @@ -0,0 +1,56 @@ +name: 'Deploy' +description: 'Build and deploy to Cloudflare Workers' + +inputs: + environment: + description: 'Cloudflare environment (e.g., presto, moderato)' + required: true + command: + description: 'Wrangler command (e.g., "versions upload" or "deploy")' + required: false + default: 'versions upload' + cloudflare-api-token: + description: 'Cloudflare API token' + required: true + cloudflare-account-id: + description: 'Cloudflare account ID' + required: true + +outputs: + status: + description: 'Deployment status (success or failed)' + value: ${{ steps.result.outputs.status }} + url: + description: 'Deployment URL' + value: ${{ steps.result.outputs.url }} + +runs: + using: 'composite' + steps: + - name: Build + shell: bash + env: + NODE_ENV: production + CLOUDFLARE_ENV: ${{ inputs.environment }} + run: pnpm build + + - name: Deploy + id: deploy + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ inputs.cloudflare-api-token }} + accountId: ${{ inputs.cloudflare-account-id }} + environment: ${{ inputs.environment }} + command: ${{ inputs.command }} + + - name: Set outputs + id: result + shell: bash + run: | + if [[ "${{ steps.deploy.outcome }}" == "success" ]]; then + echo "status=success" >> $GITHUB_OUTPUT + echo "url=${{ steps.deploy.outputs.deployment-url }}" >> $GITHUB_OUTPUT + else + echo "status=failed" >> $GITHUB_OUTPUT + echo "url=" >> $GITHUB_OUTPUT + fi diff --git a/.github/actions/post-deployment-table/action.yml b/.github/actions/post-deployment-table/action.yml deleted file mode 100644 index b5bf4d2..0000000 --- a/.github/actions/post-deployment-table/action.yml +++ /dev/null @@ -1,118 +0,0 @@ -name: 'Cloudflare Post Deployment' -description: 'Posts a consolidated table of all Cloudflare deployments to a PR' - -inputs: - github-token: - description: 'GitHub token for posting comments' - required: true - deployments-path: - description: 'Path to directory containing deployment JSON files' - required: true - -runs: - using: 'composite' - steps: - - name: Generate and Post Deployment Table - uses: actions/github-script@v8 - env: - DEPLOYMENTS_PATH: ${{ inputs.deployments-path }} - with: - github-token: ${{ inputs.github-token }} - script: | - const fs = require('node:fs') - const path = require('node:path') - - const deploymentsPath = process.env.DEPLOYMENTS_PATH - const deployments = [] - - try { - const files = fs.readdirSync(deploymentsPath) - for (const file of files) { - if (file.endsWith('.json')) { - try { - const content = fs.readFileSync(path.join(deploymentsPath, file), 'utf8') - const deployment = JSON.parse(content) - deployments.push(deployment) - } catch (parseError) { - console.log(`Failed to parse ${file}:`, parseError.message) - } - } - } - } catch (error) { - console.log('No deployment files found or error reading them:', error.message) - } - - deployments.sort((a, b) => { - const appA = a.app || '' - const appB = b.app || '' - if (appA !== appB) return appA.localeCompare(appB) - const envA = a.env || '' - const envB = b.env || '' - return envA.localeCompare(envB) - }) - - const statusEmoji = { - 'success': '[OK]', - 'skipped': '[>>]', - 'failed': '[X]' - } - - const statusText = { - 'success': 'Deployed', - 'skipped': 'Skipped', - 'failed': 'Failed' - } - - let tableRows = [] - for (const deployment of deployments) { - const app = deployment.app - const env = deployment.env || '-' - const status = `${statusEmoji[deployment.status] || '[?]'} ${statusText[deployment.status] || 'Unknown'}` - - let preview = '-' - if (deployment.status === 'success') { - const url = deployment.deployment_alias_url || deployment.deployment_url - if (url) { - preview = `[View Preview](${url})` - } - } else if (deployment.status === 'skipped') { - preview = 'No changes' - } - - tableRows.push(`| ${app} | ${env} | ${status} | ${preview} |`) - } - - let commentBody = '\n' - commentBody += '## Cloudflare Deployments\n\n' - commentBody += '| App | Environment | Status | Preview |\n' - commentBody += '|-----|-------------|--------|---------|\n' - commentBody += tableRows.join('\n') + '\n\n' - commentBody += deployments.length === 0 ? '_No deployments found._' : '' - - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - }) - - const existingComment = comments.find(comment => - comment.body.includes('') - ) - - if (existingComment) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existingComment.id, - body: commentBody - }) - console.log('Updated existing deployment comment') - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: commentBody - }) - console.log('Created new deployment comment') - } diff --git a/.github/workflows/deploy-moderato.yml b/.github/workflows/deploy-moderato.yml deleted file mode 100644 index 7338935..0000000 --- a/.github/workflows/deploy-moderato.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Deploy (Moderato) -on: - workflow_dispatch: - push: - branches: [main] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - NODE_ENV: production - WRANGLER_SEND_METRICS: false - -jobs: - verify: - name: Verify - uses: ./.github/workflows/verify.yml - secrets: inherit - - deploy: - name: Deploy (Moderato) - runs-on: ubuntu-latest - needs: verify - timeout-minutes: 60 - steps: - - name: Clone repository - uses: actions/checkout@v6 - - - name: Install dependencies - uses: ./.github/actions/install-dependencies - - - name: Build - env: - NODE_ENV: production - CLOUDFLARE_ENV: moderato - run: pnpm build - - - name: Deploy Worker - uses: cloudflare/wrangler-action@v3 - env: - NODE_ENV: production - CLOUDFLARE_ENV: moderato - with: - apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} - accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - packageManager: pnpm - command: deploy --env moderato diff --git a/.github/workflows/deploy-presto.yml b/.github/workflows/deploy-presto.yml deleted file mode 100644 index e549748..0000000 --- a/.github/workflows/deploy-presto.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Deploy (Presto) -on: - workflow_dispatch: - push: - branches: [main] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - NODE_ENV: production - WRANGLER_SEND_METRICS: false - -jobs: - verify: - name: Verify - uses: ./.github/workflows/verify.yml - secrets: inherit - - deploy: - name: Deploy (Presto) - runs-on: ubuntu-latest - needs: verify - timeout-minutes: 60 - steps: - - name: Clone repository - uses: actions/checkout@v6 - - - name: Install dependencies - uses: ./.github/actions/install-dependencies - - - name: Build - env: - NODE_ENV: production - CLOUDFLARE_ENV: presto - run: pnpm build - - - name: Deploy Worker - uses: cloudflare/wrangler-action@v3 - env: - NODE_ENV: production - CLOUDFLARE_ENV: presto - with: - apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} - accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - packageManager: pnpm - command: deploy --env presto diff --git a/.github/workflows/deploy-preview-moderato.yml b/.github/workflows/deploy-preview-moderato.yml deleted file mode 100644 index 83e6ee0..0000000 --- a/.github/workflows/deploy-preview-moderato.yml +++ /dev/null @@ -1,110 +0,0 @@ -name: Deploy Preview (Moderato) -on: - pull_request: - types: [opened, reopened, synchronize, ready_for_review] - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -env: - NODE_ENV: production - WRANGLER_SEND_METRICS: false - -jobs: - verify: - name: Verify - uses: ./.github/workflows/verify.yml - secrets: inherit - - deploy-preview: - name: Deploy Preview (Moderato) - runs-on: ubuntu-latest - needs: verify - permissions: - issues: write - contents: read - pull-requests: write - env: - NODE_ENV: production - CLOUDFLARE_ENV: moderato - steps: - - name: Clone repository - uses: actions/checkout@v6 - - - name: Install dependencies - uses: ./.github/actions/install-dependencies - - - name: Build - run: pnpm build - - - name: Upload Worker Version - id: deploy - uses: cloudflare/wrangler-action@v3 - with: - apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} - accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - environment: moderato - command: versions upload - - - name: Save Deployment Info - if: always() - shell: bash - run: | - mkdir -p deployment-info - - if [[ "${{ steps.deploy.outcome }}" == "success" ]]; then - STATUS="success" - DEPLOYMENT_URL="${{ steps.deploy.outputs.deployment-url }}" - ALIAS_URL="${{ steps.deploy.outputs.pages-deployment-alias-url }}" - else - STATUS="failed" - DEPLOYMENT_URL="" - ALIAS_URL="" - fi - - cat > "deployment-info/app-moderato.json" << EOF - { - "app": "app", - "env": "moderato", - "status": "$STATUS", - "deployment_url": "$DEPLOYMENT_URL", - "deployment_alias_url": "$ALIAS_URL" - } - EOF - - cat "deployment-info/app-moderato.json" - - - name: Upload Deployment Info - if: always() - uses: actions/upload-artifact@v6 - with: - name: deployment-app-moderato - path: deployment-info/app-moderato.json - retention-days: 1 - - comment-deployments: - name: Post Deployment Table - runs-on: ubuntu-latest - needs: deploy-preview - if: always() - permissions: - pull-requests: write - contents: read - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Download All Deployment Artifacts - uses: actions/download-artifact@v7 - with: - pattern: deployment-* - path: deployments - merge-multiple: true - - - name: Post Deployment Table - uses: ./.github/actions/post-deployment-table - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - deployments-path: deployments diff --git a/.github/workflows/deploy-preview-presto.yml b/.github/workflows/deploy-preview-presto.yml deleted file mode 100644 index b59a55e..0000000 --- a/.github/workflows/deploy-preview-presto.yml +++ /dev/null @@ -1,110 +0,0 @@ -name: Deploy Preview (Presto) -on: - pull_request: - types: [opened, reopened, synchronize, ready_for_review] - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -env: - NODE_ENV: production - WRANGLER_SEND_METRICS: false - -jobs: - verify: - name: Verify - uses: ./.github/workflows/verify.yml - secrets: inherit - - deploy-preview: - name: Deploy Preview (Presto) - runs-on: ubuntu-latest - needs: verify - permissions: - issues: write - contents: read - pull-requests: write - env: - NODE_ENV: production - CLOUDFLARE_ENV: presto - steps: - - name: Clone repository - uses: actions/checkout@v6 - - - name: Install dependencies - uses: ./.github/actions/install-dependencies - - - name: Build - run: pnpm build - - - name: Upload Worker Version - id: deploy - uses: cloudflare/wrangler-action@v3 - with: - apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} - accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - environment: presto - command: versions upload - - - name: Save Deployment Info - if: always() - shell: bash - run: | - mkdir -p deployment-info - - if [[ "${{ steps.deploy.outcome }}" == "success" ]]; then - STATUS="success" - DEPLOYMENT_URL="${{ steps.deploy.outputs.deployment-url }}" - ALIAS_URL="${{ steps.deploy.outputs.pages-deployment-alias-url }}" - else - STATUS="failed" - DEPLOYMENT_URL="" - ALIAS_URL="" - fi - - cat > "deployment-info/app-presto.json" << EOF - { - "app": "app", - "env": "presto", - "status": "$STATUS", - "deployment_url": "$DEPLOYMENT_URL", - "deployment_alias_url": "$ALIAS_URL" - } - EOF - - cat "deployment-info/app-presto.json" - - - name: Upload Deployment Info - if: always() - uses: actions/upload-artifact@v6 - with: - name: deployment-app-presto - path: deployment-info/app-presto.json - retention-days: 1 - - comment-deployments: - name: Post Deployment Table - runs-on: ubuntu-latest - needs: deploy-preview - if: always() - permissions: - pull-requests: write - contents: read - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Download All Deployment Artifacts - uses: actions/download-artifact@v7 - with: - pattern: deployment-* - path: deployments - merge-multiple: true - - - name: Post Deployment Table - uses: ./.github/actions/post-deployment-table - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - deployments-path: deployments diff --git a/.github/workflows/deploy-preview.yml b/.github/workflows/deploy-preview.yml new file mode 100644 index 0000000..5583198 --- /dev/null +++ b/.github/workflows/deploy-preview.yml @@ -0,0 +1,119 @@ +name: Deploy Preview +on: + pull_request: + types: [opened, reopened, synchronize, ready_for_review] + workflow_dispatch: + +concurrency: + group: deploy-preview-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +env: + NODE_ENV: production + WRANGLER_SEND_METRICS: false + +jobs: + verify: + name: Checks + uses: ./.github/workflows/verify.yml + secrets: inherit + + deploy-moderato: + name: Deploy Moderato + runs-on: ubuntu-latest + needs: verify + permissions: + contents: read + outputs: + status: ${{ steps.deploy.outputs.status }} + url: ${{ steps.deploy.outputs.url }} + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/install-dependencies + - name: Deploy + id: deploy + uses: ./.github/actions/deploy + with: + environment: moderato + cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }} + cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + + deploy-presto: + name: Deploy Presto + runs-on: ubuntu-latest + needs: verify + permissions: + contents: read + outputs: + status: ${{ steps.deploy.outputs.status }} + url: ${{ steps.deploy.outputs.url }} + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/install-dependencies + - name: Deploy + id: deploy + uses: ./.github/actions/deploy + with: + environment: presto + cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }} + cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + + post-table: + name: Post Deployment Table + runs-on: ubuntu-latest + needs: [deploy-moderato, deploy-presto] + if: always() && needs.verify.result == 'success' + permissions: + pull-requests: write + steps: + - name: Post table + uses: actions/github-script@v8 + with: + script: | + const sha = context.sha.slice(0, 7) + const time = new Date().toISOString() + const deployments = [ + { env: 'moderato', status: '${{ needs.deploy-moderato.outputs.status }}', url: '${{ needs.deploy-moderato.outputs.url }}' }, + { env: 'presto', status: '${{ needs.deploy-presto.outputs.status }}', url: '${{ needs.deploy-presto.outputs.url }}' }, + ] + + const rows = deployments.map(d => { + const status = d.status === 'success' + ? `Deployed ${time}` + : 'Failed' + const preview = d.status === 'success' && d.url ? `[Open](${d.url})` : '-' + return `| ${d.env} | ${status} | ${preview} |` + }) + + const body = ` +## Preview Deployments + +Commit \`${sha}\` + +| Environment | Status | Preview | +|-------------|--------|---------| +${rows.join('\n')}` + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }) + + const existing = comments.find(c => c.body.includes('')) + + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body + }) + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body + }) + } diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..9c2437f --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,51 @@ +name: Deploy +on: + workflow_dispatch: + push: + branches: [main] + +concurrency: + group: deploy-${{ github.ref }} + cancel-in-progress: true + +env: + NODE_ENV: production + WRANGLER_SEND_METRICS: false + +jobs: + verify: + name: Checks + uses: ./.github/workflows/verify.yml + secrets: inherit + + deploy-moderato: + name: Deploy Moderato + runs-on: ubuntu-latest + needs: verify + timeout-minutes: 60 + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/install-dependencies + - name: Deploy + uses: ./.github/actions/deploy + with: + environment: moderato + command: deploy + cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }} + cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + + deploy-presto: + name: Deploy Presto + runs-on: ubuntu-latest + needs: verify + timeout-minutes: 60 + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/install-dependencies + - name: Deploy + uses: ./.github/actions/deploy + with: + environment: presto + command: deploy + cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }} + cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} From c2f81c683ee64f6cdafa721109b708a584605756 Mon Sep 17 00:00:00 2001 From: Pierre Bertet Date: Tue, 20 Jan 2026 19:50:36 +0000 Subject: [PATCH 2/8] fix: escape backticks in yaml --- .github/workflows/deploy-preview.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/deploy-preview.yml b/.github/workflows/deploy-preview.yml index 5583198..937224b 100644 --- a/.github/workflows/deploy-preview.yml +++ b/.github/workflows/deploy-preview.yml @@ -85,14 +85,16 @@ jobs: return `| ${d.env} | ${status} | ${preview} |` }) - const body = ` -## Preview Deployments - -Commit \`${sha}\` - -| Environment | Status | Preview | -|-------------|--------|---------| -${rows.join('\n')}` + const body = [ + '', + '## Preview Deployments', + '', + 'Commit `' + sha + '`', + '', + '| Environment | Status | Preview |', + '|-------------|--------|---------|', + rows.join('\n'), + ].join('\n') const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, From cbf1551877c3ac1830235a11b18c7f535da219bc Mon Sep 17 00:00:00 2001 From: Pierre Bertet Date: Tue, 20 Jan 2026 19:57:07 +0000 Subject: [PATCH 3/8] tweaks --- .github/workflows/deploy-preview.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy-preview.yml b/.github/workflows/deploy-preview.yml index 937224b..e2e1eab 100644 --- a/.github/workflows/deploy-preview.yml +++ b/.github/workflows/deploy-preview.yml @@ -77,12 +77,14 @@ jobs: { env: 'presto', status: '${{ needs.deploy-presto.outputs.status }}', url: '${{ needs.deploy-presto.outputs.url }}' }, ] + const formatted = new Date(time).toLocaleString('en-US', { + month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit', timeZone: 'UTC' + }) + ' UTC' const rows = deployments.map(d => { - const status = d.status === 'success' - ? `Deployed ${time}` - : 'Failed' - const preview = d.status === 'success' && d.url ? `[Open](${d.url})` : '-' - return `| ${d.env} | ${status} | ${preview} |` + const status = d.status === 'success' && d.url + ? '✓ [' + formatted + '](' + d.url + ')' + : d.status === 'success' ? '✓ ' + formatted : '✗ Failed' + return `| ${d.env} | ${status} |` }) const body = [ @@ -91,8 +93,8 @@ jobs: '', 'Commit `' + sha + '`', '', - '| Environment | Status | Preview |', - '|-------------|--------|---------|', + '| Environment | Deployment |', + '|-------------|------------|', rows.join('\n'), ].join('\n') From 651a087cdc0a5029742f87e3d1c7f8c4792ed407 Mon Sep 17 00:00:00 2001 From: Pierre Bertet Date: Tue, 20 Jan 2026 20:07:33 +0000 Subject: [PATCH 4/8] refactor: use matrix for deploy workflows + improve PR comment format - Use matrix strategy for parallel deploys - Simplify PR comment: ### Preview header, neutral timestamp - Pass deployment results via artifacts Co-Authored-By: Claude Opus 4.5 --- .github/workflows/deploy-preview.yml | 71 +++++++++++++--------------- .github/workflows/deploy.yml | 25 +++------- 2 files changed, 39 insertions(+), 57 deletions(-) diff --git a/.github/workflows/deploy-preview.yml b/.github/workflows/deploy-preview.yml index e2e1eab..44f0fca 100644 --- a/.github/workflows/deploy-preview.yml +++ b/.github/workflows/deploy-preview.yml @@ -18,15 +18,15 @@ jobs: uses: ./.github/workflows/verify.yml secrets: inherit - deploy-moderato: - name: Deploy Moderato + deploy: + name: Deploy ${{ matrix.env }} runs-on: ubuntu-latest needs: verify permissions: contents: read - outputs: - status: ${{ steps.deploy.outputs.status }} - url: ${{ steps.deploy.outputs.url }} + strategy: + matrix: + env: [moderato, presto] steps: - uses: actions/checkout@v6 - uses: ./.github/actions/install-dependencies @@ -34,67 +34,62 @@ jobs: id: deploy uses: ./.github/actions/deploy with: - environment: moderato + environment: ${{ matrix.env }} cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }} cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - - deploy-presto: - name: Deploy Presto - runs-on: ubuntu-latest - needs: verify - permissions: - contents: read - outputs: - status: ${{ steps.deploy.outputs.status }} - url: ${{ steps.deploy.outputs.url }} - steps: - - uses: actions/checkout@v6 - - uses: ./.github/actions/install-dependencies - - name: Deploy - id: deploy - uses: ./.github/actions/deploy + - name: Save result + if: always() + run: | + mkdir -p results + echo '{"env":"${{ matrix.env }}","status":"${{ steps.deploy.outputs.status }}","url":"${{ steps.deploy.outputs.url }}"}' > results/${{ matrix.env }}.json + - uses: actions/upload-artifact@v4 + if: always() with: - environment: presto - cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }} - cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + name: deploy-${{ matrix.env }} + path: results/ post-table: - name: Post Deployment Table + name: Post Table runs-on: ubuntu-latest - needs: [deploy-moderato, deploy-presto] + needs: deploy if: always() && needs.verify.result == 'success' permissions: pull-requests: write steps: + - uses: actions/download-artifact@v4 + with: + pattern: deploy-* + merge-multiple: true - name: Post table uses: actions/github-script@v8 with: script: | + const fs = require('fs') const sha = context.sha.slice(0, 7) const time = new Date().toISOString() - const deployments = [ - { env: 'moderato', status: '${{ needs.deploy-moderato.outputs.status }}', url: '${{ needs.deploy-moderato.outputs.url }}' }, - { env: 'presto', status: '${{ needs.deploy-presto.outputs.status }}', url: '${{ needs.deploy-presto.outputs.url }}' }, - ] - const formatted = new Date(time).toLocaleString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit', timeZone: 'UTC' }) + ' UTC' + + const deployments = fs.readdirSync('.').filter(f => f.endsWith('.json')).map(f => JSON.parse(fs.readFileSync(f))) + deployments.sort((a, b) => a.env.localeCompare(b.env)) + const rows = deployments.map(d => { const status = d.status === 'success' && d.url - ? '✓ [' + formatted + '](' + d.url + ')' - : d.status === 'success' ? '✓ ' + formatted : '✗ Failed' + ? '✓ [Open](' + d.url + ')' + : d.status === 'success' ? '✓' : '✗ Failed' return `| ${d.env} | ${status} |` }) + const commitUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${context.sha}` const body = [ '', - '## Preview Deployments', + '### Preview', '', - 'Commit `' + sha + '`', + '[' + sha + '](' + commitUrl + ') · ' + formatted, '', - '| Environment | Deployment |', - '|-------------|------------|', + '| Environment | |', + '|-------------|---|', rows.join('\n'), ].join('\n') diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9c2437f..852c127 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -18,34 +18,21 @@ jobs: uses: ./.github/workflows/verify.yml secrets: inherit - deploy-moderato: - name: Deploy Moderato + deploy: + name: Deploy ${{ matrix.env }} runs-on: ubuntu-latest needs: verify timeout-minutes: 60 + strategy: + matrix: + env: [moderato, presto] steps: - uses: actions/checkout@v6 - uses: ./.github/actions/install-dependencies - name: Deploy uses: ./.github/actions/deploy with: - environment: moderato - command: deploy - cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }} - cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - - deploy-presto: - name: Deploy Presto - runs-on: ubuntu-latest - needs: verify - timeout-minutes: 60 - steps: - - uses: actions/checkout@v6 - - uses: ./.github/actions/install-dependencies - - name: Deploy - uses: ./.github/actions/deploy - with: - environment: presto + environment: ${{ matrix.env }} command: deploy cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }} cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} From f1812908df66a2baaa51d73da6786c3e227a14bb Mon Sep 17 00:00:00 2001 From: Pierre Bertet Date: Tue, 20 Jan 2026 20:10:19 +0000 Subject: [PATCH 5/8] fix: remove duplicate verify trigger on PRs --- .github/workflows/verify.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 262527b..368d377 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -1,6 +1,5 @@ name: Verify on: - pull_request: workflow_call: workflow_dispatch: From 63aadabdc0d48218819e37832415dca5d9f82a45 Mon Sep 17 00:00:00 2001 From: Pierre Bertet Date: Tue, 20 Jan 2026 20:12:29 +0000 Subject: [PATCH 6/8] style: add Status column with Ready/Failed to preview table --- .github/workflows/deploy-preview.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deploy-preview.yml b/.github/workflows/deploy-preview.yml index 44f0fca..6a94e99 100644 --- a/.github/workflows/deploy-preview.yml +++ b/.github/workflows/deploy-preview.yml @@ -75,10 +75,9 @@ jobs: deployments.sort((a, b) => a.env.localeCompare(b.env)) const rows = deployments.map(d => { - const status = d.status === 'success' && d.url - ? '✓ [Open](' + d.url + ')' - : d.status === 'success' ? '✓' : '✗ Failed' - return `| ${d.env} | ${status} |` + const status = d.status === 'success' ? '✓ Ready' : '✗ Failed' + const preview = d.status === 'success' && d.url ? '[Open](' + d.url + ')' : '-' + return `| ${d.env} | ${status} | ${preview} |` }) const commitUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${context.sha}` @@ -88,8 +87,8 @@ jobs: '', '[' + sha + '](' + commitUrl + ') · ' + formatted, '', - '| Environment | |', - '|-------------|---|', + '| Environment | Status | Preview |', + '|-------------|--------|---------|', rows.join('\n'), ].join('\n') From 3adb94af30e85ea88527ef09aed4084c09abc1ec Mon Sep 17 00:00:00 2001 From: Pierre Bertet Date: Tue, 20 Jan 2026 20:13:13 +0000 Subject: [PATCH 7/8] fix: use PR head sha instead of merge commit sha --- .github/workflows/deploy-preview.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-preview.yml b/.github/workflows/deploy-preview.yml index 6a94e99..26f6c21 100644 --- a/.github/workflows/deploy-preview.yml +++ b/.github/workflows/deploy-preview.yml @@ -65,7 +65,7 @@ jobs: with: script: | const fs = require('fs') - const sha = context.sha.slice(0, 7) + const sha = '${{ github.event.pull_request.head.sha }}'.slice(0, 7) const time = new Date().toISOString() const formatted = new Date(time).toLocaleString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit', timeZone: 'UTC' @@ -80,7 +80,7 @@ jobs: return `| ${d.env} | ${status} | ${preview} |` }) - const commitUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${context.sha}` + const commitUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${{ github.event.pull_request.head.sha }}` const body = [ '', '### Preview', From 083e526a81137d14bc8422ab3db760e582f0339d Mon Sep 17 00:00:00 2001 From: Pierre Bertet Date: Tue, 20 Jan 2026 20:17:19 +0000 Subject: [PATCH 8/8] perf: remove build from verify (deploys build anyway) --- .github/workflows/verify.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 368d377..0572b92 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -27,6 +27,3 @@ jobs: - name: Check types run: pnpm gen:types && pnpm check:types - - - name: Build - run: pnpm build