-
Notifications
You must be signed in to change notification settings - Fork 0
208 lines (181 loc) · 10.2 KB
/
Copy pathdeploy.yml
File metadata and controls
208 lines (181 loc) · 10.2 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
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
196
197
198
199
200
201
202
203
204
205
206
207
208
name: Deploy
# Publishes the judge-facing /verify dashboard to Vercel production.
#
# DEPLOY SHAPE — read before changing anything here:
# * The Vercel project (`overrule`) serves ONLY the static contents of the
# `verify/` directory: index.html + data/. It does NOT serve the repo root.
# There is no server, no framework, no Dockerfile — `--cwd verify` makes
# verify/ the deployment root so `/` resolves to verify/index.html.
# * Project linking comes from the VERCEL_ORG_ID / VERCEL_PROJECT_ID repo
# variables. Without them the CLI silently CREATES A NEW PROJECT named
# after the directory instead of deploying to the live one.
# * `vercel deploy --prod` does not reliably move the custom-domain alias on
# this account, so the alias is re-asserted explicitly after every deploy.
# * https://overrule.edycu.dev is a judged artifact that must stay up through
# 2026-09-15. The preflight refuses to deploy a broken/missing dashboard,
# and the post-deploy smoke check FAILS the job if the live page is not the
# dashboard — byte-identical to the verify/index.html in this commit.
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
# One production deploy at a time; never cancel one mid-flight — a cancelled
# deploy can leave the alias pointing at a half-promoted deployment.
concurrency:
group: deploy-production
cancel-in-progress: false
env:
VERCEL_ORG_ID: ${{ vars.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ vars.VERCEL_PROJECT_ID }}
VERCEL_CLI_VERSION: "53.2.0"
LIVE_URL: "https://overrule.edycu.dev"
LIVE_DOMAIN: "overrule.edycu.dev"
jobs:
deploy:
name: "Deploy /verify to Vercel production"
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
# ── Guard 1 · never ship a broken artifact ────────────────────────
# If this commit's verify/ is not a healthy dashboard, stop BEFORE the
# deploy so the currently-live page is left untouched.
- name: Preflight — verify/ is a healthy static dashboard
run: |
set -euo pipefail
fail() { echo "::error::preflight: $*"; exit 1; }
[ -f verify/index.html ] || fail "verify/index.html is missing"
[ -f verify/data/dashboard-data.js ] || fail "verify/data/dashboard-data.js is missing"
size=$(wc -c < verify/index.html | tr -d "[:space:]")
[ "$size" -ge 20000 ] || fail "verify/index.html is only ${size} bytes — expected >= 20000"
grep -q '<title>Overrule' verify/index.html || fail "no Overrule <title> in verify/index.html"
grep -q 'id="proofstrip"' verify/index.html || fail "dashboard markup (#proofstrip) missing"
grep -q 'src="data/dashboard-data.js"' verify/index.html || fail "dashboard data script tag missing"
grep -q '__OVERRULE_DATA__' verify/data/dashboard-data.js || fail "dashboard data payload missing"
# A stale .vercel/ would override the org/project variables below.
[ ! -e verify/.vercel ] || fail "verify/.vercel must not be committed"
echo "preflight OK — verify/index.html ${size} bytes"
sha256sum verify/index.html verify/data/dashboard-data.js
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install Vercel CLI (pinned)
run: npm install --global "vercel@${VERCEL_CLI_VERSION}"
- name: Sanity — Vercel project variables are present
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
run: |
set -euo pipefail
[ -n "${VERCEL_ORG_ID:-}" ] || { echo "::error::VERCEL_ORG_ID variable is not set"; exit 1; }
[ -n "${VERCEL_PROJECT_ID:-}" ] || { echo "::error::VERCEL_PROJECT_ID variable is not set"; exit 1; }
[ -n "${VERCEL_TOKEN:-}" ] || { echo "::error::VERCEL_TOKEN secret is not set"; exit 1; }
echo "org=${VERCEL_ORG_ID} project=${VERCEL_PROJECT_ID}"
# ── Deploy ────────────────────────────────────────────────────────
- name: Deploy verify/ to Vercel production
id: deploy
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
run: |
set -euo pipefail
set +e
vercel deploy --prod --cwd verify --yes --token="$VERCEL_TOKEN" \
> /tmp/vercel.out 2> /tmp/vercel.err
rc=$?
set -e
echo "--- vercel stdout ---"; cat /tmp/vercel.out
echo "--- vercel stderr ---"; cat /tmp/vercel.err
if [ "$rc" -ne 0 ]; then
echo "::error::vercel deploy exited ${rc}"
exit "$rc"
fi
# The CLI prints plain text in CI and JSON when it detects an agent;
# both put the deployment origin in the output. Take the first
# *.vercel.app URL (the inspector URL is on vercel.com, so it cannot
# be matched by accident).
url=$(grep -Eoh 'https://[a-zA-Z0-9._-]+\.vercel\.app' /tmp/vercel.out /tmp/vercel.err | head -n 1 || true)
[ -n "$url" ] || { echo "::error::could not parse a deployment URL from the vercel output"; exit 1; }
echo "deployment_url=${url}" >> "$GITHUB_OUTPUT"
echo "Deployed: ${url}"
# ── Known gotcha · move the custom-domain alias ───────────────────
# `--prod` does not reliably re-point overrule.edycu.dev on this account.
# `vercel alias set` is idempotent, so assert it unconditionally. Failure
# here is not fatal on its own — the smoke check below is the real gate,
# and it retries the alias once more before giving up.
- name: Point overrule.edycu.dev at this deployment
continue-on-error: true
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
DEPLOYMENT_URL: ${{ steps.deploy.outputs.deployment_url }}
run: |
set -euo pipefail
vercel alias set "$DEPLOYMENT_URL" "$LIVE_DOMAIN" \
--scope "$VERCEL_ORG_ID" --token="$VERCEL_TOKEN"
# ── Guard 2 · the live page must be the dashboard ─────────────────
- name: Smoke check — https://overrule.edycu.dev serves this build
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
DEPLOYMENT_URL: ${{ steps.deploy.outputs.deployment_url }}
run: |
set -uo pipefail
expected_html=$(sha256sum verify/index.html | cut -d' ' -f1)
expected_data=$(sha256sum verify/data/dashboard-data.js | cut -d' ' -f1)
echo "expected index.html sha256 = ${expected_html}"
code=""; actual_html=""
for attempt in 1 2 3 4 5 6 7 8; do
# Unique query string defeats the Vercel edge cache.
code=$(curl -sS --compressed --max-time 30 \
-o /tmp/live.html -w '%{http_code}' \
"${LIVE_URL}/?ci=${GITHUB_RUN_ID}-${attempt}" || echo "000")
actual_html=$(sha256sum /tmp/live.html | cut -d' ' -f1)
echo "attempt ${attempt}: HTTP ${code} sha256 ${actual_html}"
if [ "$code" = "200" ] && [ "$actual_html" = "$expected_html" ]; then
break
fi
# Halfway through, re-assert the alias: this is the documented
# failure mode (deployment is fine, the domain never moved).
if [ "$attempt" = "4" ]; then
echo "live page still stale — re-running: vercel alias set"
vercel alias set "$DEPLOYMENT_URL" "$LIVE_DOMAIN" \
--scope "$VERCEL_ORG_ID" --token="$VERCEL_TOKEN" || true
fi
sleep 8
done
set -e
fail() { echo "::error::LIVE SITE CHECK FAILED: $*"; echo "--- first 400 bytes served ---"; head -c 400 /tmp/live.html; exit 1; }
# 1 · reachable
[ "$code" = "200" ] || fail "${LIVE_URL} returned HTTP ${code}"
# 2 · it is the dashboard, not a 404 / directory index / repo listing
size=$(wc -c < /tmp/live.html | tr -d "[:space:]")
[ "$size" -ge 20000 ] || fail "served page is only ${size} bytes"
grep -q '<title>Overrule' /tmp/live.html || fail "served page has no Overrule <title>"
grep -q 'id="proofstrip"' /tmp/live.html || fail "served page lacks the dashboard markup (#proofstrip)"
grep -q 'src="data/dashboard-data.js"' /tmp/live.html || fail "served page lacks the dashboard data script"
grep -qiE 'Index of /|<title>404|Directory listing|DEPLOYMENT_NOT_FOUND' /tmp/live.html \
&& fail "served page looks like a 404 or a directory index" || true
# 3 · it is THIS build, not a stale alias
[ "$actual_html" = "$expected_html" ] \
|| fail "live page does not match verify/index.html in this commit (expected ${expected_html}, got ${actual_html})"
# 4 · the data the dashboard renders from is served too
data_code=$(curl -sS --compressed --max-time 30 \
-o /tmp/live-data.js -w '%{http_code}' \
"${LIVE_URL}/data/dashboard-data.js?ci=${GITHUB_RUN_ID}" || echo "000")
[ "$data_code" = "200" ] || fail "/data/dashboard-data.js returned HTTP ${data_code}"
grep -q '__OVERRULE_DATA__' /tmp/live-data.js || fail "/data/dashboard-data.js is not the dashboard payload"
actual_data=$(sha256sum /tmp/live-data.js | cut -d' ' -f1)
[ "$actual_data" = "$expected_data" ] \
|| fail "/data/dashboard-data.js does not match this commit"
{
echo "### Live dashboard verified"
echo ""
echo "| check | result |"
echo "| --- | --- |"
echo "| ${LIVE_URL} | HTTP 200, ${size} bytes |"
echo "| dashboard markup | present (#proofstrip) |"
echo "| index.html sha256 | \`${actual_html}\` (matches this commit) |"
echo "| data/dashboard-data.js | HTTP 200, matches this commit |"
echo "| deployment | ${DEPLOYMENT_URL} |"
} >> "$GITHUB_STEP_SUMMARY"
echo "LIVE SITE OK — ${LIVE_URL} serves this build."