Repository navigation
101 lines (88 loc) · 4.44 KB
/
Copy pathdeploy.yml
File metadata and controls
101 lines (88 loc) · 4.44 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
# Production deploy (spec overview §Step 4). Every push to `main` builds,
# tests, and ships the Worker — "deploy" is now "merge to main", not a
# maintainer running `wrangler deploy` from a laptop (see the `deploy`
# skill's §5, and CLAUDE.md's Release section).
#
# No `paths-ignore`. Deliberately — skipping doc-only commits would let the
# footer/`/version` SHA drift behind head-of-main, which silently breaks
# this entire feature: the whole point is that the deployed commit always
# matches `main`. Every commit to `main` must redeploy, no exceptions.
name: Deploy
on:
push:
branches: [main]
# CI never commits back to the repo (the build-info stamp below is
# runner-only — see the stamp step), so it only needs read access.
permissions:
contents: read
# A second push queues behind an in-flight deploy instead of cancelling it
# mid-upload — a cancelled `wrangler deploy` can leave the Worker half
# updated.
concurrency:
group: deploy-production
cancel-in-progress: false
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
# Runs BEFORE the stamp step below: `npm run lint` is
# `prettier --check .`, and the heredoc-generated `build-info.ts`
# content would eventually fail a prettier check of its own (ordering
# is load-bearing, not style — spec overview §Step 4).
- run: npm run lint
# Regenerates the tracked `public/styles.css` asset so the uploaded
# stylesheet matches this build. Never committed back.
- run: npm run build:css
# `check` and `test` run against the COMMITTED `'dev'` placeholder,
# BEFORE the stamp below. This ordering is load-bearing, not style:
# the dev-sentinel tests (footer renders plain unlinked text, /verify
# omits its provenance line, GET /version returns `dev`) are the guard
# that a non-CI build never claims provenance. They assert against the
# real committed file on purpose — that is also what catches a real SHA
# accidentally committed into src/build-info.ts. Stamping first would
# make those four tests unrunnable in CI by construction.
- run: npm run check
- run: npm test
# Overwrites the committed `'dev'` placeholder with this run's real
# provenance, IN THE RUNNER ONLY — never committed back (`permissions:
# contents: read` above has no write scope to do so anyway). Last
# mutation before upload: the only delta from the tested tree is this
# one three-field constant, and a malformed stamp fails the deploy
# step's own bundle rather than shipping.
# The type annotation is intentional and must match src/build-info.ts's
# committed shape exactly: an explicit `{ readonly ...: string }`
# object type, not `as const`. Without it, this real-SHA rewrite would
# infer literal string types for commit/runId/builtAt instead of
# `string`, breaking every consumer's `BUILD.commit !== 'dev'` check
# at typecheck time (the same `never`-narrowing failure the committed
# placeholder's `as const` used to cause).
- name: Stamp build-info.ts
run: |
cat > src/build-info.ts <<EOF
export const BUILD: { readonly commit: string; readonly runId: string; readonly builtAt: string } = {
commit: '${{ github.sha }}',
runId: '${{ github.run_id }}',
builtAt: '$(date -u +%Y-%m-%dT%H:%M:%SZ)',
};
EOF
# Typecheck the stamped file before upload — cheap, and it is the only
# thing between the stamp and production.
- run: npm run typecheck
# `--tag`/`--message` write this run's identity into CLOUDFLARE's own
# version record, not just ours. Without them the record carries
# `Tag: -` / `Message: -`, so auditing "did every deploy come from CI?"
# means correlating upload timestamps against Actions run times — a
# clock argument, not an identity one. With them, `wrangler deployments
# list` names the commit outright, and an out-of-band deploy shows up
# as an untagged entry at a glance.
- uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --tag ${{ github.sha }} --message "GitHub Actions run ${{ github.run_id }}"