-
Notifications
You must be signed in to change notification settings - Fork 0
159 lines (143 loc) · 6.39 KB
/
Copy pathdeploy.yml
File metadata and controls
159 lines (143 loc) · 6.39 KB
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
# Deploys asifuddin.com whenever main changes.
#
# Why this file exists: Sveltia CMS commits straight to this repository, and
# nothing was watching. Twenty-five plates added through /admin sat on main for
# days without reaching the site, because every deploy until now was somebody
# running `wrangler deploy` by hand.
#
# The verification step is deliberate. A save from /admin can break a schema, a
# build guard or the embargo rule, and shipping a broken site is worse than
# shipping late — so the deploy runs only if `npm run verify` passes. When it
# does not, GitHub emails the failure and the live site keeps the last good
# version.
name: Deploy
on:
push:
branches: [main]
# Lets you re-run a deploy from the Actions tab without pushing anything.
workflow_dispatch:
# A burst of CMS saves would otherwise start several overlapping deploys that
# finish out of order, and the site could end up on an older build than the one
# that started last. Cancel the earlier run instead.
concurrency:
group: deploy-main
cancel-in-progress: true
jobs:
deploy:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
# The reproduction snippets are executed by `verify-snippets`, so the
# runner needs a Python that matches the one their output was recorded
# under (spec I-4).
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install
run: npm ci
# The credential is inspected before the build, so a missing one costs
# ten seconds instead of three minutes. The result is turned into three
# separately named steps rather than one message, so the *name of the
# failing step* states the cause — visible at a glance in the Actions
# list, and readable through the API without opening a log.
- name: Inspect the deploy credential
id: cred
env:
TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
ACCOUNT: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
set +e
# A length is not a value. GitHub masks the secret itself; this is the
# one fact that separates "never arrived" from "arrived and refused".
echo "token characters visible to this job: ${#TOKEN}"
echo "account characters visible to this job: ${#ACCOUNT}"
if [ -z "$TOKEN" ]; then
echo "state=absent" >> "$GITHUB_OUTPUT"; exit 0
fi
export CLOUDFLARE_API_TOKEN="$TOKEN"
if [ -n "$ACCOUNT" ]; then export CLOUDFLARE_ACCOUNT_ID="$ACCOUNT"; fi
OUT=$(npx wrangler whoami 2>&1); RC=$?
echo "$OUT"
if [ $RC -ne 0 ]; then
echo "state=rejected" >> "$GITHUB_OUTPUT"; exit 0
fi
if echo "$OUT" | grep -qi "more than one account\|multiple accounts"; then
echo "state=ambiguous" >> "$GITHUB_OUTPUT"; exit 0
fi
echo "state=ok" >> "$GITHUB_OUTPUT"
- name: "Blocked - no token reached this job"
if: steps.cred.outputs.state == 'absent'
run: |
{
echo "### No token reached this job"
echo ""
echo "\`CLOUDFLARE_API_TOKEN\` arrived empty. Everything else passed."
echo ""
echo "GitHub keeps **four** secret stores and a workflow can read only"
echo "one of them. Open"
echo "<https://github.com/asifuddin01/portfolio/settings/secrets/actions>"
echo "and confirm the name is listed under **Repository secrets** —"
echo "not *Environment*, not *Dependabot*, not *Codespaces* — spelled"
echo "exactly \`CLOUDFLARE_API_TOKEN\`, with no trailing space."
} >> "$GITHUB_STEP_SUMMARY"
exit 1
- name: "Blocked - Cloudflare rejected the token"
if: steps.cred.outputs.state == 'rejected'
run: |
{
echo "### Cloudflare rejected the token"
echo ""
echo "The secret arrived, so the GitHub side is correct. \`wrangler whoami\`"
echo "refused it — see the previous step's output for Cloudflare's own words."
echo ""
echo "The usual cause is that the token **ID** was copied rather than the"
echo "token **value**. Cloudflare shows the value once, on the screen"
echo "immediately after creation. Create a fresh one from the"
echo "**Edit Cloudflare Workers** template at"
echo "<https://dash.cloudflare.com/profile/api-tokens> and replace the secret."
} >> "$GITHUB_STEP_SUMMARY"
exit 1
- name: "Blocked - the token reaches several accounts"
if: steps.cred.outputs.state == 'ambiguous'
run: |
{
echo "### The token reaches more than one account"
echo ""
echo "Add a second repository secret \`CLOUDFLARE_ACCOUNT_ID\` naming the"
echo "account that owns the \`portfolio\` Worker, then re-run."
} >> "$GITHUB_STEP_SUMMARY"
exit 1
- name: Verify
env:
# The public demo, embedded on /researchlens. Set here rather than
# as a repository secret: it is a public URL, and a value the build
# depends on should be readable in the file that builds it.
#
# Unset it and the page falls back to probing for a local instance,
# then to the offline message — which is what it should say when
# there is nothing to embed.
PUBLIC_RESEARCHLENS_SPACE: https://asifuddin01-researchlens.hf.space
run: npm run verify
- name: Deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CF_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
if [ -n "$CF_ACCOUNT_ID" ]; then export CLOUDFLARE_ACCOUNT_ID="$CF_ACCOUNT_ID"; fi
# Verify built dist a moment ago, but assert it rather than assume it:
# wrangler uploads the directory as it finds it and cannot tell that
# it predates the content it is meant to serve.
npm run check:dist
npx wrangler deploy
- name: Report
run: |
{
echo "### Deployed"
echo ""
echo "<https://asifuddin.com> now serves \`${GITHUB_SHA:0:7}\`."
} >> "$GITHUB_STEP_SUMMARY"