Skip to content

Deploy Preview

Deploy Preview #6736

# Because C.I. jobs could expose secrets to malicious pull requsets,
# GitHub prevents (by default) exposing action secrets to pull requests
# from forks.
#
# This is great, however, the jobs that use the secrets are still useful on
# pull requests.
#
# To run a _trusted_ workflow, we can trigger it from an event from an _untrusted_
# workflow. This keeps the secrets out of reach from the fork, but still allows
# us to keep the utility of pull request preview deploys, browserstack running, etc.
# Normally, this _trusted_ behavior is offloaded by Cloudflare, Netlify, Vercel, etc
# -- their own workers are trusted and can push comments / updates to pull requests.
#
# We don't want to use their slower (and sometimes paid) hardware.
# When we use our own workflows, we can re-use the cache built from the PR
# (or elsewhere).
#
# To be *most* secure, you'd need to build all the artifacts in the PR,
# then upload them to then be downloaded in the trusted workflows.
# Trusted workflows should not run any scripts from a PR, as malicious
# submitters may tweak the build scripts.
# Since all build artifacts are for the web browser, and not executed in
# node-space, we can be reasonably confident that downloading and testing/deploying
# those artifacts does not compromise our secrets.
#
#
# More information here:
# https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
#
# Things that would make this easier:
# Readablity: https://github.com/actions/download-artifact/issues/172
# Security:
# - if there was a way to avoid pnpm install *entirely*
name: Deploy Preview
# read-write repo token
# access to secrets
on:
workflow_run:
workflows: ["CI"]
types:
# as early as possible
- requested
concurrency:
group: deploy-preview-${{ github.event.workflow_run.pull_requests[0].number }}
cancel-in-progress: true
env:
TURBO_API: http://127.0.0.1:9080
TURBO_TOKEN: this-is-not-a-secret
TURBO_TEAM: myself
jobs:
# This is the only job that needs access to the source code
Build:
runs-on: ubuntu-latest
timeout-minutes: 15
needs: [determinePR]
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.workflow_run.head_branch }}
- name: TurboRepo local server
uses: felixmosh/turborepo-gh-artifacts@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- uses: wyvox/action-setup-pnpm@v3
- run: pnpm turbo build
- uses: actions/upload-artifact@v4
with:
name: deploy-prep-dist
if-no-files-found: error
path: |
./apps/**/dist/**/*
!node_modules/
!./**/node_modules/
#################################################################
# For the rest:
# Does not checkout code, has access to secrets
#################################################################
determinePR:
# this job gates the others -- if the workflow_run request did not come from a PR,
# exit as early as possible
runs-on: ubuntu-latest
if: github.event.workflow_run.event == 'pull_request'
outputs:
number: ${{ steps.number.outputs.pr-number }}
steps:
- run: echo "${{ toJSON(github.event.workflow_run) }}"
name: "debug workflow_run event"
- run: echo "${{ github.event.workflow_run.pull_requests[0].number }}"
id: number
# We can know the URL ahead of time:
# https://<SHA>.limber-glimmer-tutorial.pages.dev
# https://<SHA>.limber-glimdown.pages.dev
DeployPreview:
name: "Deploy: Preview ${{ matrix.app.name }}"
runs-on: ubuntu-latest
timeout-minutes: 15
needs: [Build]
permissions:
contents: read
strategy:
matrix:
app:
- { path: "./tutorial/dist", cloudflareName: "limber-glimmer-tutorial", name: "tutorial" }
- { path: "./repl/dist", cloudflareName: "limber-glimdown", name: "limber" }
steps:
- uses: actions/download-artifact@v4
name: deploy-prep-dist
- name: Preview ${{ matrix.app.name }}
working-directory: ./deploy-prep-dist/${{ matrix.app.path }}
run: |
npx wrangler pages deploy ./ \
--project-name=${{ matrix.app.cloudflareName }} \
--branch=${{ github.event.workflow_run.head_branch }} \
--commit-hash=${{ github.event.workflow_run.head_sha }}
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
PostComment:
name: Post Preview URL as comment to PR
runs-on: ubuntu-latest
needs: [DeployPreview, determinePR]
permissions:
pull-requests: write
steps:
- uses: marocchino/sticky-pull-request-comment@v2
with:
header: preview-urls
number: ${{ github.event.workflow_run.pull_requests[0].number }}
message: |+
| Project | Preview URL[^note] |
| ------- | ----------- |
| Limber | https://${{ github.event.workflow_run.head_branch }}.limber-glimdown.pages.dev |
| Tutorial | https://${{ github.event.workflow_run.head_branch }}.limber-glimmer-tutorial.pages.dev |
[Logs](https://github.com/NullVoxPopuli/limber/actions/runs/${{ github.run_id }})
[^note]: if these branch preview links are not working, please check the logs for the commit-based preview link. There is a character limit of 28 for the branch subdomain, as well as some other heuristics, [described here](https://community.cloudflare.com/t/algorithm-to-generate-a-preview-dns-subdomain-from-a-branch-name/477633?u=walshymvp) for the sake of implementation ease in deploy-preview.yml, that algo has been omitted. The URLs are logged in the wrangler output, but it's hard to get outputs from a matrix job.