-
Notifications
You must be signed in to change notification settings - Fork 0
165 lines (143 loc) Β· 5.91 KB
/
Copy pathdeploy.yml
File metadata and controls
165 lines (143 loc) Β· 5.91 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
name: π Deploy (production)
on:
workflow_run:
workflows:
- 'β
Validate'
types:
- completed
permissions:
contents: read
deployments: write
concurrency:
group: deploy-production
cancel-in-progress: true
jobs:
sha-guard:
runs-on: ubuntu-latest
name: π§Ύ Production deploy guard
if: >-
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.head_branch == 'main' &&
github.event.workflow_run.event == 'push'
outputs:
should_deploy: ${{ steps.sha_guard.outputs.should_deploy }}
steps:
- name: π¦ Checkout
uses: actions/checkout@v6.0.2
with:
ref: ${{ github.event.workflow_run.head_sha }}
fetch-depth: 0
- name: π§Ύ Ensure validated SHA is main HEAD
id: sha_guard
shell: bash
run: |
set -euo pipefail
git fetch origin main
HEAD_SHA="$(git rev-parse origin/main)"
if [ "$HEAD_SHA" != "${{ github.event.workflow_run.head_sha }}" ]; then
echo "should_deploy=false" >> "$GITHUB_OUTPUT"
echo "Skipping deploy: validated SHA is not current main HEAD."
echo "validated=${{ github.event.workflow_run.head_sha }}"
echo "head=$HEAD_SHA"
exit 0
fi
echo "should_deploy=true" >> "$GITHUB_OUTPUT"
deploy:
runs-on: ubuntu-latest
name: π Deploy to production
needs: sha-guard
if: needs.sha-guard.outputs.should_deploy == 'true'
environment:
name: production
url: ${{ steps.deploy.outputs.url }}
steps:
- name: π¦ Checkout
uses: actions/checkout@v6.0.2
with:
ref: ${{ github.event.workflow_run.head_sha }}
fetch-depth: 0
- name: π§° Setup Bun
uses: oven-sh/setup-bun@v2.2.0
with:
bun-version: latest
- name: π₯ Install Dependencies
run: bun install --frozen-lockfile
- name: π§± Ensure production resources (D1 + KV)
id: resources
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
run: |
set -euo pipefail
bun tools/ci/production-resources.ts ensure --out-config wrangler-production.generated.json | tee -a "$GITHUB_OUTPUT"
- name: π Sync Cloudflare Secrets (bulk)
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
COOKIE_SECRET: ${{ secrets.COOKIE_SECRET }}
APP_BASE_URL: ${{ secrets.APP_BASE_URL }}
AI_GATEWAY_ID: ${{ secrets.AI_GATEWAY_ID }}
RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }}
RESEND_FROM_EMAIL: ${{ secrets.RESEND_FROM_EMAIL }}
run: >
bun tools/ci/sync-worker-secrets.ts --env production --set-from-env
COOKIE_SECRET --set-from-env AI_GATEWAY_ID --set-from-env-optional
APP_BASE_URL --set-from-env-optional RESEND_API_KEY
--set-from-env-optional RESEND_FROM_EMAIL
- name: ποΈ Apply D1 Migrations
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
WRANGLER_CONFIG: ${{ steps.resources.outputs.wrangler_config }}
run:
bun ./wrangler-env.ts d1 migrations apply APP_DB --remote --config
"$WRANGLER_CONFIG"
- name: βοΈ Deploy to Cloudflare Workers
id: deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
APP_BASE_URL: ${{ secrets.APP_BASE_URL }}
DEPLOY_COMMIT_SHA: ${{ github.event.workflow_run.head_sha }}
WRANGLER_CONFIG: ${{ steps.resources.outputs.wrangler_config }}
run: |
set -euo pipefail
bun run deploy -- --config "$WRANGLER_CONFIG" --var "APP_COMMIT_SHA:${DEPLOY_COMMIT_SHA}" 2>&1 | tee deploy.log
DEPLOY_URL="${APP_BASE_URL:-}"
if [ -z "$DEPLOY_URL" ]; then
DEPLOY_URL="$(node -e "const fs = require('node:fs'); const t = fs.readFileSync('deploy.log','utf8'); const m = t.match(/https:\\/\\/[a-zA-Z0-9._-]+\\.workers\\.dev/g); process.stdout.write(m?.at(-1) ?? '')")"
fi
if [ -n "$DEPLOY_URL" ]; then
echo "url=$DEPLOY_URL" >> "$GITHUB_OUTPUT"
fi
- name: π©Ί Healthcheck (production)
shell: bash
env:
DEPLOY_URL: ${{ steps.deploy.outputs.url }}
EXPECTED_COMMIT_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
set -euo pipefail
if [ -z "${DEPLOY_URL:-}" ]; then
echo "Missing deploy URL output; cannot run healthcheck." >&2
exit 1
fi
if [ -z "${EXPECTED_COMMIT_SHA:-}" ]; then
echo "Missing expected commit SHA; cannot verify deployment version." >&2
exit 1
fi
HEALTHCHECK_URL="${DEPLOY_URL%/}/health"
echo "Healthcheck URL: $HEALTHCHECK_URL"
attempts=20
delay_seconds=3
for i in $(seq 1 "$attempts"); do
echo "Attempt $i/$attempts"
if curl --fail --silent --show-error --location --max-time 10 \
--header "Accept: application/json" \
"$HEALTHCHECK_URL" > health.json && \
node -e "const fs = require('node:fs'); const body = fs.readFileSync('health.json','utf8'); const json = JSON.parse(body); const expected = process.env.EXPECTED_COMMIT_SHA; if (json?.ok !== true) { console.error('healthcheck-unexpected-response', json); process.exit(1); } if (json?.commitSha !== expected) { console.error('healthcheck-unexpected-commit-sha', { expected, actual: json?.commitSha }); process.exit(1); } console.log('healthcheck-ok', json);"; then
exit 0
fi
sleep "$delay_seconds"
done
echo "Healthcheck failed after ${attempts} attempts: $HEALTHCHECK_URL" >&2
if [ -f health.json ]; then
echo "Last response body:" >&2
cat health.json >&2
fi
exit 1