-
-
Notifications
You must be signed in to change notification settings - Fork 0
295 lines (286 loc) · 13.1 KB
/
Copy pathdeploy-workers.yml
File metadata and controls
295 lines (286 loc) · 13.1 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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
name: Deploy Workers
on:
push:
branches: [main]
workflow_dispatch:
inputs:
worker:
description: Which worker to deploy (manual run)
type: choice
options: [all, api, mcp, discovery, webhooks]
default: all
# Target environment for a manual run. On push:main, the api/mcp/discovery
# jobs fan out to BOTH prod and staging via matrix (webhooks + the MCP
# registry remain prod-only). Constrained to a choice so the value is one
# of production|staging; shell steps reference it through env: to keep
# interpolation inert.
environment:
description: Target environment (manual run only; push deploys both)
type: choice
options: [production, staging]
default: production
concurrency:
group: deploy-workers-${{ github.ref }}
cancel-in-progress: false
env:
BUN_VERSION: "1.3.14"
# wrangler 4.88+ requires Node 22+; portless ≥0.13 requires Node 24+. bunx
# invokes wrangler's bin via the `#!/usr/bin/env node` shebang, so the runner's
# Node version is what counts. Pinned to 24.15.0 (bundles npm 11.12.1).
NODE_VERSION: "24.15.0"
jobs:
changes:
name: Detect changes
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
api: ${{ steps.filter.outputs.api }}
mcp: ${{ steps.filter.outputs.mcp }}
mcp_registry: ${{ steps.filter.outputs.mcp_registry }}
discovery: ${{ steps.filter.outputs.discovery }}
webhooks: ${{ steps.filter.outputs.webhooks }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 2
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: filter
with:
filters: |
_shared: &shared
- 'packages/**'
- 'package.json'
- 'bun.lock'
- '.github/workflows/deploy-workers.yml'
api:
- *shared
- 'workers/api/**'
# The API worker compiles the persisted-document allowlist into
# the bundle (workers/api/src/graphql/persisted.ts). When the web
# codegen mints a new hash, the API must redeploy or non-admin
# callers hit PERSISTED_QUERY_NOT_IN_LIST.
- 'web/src/lib/graphql/__generated__/persisted-documents.json'
mcp:
- *shared
- 'workers/mcp/**'
mcp_registry:
- 'workers/mcp/server.json'
- '.github/workflows/deploy-workers.yml'
discovery:
- *shared
- 'workers/discovery/**'
- 'src/**'
webhooks:
- *shared
- 'workers/webhooks/**'
deploy-api:
name: Deploy API worker (${{ matrix.environment }})
needs: changes
if: |
(github.event_name == 'push' && needs.changes.outputs.api == 'true') ||
(github.event_name == 'workflow_dispatch' && (inputs.worker == 'all' || inputs.worker == 'api'))
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
# On push: fan out to prod + staging. On manual dispatch: just the chosen env.
environment: ${{ github.event_name == 'workflow_dispatch' && fromJSON(format('["{0}"]', inputs.environment)) || fromJSON('["production", "staging"]') }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: ${{ env.BUN_VERSION }}
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ env.NODE_VERSION }}
- run: bun install --frozen-lockfile
# Apply D1 migrations before the worker starts serving the new code so
# new schema lands before anything queries it. Idempotent: wrangler
# skips entries already tracked in d1_migrations.
#
# Prod passes the DB name (`released-db`); staging passes the binding name
# (`DB`) + `--env staging`, so wrangler resolves through the env block.
- name: Apply D1 migrations
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
TARGET_ENV: ${{ matrix.environment }}
run: |
if [ "$TARGET_ENV" = "staging" ]; then
bunx wrangler d1 migrations apply DB --env staging --remote --config workers/api/wrangler.jsonc
else
bunx wrangler d1 migrations apply released-db --remote --config workers/api/wrangler.jsonc
fi
- name: Deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
WRANGLER_ENV_FLAG: ${{ matrix.environment == 'staging' && '--env staging' || '' }}
run: bunx wrangler deploy $WRANGLER_ENV_FLAG --config workers/api/wrangler.jsonc
deploy-mcp:
name: Deploy MCP worker (${{ matrix.environment }})
needs: changes
if: |
(github.event_name == 'push' && needs.changes.outputs.mcp == 'true') ||
(github.event_name == 'workflow_dispatch' && (inputs.worker == 'all' || inputs.worker == 'mcp'))
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
environment: ${{ github.event_name == 'workflow_dispatch' && fromJSON(format('["{0}"]', inputs.environment)) || fromJSON('["production", "staging"]') }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: ${{ env.BUN_VERSION }}
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ env.NODE_VERSION }}
- run: bun install --frozen-lockfile
# workers/mcp is intentionally excluded from root workspaces (cloudflare:workers
# imports break Bun's eager workspace resolution). Install its deps locally.
- name: Install MCP worker deps
working-directory: workers/mcp
run: bun install --frozen-lockfile
- name: Deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
WRANGLER_ENV_FLAG: ${{ matrix.environment == 'staging' && '--env staging' || '' }}
run: bunx wrangler deploy $WRANGLER_ENV_FLAG --config workers/mcp/wrangler.jsonc
deploy-discovery:
name: Deploy discovery worker (${{ matrix.environment }})
needs: changes
if: |
(github.event_name == 'push' && needs.changes.outputs.discovery == 'true') ||
(github.event_name == 'workflow_dispatch' && (inputs.worker == 'all' || inputs.worker == 'discovery'))
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
environment: ${{ github.event_name == 'workflow_dispatch' && fromJSON(format('["{0}"]', inputs.environment)) || fromJSON('["production", "staging"]') }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: ${{ env.BUN_VERSION }}
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ env.NODE_VERSION }}
- run: bun install --frozen-lockfile
- name: Install discovery worker deps
working-directory: workers/discovery
run: bun install --frozen-lockfile
- name: Deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
WRANGLER_ENV_FLAG: ${{ matrix.environment == 'staging' && '--env staging' || '' }}
run: bunx wrangler deploy $WRANGLER_ENV_FLAG --config workers/discovery/wrangler.jsonc
deploy-webhooks:
name: Deploy webhooks worker
needs: changes
# Webhooks has no staging env; skip when staging is selected.
if: |
inputs.environment != 'staging' && (
(github.event_name == 'push' && needs.changes.outputs.webhooks == 'true') ||
(github.event_name == 'workflow_dispatch' && (inputs.worker == 'all' || inputs.worker == 'webhooks'))
)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: ${{ env.BUN_VERSION }}
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ env.NODE_VERSION }}
- run: bun install --frozen-lockfile
# workers/webhooks is intentionally excluded from root workspaces (cloudflare:workers
# imports break Bun's eager workspace resolution). Install its deps locally.
- name: Install webhooks worker deps
working-directory: workers/webhooks
run: bun install --frozen-lockfile
- name: Deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: bunx wrangler deploy --config workers/webhooks/wrangler.jsonc
# Live e2e: enqueue a synthetic event through the echo subscriber and tail
# deliveries. Only runs on push to main when WEBHOOK_E2E_SUBSCRIPTION_ID is set.
# continue-on-error keeps the job green; the `|| echo` lines print a named
# diagnostic before the per-command failure is swallowed.
- name: Live e2e — webhook delivery smoke test
if: github.event_name == 'push'
continue-on-error: true
env:
RELEASES_API_URL: https://api.releases.sh
RELEASES_API_KEY: ${{ secrets.RELEASES_API_KEY || secrets.RELEASED_API_KEY }}
SUBSCRIPTION_ID: ${{ secrets.WEBHOOK_E2E_SUBSCRIPTION_ID }}
run: |
set -euo pipefail
if [ -z "${SUBSCRIPTION_ID:-}" ]; then
echo "[e2e] WEBHOOK_E2E_SUBSCRIPTION_ID not set — skipping"
exit 0
fi
AUTH="Authorization: Bearer $RELEASES_API_KEY"
curl -fsS -X POST -H "$AUTH" "$RELEASES_API_URL/v1/admin/webhooks/$SUBSCRIPTION_ID/test" \
|| echo "[e2e] webhook test call failed (non-fatal)"
# Queue max_batch_timeout is 5s, so a plain sleep races the flush. Poll up to ~14s.
for _ in 1 2 3 4 5 6 7; do
ROWS=$(curl -fsS -H "$AUTH" "$RELEASES_API_URL/v1/admin/webhooks/$SUBSCRIPTION_ID/deliveries?limit=1" 2>/dev/null || true)
if echo "$ROWS" | grep -q '"event_id"'; then break; fi
sleep 2
done
curl -fsS -H "$AUTH" "$RELEASES_API_URL/v1/admin/webhooks/$SUBSCRIPTION_ID/deliveries?limit=5" \
|| echo "[e2e] deliveries call failed (non-fatal)"
publish-mcp-registry:
name: Publish MCP registry metadata
needs: [changes, deploy-mcp]
# Registry metadata is production-only — staging deploys skip it.
if: |
inputs.environment != 'staging' && (
(github.event_name == 'push' && needs.changes.outputs.mcp_registry == 'true') ||
(github.event_name == 'workflow_dispatch' && (inputs.worker == 'all' || inputs.worker == 'mcp'))
)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install mcp-publisher
run: |
set -euo pipefail
curl -fsSL "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64.tar.gz" -o /tmp/mcp-publisher.tar.gz
tar -xzf /tmp/mcp-publisher.tar.gz -C /tmp mcp-publisher
sudo mv /tmp/mcp-publisher /usr/local/bin/mcp-publisher
- name: Login and publish
working-directory: workers/mcp
env:
PRIVATE_KEY_PEM: ${{ secrets.MCP_REGISTRY_PRIVATE_KEY_PEM }}
run: |
set -euo pipefail
if [ -z "${PRIVATE_KEY_PEM:-}" ]; then
echo "MCP_REGISTRY_PRIVATE_KEY_PEM secret is not set" >&2
exit 1
fi
KEY_FILE="$(mktemp)"
trap 'rm -f "$KEY_FILE"' EXIT
printf '%s\n' "$PRIVATE_KEY_PEM" > "$KEY_FILE"
PRIVATE_KEY="$(openssl pkey -in "$KEY_FILE" -noout -text | grep -A3 'priv:' | tail -n +2 | tr -d ' :\n')"
mcp-publisher login http --domain releases.sh --private-key "$PRIVATE_KEY"
# Registry rejects re-publishing an existing version; treat that as a no-op
# so workflow-dispatch reruns and workflow-only edits don't fail the deploy.
if mcp-publisher publish > /tmp/publish.log 2>&1; then
cat /tmp/publish.log
else
cat /tmp/publish.log
if grep -qE 'already exists|duplicate version' /tmp/publish.log; then
echo "Version already published — skipping."
exit 0
fi
exit 1
fi