-
Notifications
You must be signed in to change notification settings - Fork 12.6k
169 lines (163 loc) · 8.38 KB
/
Copy pathconvex-deploy.yml
File metadata and controls
169 lines (163 loc) · 8.38 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
name: Convex Deploy
# Auto-deploy `convex/` changes to the Convex production deployment on every
# merge to `main`. Required because Vercel's build step only deploys api/, src/,
# and other Vercel-served code — the Convex backend has its own deployment
# pipeline that must be triggered separately. Without this workflow,
# `convex/<module>.ts` changes silently merged into main without ever running
# in production. Surfaced concretely as PR #3460 / #3466: the structured-data
# `ConvexError({ kind, ... })` fix sat in main for 30+ minutes while
# `WORLDMONITOR-PD` kept growing because Convex prod was still running the old
# string-data throws.
#
# Setup required (one-time): add `CONVEX_DEPLOY_KEY` to the repo's GitHub
# Actions secrets. Generate via `npx convex deploy --once-create-deploy-key`
# against the prod deployment, or via the Convex dashboard → Settings →
# Deploy Keys → "Production: deploy" scope.
on:
push:
branches: [main]
# Manual fallback so the operator can re-run a deploy without a code change
# (e.g. recover from a failed deploy or push a hotfix off-cycle).
workflow_dispatch:
permissions:
contents: read
concurrency:
# Serialize deploys so two back-to-back merges don't race against each other.
# `cancel-in-progress: false` is intentional — every merge that touches
# convex/ should reach prod, even if a later merge follows quickly.
group: convex-deploy-prod
cancel-in-progress: false
jobs:
changes:
# Distinct check-run name — see the note in typecheck.yml (#5822). This
# workflow is push-to-main only, so it never competes on a PR head SHA, but
# the deploy gate evaluates main pushes too and `changes` is one of its
# required names: sharing the name would let this job's result stand in for
# test.yml's there. Not gated itself — deploy-gate.yml only aggregates Test,
# Typecheck, Lint Code, Security Audit and Stacked Merge Guard.
name: convex-changes
runs-on: ubuntu-latest
outputs:
convex: ${{ steps.diff.outputs.convex }}
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
# Need both BEFORE and AFTER commits locally so `git diff` can name
# the changed files authoritatively. `fetch-depth: 0` (full history)
# is the cheapest way; the alternative — `gh api compare` — paginates
# at 300 files and silently empties on API failure, which fails OPEN
# (would skip a real convex/ change → recreates the exact drift this
# workflow is meant to prevent). git diff fails CLOSED: if it can't
# answer, the job errors and the deploy doesn't silently skip.
fetch-depth: 0
- id: diff
run: |
set -euo pipefail
# workflow_dispatch always deploys; nothing to diff.
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "convex=true" >> "$GITHUB_OUTPUT"
exit 0
fi
BEFORE="${{ github.event.before }}"
AFTER="${{ github.event.after }}"
# Brand-new branch / first push (BEFORE is the all-zero SHA): we
# can't diff against a non-existent prior state. Fall through to
# deploy — this is the safe default for first-push scenarios.
if [ "$BEFORE" = "0000000000000000000000000000000000000000" ]; then
echo "convex=true" >> "$GITHUB_OUTPUT"
exit 0
fi
# Force-push or rebase can leave BEFORE unreachable in our local
# clone even at fetch-depth: 0. Verify both SHAs are present;
# if not, deploy (fail-CLOSED — better a redundant deploy than a
# missed one).
if ! git cat-file -e "$BEFORE^{commit}" 2>/dev/null \
|| ! git cat-file -e "$AFTER^{commit}" 2>/dev/null; then
echo "::warning::commit not in fetched history (force-push?), deploying defensively"
echo "convex=true" >> "$GITHUB_OUTPUT"
exit 0
fi
# Authoritative path-scoped diff. `--` separates revisions from
# pathspecs, so `convex/` is interpreted as a path filter even if
# something weird is going on with the SHAs.
if git diff --name-only "$BEFORE" "$AFTER" -- 'convex/' | grep -q .; then
echo "convex=true" >> "$GITHUB_OUTPUT"
else
echo "convex=false" >> "$GITHUB_OUTPUT"
fi
deploy:
needs: changes
if: needs.changes.outputs.convex == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '24'
cache: 'npm'
- run: npm ci --no-audit --no-fund
- id: deploy
name: Convex deploy (prod)
# `--yes` skips the interactive "Are you sure?" prompt. The deploy
# key in CONVEX_DEPLOY_KEY pins the target deployment, so there is
# no ambiguity about which environment we're pushing to.
run: npx convex deploy --yes
env:
CONVEX_DEPLOY_KEY: ${{ secrets.CONVEX_DEPLOY_KEY }}
- id: seed_dodo_webhook_failure_summary
name: Seed Dodo webhook failure summary (idempotent)
# Failure recording uses this pre-seeded aggregate row as its
# document-backed OCC lock. Keep this immediately after deploy so an
# unrelated followedCountries seed failure cannot leave payment
# failure recording uninitialized.
run: npx convex run --prod payments/webhookMutations:_seedFailureSummary
continue-on-error: true
env:
CONVEX_DEPLOY_KEY: ${{ secrets.CONVEX_DEPLOY_KEY }}
- id: seed_intel_history_append_lock
name: Seed intelligence-history append lock (idempotent)
# `intelHistory.append` fails closed with APPEND_LOCK_NOT_SEEDED until
# this document-backed OCC lock exists. Seed it immediately after
# deploy so concurrent first-seen appends cannot fall back to an empty
# index-range check, which Convex does not serialize.
run: npx convex run --prod intelHistory:_seedAppendLock
continue-on-error: true
env:
CONVEX_DEPLOY_KEY: ${{ secrets.CONVEX_DEPLOY_KEY }}
- id: seed_followed_countries_shards
name: Seed followedCountries shards (idempotent)
# The `followCountry` / `unfollowCountry` / `mergeAnonymousLocal`
# mutations throw `SHARDS_NOT_SEEDED` if the `followedCountriesShards`
# table is empty (Codex round-4 P0 v2 — pre-seeded sharded lock). The
# daily cron at 03:00 UTC also seeds, but a deploy that lands at
# 04:00 UTC would leave the feature broken for ~23h until the next
# cron tick. Running the seed inline AFTER `convex deploy --yes` (and
# therefore against the just-deployed code) closes that window.
# Idempotent — `_seedShards` collects existing shard ids and inserts
# only the missing ones. `npx convex run` targets internal functions
# by their file:export path; `--prod` pins the production deployment
# via CONVEX_DEPLOY_KEY.
run: npx convex run --prod followedCountries:_seedShards
continue-on-error: true
env:
CONVEX_DEPLOY_KEY: ${{ secrets.CONVEX_DEPLOY_KEY }}
- id: seed_followed_countries_country_locks
name: Seed followedCountries country locks (idempotent)
# Counter writes are serialized by a pre-seeded per-country lock row.
# The daily cron also self-heals this table, but deploying and then
# seeding inline avoids a temporary COUNTRY_LOCKS_NOT_SEEDED window.
run: npx convex run --prod followedCountries:_seedCountryLocks
continue-on-error: true
env:
CONVEX_DEPLOY_KEY: ${{ secrets.CONVEX_DEPLOY_KEY }}
- name: Verify post-deploy seeds
if: always() && steps.deploy.outcome == 'success'
run: |
if [ "${{ steps.seed_dodo_webhook_failure_summary.outcome }}" != "success" ] \
|| [ "${{ steps.seed_intel_history_append_lock.outcome }}" != "success" ] \
|| [ "${{ steps.seed_followed_countries_shards.outcome }}" != "success" ] \
|| [ "${{ steps.seed_followed_countries_country_locks.outcome }}" != "success" ]; then
echo "::error::One or more post-deploy seeds failed; inspect the seed steps above"
exit 1
fi