perf(ci): reuse build artifacts across PR checks #69
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: Preview deployment | |
| # Since 2025-12-08 `pull_request_target` always takes the workflow file and the checked-out commit | |
| # from the default branch, whatever the pull request targets. Pull-request code therefore never runs | |
| # on this runner, which holds the Coolify webhook secret. | |
| on: | |
| pull_request_target: | |
| types: [labeled, synchronize, reopened, ready_for_review] | |
| permissions: {} | |
| jobs: | |
| deploy: | |
| name: "Preview / Deploy" | |
| if: >- | |
| vars.COOLIFY_URL != '' && | |
| vars.COOLIFY_APP_UUID != '' && | |
| vars.COOLIFY_PREVIEW_URL_TEMPLATE != '' && | |
| (github.event.label.name == 'preview' || | |
| contains(github.event.pull_request.labels.*.name, 'preview')) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| attestations: read | |
| contents: read | |
| deployments: write | |
| packages: read | |
| pull-requests: write | |
| # Longer than the budgets inside the scripts (image wait, deploy, reachability), so a run that | |
| # exceeds them reports a deployment status instead of being cancelled without one. | |
| timeout-minutes: 40 | |
| concurrency: | |
| group: hephaestus-preview-admission | |
| cancel-in-progress: false | |
| env: | |
| COOLIFY_URL: ${{ vars.COOLIFY_URL }} | |
| COOLIFY_APP_UUID: ${{ vars.COOLIFY_APP_UUID }} | |
| COOLIFY_PREVIEW_URL_TEMPLATE: ${{ vars.COOLIFY_PREVIEW_URL_TEMPLATE }} | |
| PREVIEW_MAX_ACTIVE: ${{ vars.PREVIEW_MAX_ACTIVE }} | |
| steps: | |
| - name: Load the trusted preview controller | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| ref: ${{ github.sha }} | |
| persist-credentials: false | |
| sparse-checkout: | | |
| .github/actions/setup-bun | |
| package.json | |
| scripts | |
| - name: Set up the repository's Bun version | |
| uses: ./.github/actions/setup-bun | |
| - name: Resolve the pull request head | |
| id: context | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: | | |
| const controller = await import(`${process.env.GITHUB_WORKSPACE}/scripts/preview-controller.ts`); | |
| await controller.resolve({ github, context, core }); | |
| - name: Explain why the preview did not deploy | |
| if: steps.context.outputs.announce == 'true' | |
| uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4 | |
| with: | |
| header: app-preview | |
| number: ${{ steps.context.outputs.pr_number }} | |
| message: | | |
| ## 🚧 App Preview | |
| Not deployed: ${{ steps.context.outputs.reason }} | |
| <sub>Remove the `preview` label to opt out.</sub> | |
| - name: Wait for CI to publish this commit's signed images | |
| if: steps.context.outputs.eligible == 'true' | |
| env: | |
| GHCR_TOKEN: ${{ github.token }} | |
| GH_TOKEN: ${{ github.token }} | |
| HEAD_SHA: ${{ steps.context.outputs.head_sha }} | |
| run: node scripts/coolify-preview.ts images | |
| - name: Recheck opt-in and head before queueing | |
| id: recheck | |
| if: steps.context.outputs.eligible == 'true' | |
| env: | |
| PR_NUMBER: ${{ steps.context.outputs.pr_number }} | |
| HEAD_SHA: ${{ steps.context.outputs.head_sha }} | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: | | |
| const controller = await import(`${process.env.GITHUB_WORKSPACE}/scripts/preview-controller.ts`); | |
| await controller.recheck({ github, context, core }); | |
| - name: Reserve the native GitHub deployment | |
| id: github_deployment | |
| if: steps.recheck.outputs.proceed == 'true' | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| PR_NUMBER: ${{ steps.context.outputs.pr_number }} | |
| HEAD_SHA: ${{ steps.context.outputs.head_sha }} | |
| ENVIRONMENT: ${{ steps.context.outputs.environment }} | |
| PREVIEW_URL: ${{ steps.context.outputs.preview_url }} | |
| SOURCE_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| with: | |
| script: | | |
| const controller = await import(`${process.env.GITHUB_WORKSPACE}/scripts/preview-controller.ts`); | |
| await controller.create({ github, context, core }); | |
| - name: Announce the deployment | |
| if: steps.github_deployment.outputs.deployment_id != '' | |
| uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4 | |
| with: | |
| header: app-preview | |
| number: ${{ steps.context.outputs.pr_number }} | |
| message: | | |
| ## 🚧 App Preview | |
| Deploying `${{ steps.context.outputs.head_sha }}`… | |
| <sub>Runs the images CI published for this commit. Updates on every commit. Remove the `preview` label to tear it down.</sub> | |
| - name: Queue the exact commit through Coolify's signed PR webhook | |
| id: queue | |
| if: steps.recheck.outputs.proceed == 'true' | |
| env: | |
| COOLIFY_TOKEN: ${{ secrets.COOLIFY_PREVIEW_READ_TOKEN }} | |
| COOLIFY_WEBHOOK_SECRET: ${{ secrets.COOLIFY_PREVIEW_WEBHOOK_SECRET }} | |
| PR_NUMBER: ${{ steps.context.outputs.pr_number }} | |
| HEAD_SHA: ${{ steps.context.outputs.head_sha }} | |
| PR_URL: ${{ steps.context.outputs.pr_url }} | |
| PR_TITLE: ${{ steps.context.outputs.pr_title }} | |
| AUTHOR_ASSOCIATION: ${{ steps.context.outputs.author_association }} | |
| HEAD_REF: ${{ steps.context.outputs.head_ref }} | |
| BASE_REF: ${{ steps.context.outputs.base_ref }} | |
| run: node scripts/coolify-preview.ts queue | |
| - name: Wait for Coolify and verify the preview | |
| id: wait | |
| if: steps.queue.outputs.deployment_uuid != '' | |
| env: | |
| COOLIFY_TOKEN: ${{ secrets.COOLIFY_PREVIEW_READ_TOKEN }} | |
| DEPLOYMENT_UUID: ${{ steps.queue.outputs.deployment_uuid }} | |
| EXPECTED_SHA: ${{ steps.context.outputs.head_sha }} | |
| PR_NUMBER: ${{ steps.context.outputs.pr_number }} | |
| PREVIEW_URL: ${{ steps.context.outputs.preview_url }} | |
| run: node scripts/coolify-preview.ts wait | |
| - name: Finalize the GitHub deployment | |
| id: finalize | |
| if: always() && steps.github_deployment.outputs.deployment_id != '' | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| DEPLOYMENT_ID: ${{ steps.github_deployment.outputs.deployment_id }} | |
| PR_NUMBER: ${{ steps.context.outputs.pr_number }} | |
| ENVIRONMENT: ${{ steps.context.outputs.environment }} | |
| PREVIEW_URL: ${{ steps.context.outputs.preview_url }} | |
| SOURCE_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| FINAL_STATE: ${{ steps.wait.outputs.state || 'error' }} | |
| DESCRIPTION: ${{ steps.wait.outputs.description || 'Preview workflow stopped unexpectedly.' }} | |
| LOG_URL: ${{ steps.wait.outputs.log_url || format('{0}/{1}/actions/runs/{2}', github.server_url, github.repository, github.run_id) }} | |
| with: | |
| script: | | |
| const controller = await import(`${process.env.GITHUB_WORKSPACE}/scripts/preview-controller.ts`); | |
| await controller.finalize({ github, context, core }); | |
| - name: Publish the preview link | |
| if: steps.finalize.outputs.final_state == 'success' | |
| uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4 | |
| with: | |
| header: app-preview | |
| number: ${{ steps.context.outputs.pr_number }} | |
| message: | | |
| ## 🚀 App Preview | |
| 🔗 **[Open preview](${{ steps.context.outputs.preview_url }})** | |
| <sub>Preview for commit ${{ steps.context.outputs.head_sha }}. Updates on every commit. Remove the `preview` label to tear it down.</sub> | |
| # An opted-in pull request must not learn about a red preview only from the Actions tab. | |
| - name: Report a failed preview | |
| if: >- | |
| always() && | |
| steps.context.outputs.eligible == 'true' && | |
| steps.recheck.outputs.opted_out != 'true' && | |
| steps.wait.outputs.state != 'success' | |
| uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4 | |
| with: | |
| header: app-preview | |
| number: ${{ steps.context.outputs.pr_number }} | |
| message: | | |
| ## ❌ App Preview | |
| ${{ steps.wait.outputs.description || 'CI did not publish images for this commit in time, or the preview could not be prepared.' }} | |
| 🔎 **[Deployment log](${{ steps.wait.outputs.log_url || format('{0}/{1}/actions/runs/{2}', github.server_url, github.repository, github.run_id) }})** | |
| <sub>The next commit retries automatically.</sub> | |
| - name: Fail when the preview failed | |
| if: steps.queue.outputs.deployment_uuid != '' && steps.wait.outputs.state != 'success' | |
| env: | |
| FINAL_STATE: ${{ steps.wait.outputs.state || 'error' }} | |
| run: | | |
| echo "::error::Preview deployment ended in state ${FINAL_STATE}." | |
| exit 1 |