-
-
Notifications
You must be signed in to change notification settings - Fork 22
196 lines (182 loc) · 8.05 KB
/
deploy-preview.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# 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_dispatch:
inputs:
prNumber:
required: true
description: Which PR to create a preview for
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:
# Defaults to ${{ github.repository }}, but that doesn't work for forks
# This should also make it obvious why this is in a separate workflow
# and in a job that doesn't have access to our secrets.
repository: ${{ github.event.workflow_run.head_repository.full_name }}
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' || github.event.inputs.prNumber
outputs:
number: ${{ steps.pr.outputs.result || steps.number.outputs.pr-number || github.event.inputs.prNumber }}
steps:
- run: echo '${{ toJSON(github.event) }}'
name: "debug workflow_run event"
- run: echo "${{ github.event.number }}"
name: 'debug github.event.number'
# When PR comes from a fork,
# github.event.workflow_run.pull_requests is empty
- run: echo "${{ github.event.workflow_run.pull_requests[0].number }}"
id: number
# https://github.com/orgs/community/discussions/25220#discussioncomment-8697399
- name: Find associated pull request
# if: "!${{ steps.number.output.pr-number }}"
id: pr
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
with:
script: |
const response = await github.rest.search.issuesAndPullRequests({
q: 'repo:${{ github.repository }} is:pr sha:${{ github.event.workflow_run.head_sha }}',
per_page: 1,
})
const items = response.data.items
if (items.length < 1) {
console.error('No PRs found')
return
}
const pullRequestNumber = items[0].number
console.info("Pull request number is", pullRequestNumber)
return pullRequestNumber
# 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 }}
ReformatSubDomain:
name: Reformat Sub-domain Alias
runs-on: ubuntu-latest
needs: [determinePR]
outputs:
subdomain: ${{ steps.reformat.outputs.subdomain }}
steps:
- id: reformat
run: |
branch="${{ github.event.workflow_run.head_branch }}"
# Remove any / in the branch name
# renovate/uuid becomes renovate-uuid
subdomain="${branch//\//-}"
subdomain=$(echo $subdomain | cut -c -28)
echo "subdomain=$subdomain" >> $GITHUB_OUTPUT
PostComment:
name: Post Preview URL as comment to PR
runs-on: ubuntu-latest
needs: [DeployPreview, ReformatSubDomain, determinePR]
permissions:
pull-requests: write
steps:
- uses: marocchino/sticky-pull-request-comment@v2
with:
header: preview-urls
number: ${{ needs.determinePR.outputs.number }}
message: |+
| Project | Preview URL[^note] | Manage |
| ------- | ------------------ | ------ |
| Limber | https://${{ needs.ReformatSubDomain.outputs.subdomain }}.limber-glimdown.pages.dev | [on Cloudflare](https://dash.cloudflare.com/c67910a047e1510fec6d0d0cf442934c/pages/view/limber-glimdown) |
| Tutorial | https://${{ needs.ReformatSubDomain.outputs.subdomain }}.limber-glimmer-tutorial.pages.dev | [on Cloudflare](https://dash.cloudflare.com/c67910a047e1510fec6d0d0cf442934c/pages/view/limber-glimmer-tutorial)
[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.