-
Notifications
You must be signed in to change notification settings - Fork 247
496 lines (435 loc) · 18.5 KB
/
Copy pathci.yml
File metadata and controls
496 lines (435 loc) · 18.5 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
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
name: CI
on:
pull_request:
push:
branches:
- main
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
# CI is not a user. Without this, executor's own product telemetry reports to the
# production PostHog project from every e2e shard: the analytics layer
# (integration_added, execution_completed, artifact_*) and the
# integrations-registry fetch, which lands as a `hit` event. Each shard is a
# fresh container, so it mints a fresh anonymous id and arrives on a fresh
# runner IP — inflating machine and user counts by roughly an order of
# magnitude. Both opt-outs are the packages' documented CI/test hooks.
env:
DO_NOT_TRACK: "1"
EXECUTOR_DISABLE_ANALYTICS: "1"
EXECUTOR_DISABLE_INTEGRATIONS_FETCH: "1"
jobs:
changes:
name: Changed paths
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 10
outputs:
desktop_smoke: ${{ github.event_name != 'pull_request' || steps.filter.outputs.desktop_smoke == 'true' }}
selfhost_docker_smoke: ${{ github.event_name != 'pull_request' || steps.filter.outputs.selfhost_docker_smoke == 'true' }}
cloud_closure: ${{ github.event_name != 'pull_request' || steps.filter.outputs.cloud_closure == 'true' }}
steps:
- uses: actions/checkout@v4
- name: Filter changed paths
id: filter
if: github.event_name == 'pull_request'
uses: dorny/paths-filter@v3
with:
filters: |
desktop_smoke:
- ".github/workflows/**"
- "bun.lock"
- "package.json"
- "turbo.json"
- "apps/desktop/**"
- "apps/local/**"
- "apps/cli/**"
- "packages/app/**"
- "packages/core/**"
- "packages/hosts/mcp/**"
- "packages/kernel/runtime-quickjs/**"
- "packages/plugins/**"
- "packages/react/**"
cloud_closure:
- ".github/workflows/**"
- "bun.lock"
- "package.json"
- "turbo.json"
- "apps/cloud/**"
- "packages/**"
selfhost_docker_smoke:
- ".github/workflows/**"
- ".dockerignore"
- "bun.lock"
- "package.json"
- "turbo.json"
- "apps/host-selfhost/**"
- "packages/app/**"
- "packages/core/**"
- "packages/hosts/mcp/**"
- "packages/kernel/runtime-quickjs/**"
- "packages/plugins/**"
- "packages/react/**"
format:
name: Format
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
- name: Cache Bun package cache
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-1.3.11-${{ hashFiles('bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-1.3.11-
- run: bun install --frozen-lockfile --ignore-scripts
- run: bun run format:check
lint:
name: Lint
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
- name: Cache Bun package cache
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-1.3.11-${{ hashFiles('bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-1.3.11-
- run: bun install --frozen-lockfile --ignore-scripts
- run: bun run lint
typecheck:
name: Typecheck
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 15
env:
TURBO_API: ${{ vars.TURBO_API }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
- name: Cache Bun package cache
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-1.3.11-${{ hashFiles('bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-1.3.11-
# No prebuilt better-sqlite3 binary matches this runner, so `bun install`
# builds it from source via node-gyp, whose undici needs Node 22.10+
# (webidl.markAsUncloneable). Pin the same Node 24 runtime used by release.
- uses: actions/setup-node@v4
with:
node-version: 24
- run: bun install --frozen-lockfile
- run: bun run typecheck
# The committed routeTree.gen.ts files are generated but checked in, and
# they carry @ts-nocheck — so the typecheck above structurally CANNOT see
# a route that the contract declares and the tree lacks. That drift has
# already shipped a live 404 once. Regenerate and diff instead.
- run: bun run check:routes
test:
name: Test
runs-on: blacksmith-16vcpu-ubuntu-2404
timeout-minutes: 15
# Run eight independent packages at once and cap each Vitest process at two
# workers. This uses all 16 vCPUs without every nested runner seeing the
# whole machine and oversubscribing it.
env:
TURBO_API: ${{ vars.TURBO_API }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
TURBO_TEST_CONCURRENCY: 8
VITEST_MAX_WORKERS: 2
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
- name: Cache Bun package cache
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-1.3.11-${{ hashFiles('bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-1.3.11-
# apps/cloud's test script invokes `node` directly; undici 8.x (pulled
# in by @cloudflare/vitest-pool-workers) calls webidl.markAsUncloneable
# which only exists in Node 22.10+. Pin the Node 24 CI runtime.
- uses: actions/setup-node@v4
with:
node-version: 24
- run: bun install --frozen-lockfile
# Fail loudly if a load-bearing patched dependency (e.g. the agents MCP
# transport hang fix) resolved to a stale, unpatched dist. Without this,
# a bun cache edge case can leave the patch unapplied while everything
# still builds and tests green, and prod ships the pre-fix transport with
# no signal. This is just a few file reads plus greps, so ~zero cost.
- run: bun run check:patches
- run: bun run --cwd e2e test:ci-shard
- run: bun run test
e2e:
name: E2E (${{ matrix.target }}${{ matrix['shard-name'] && format(' {0}', matrix['shard-name']) || '' }})
strategy:
fail-fast: false
matrix:
include:
# The planner assigns every file exactly once using recorded slow-file
# durations plus a conservative weight for new tests. The cloud DB's
# connection teardown and concurrent socket protocol have dedicated
# regression tests; these shards balance wall clock, not hide retries.
- { target: cloud, shard-index: 1, shard-name: 1of16 }
- { target: cloud, shard-index: 2, shard-name: 2of16 }
- { target: cloud, shard-index: 3, shard-name: 3of16 }
- { target: cloud, shard-index: 4, shard-name: 4of16 }
- { target: cloud, shard-index: 5, shard-name: 5of16 }
- { target: cloud, shard-index: 6, shard-name: 6of16 }
- { target: cloud, shard-index: 7, shard-name: 7of16 }
- { target: cloud, shard-index: 8, shard-name: 8of16 }
- { target: cloud, shard-index: 9, shard-name: 9of16 }
- { target: cloud, shard-index: 10, shard-name: 10of16 }
- { target: cloud, shard-index: 11, shard-name: 11of16 }
- { target: cloud, shard-index: 12, shard-name: 12of16 }
- { target: cloud, shard-index: 13, shard-name: 13of16 }
- { target: cloud, shard-index: 14, shard-name: 14of16 }
- { target: cloud, shard-index: 15, shard-name: 15of16 }
- { target: cloud, shard-index: 16, shard-name: 16of16 }
- { target: selfhost, shard-index: 1, shard-name: 1of10 }
- { target: selfhost, shard-index: 2, shard-name: 2of10 }
- { target: selfhost, shard-index: 3, shard-name: 3of10 }
- { target: selfhost, shard-index: 4, shard-name: 4of10 }
- { target: selfhost, shard-index: 5, shard-name: 5of10 }
- { target: selfhost, shard-index: 6, shard-name: 6of10 }
- { target: selfhost, shard-index: 7, shard-name: 7of10 }
- { target: selfhost, shard-index: 8, shard-name: 8of10 }
- { target: selfhost, shard-index: 9, shard-name: 9of10 }
- { target: selfhost, shard-index: 10, shard-name: 10of10 }
# Local files own their server, browser and data directory. Separate
# runners preserve that isolation while removing its 69-second serial
# lane from the two-minute critical path.
- { target: local, shard-index: 1, shard-name: 1of2 }
- { target: local, shard-index: 2, shard-name: 2of2 }
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
- name: Cache Bun package cache
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-1.3.11-${{ hashFiles('bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-1.3.11-
# The dev stacks spawn Node sidecars (vite/workerd tooling); pin the
# same Node 24 runtime the release and publish workflows use.
- uses: actions/setup-node@v4
with:
node-version: 24
- run: bun install --frozen-lockfile
- name: Cache Playwright browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-1.60.0
restore-keys: |
${{ runner.os }}-playwright-
# Install from e2e so bunx resolves ITS pinned playwright (the version
# the tests run against) rather than floating to the latest.
# Blacksmith's Ubuntu image carries the official GitHub runner system
# dependencies. Restore the pinned browser binaries without apt-updating
# every matrix machine.
- name: Install Playwright Chromium
run: bunx playwright install chromium chromium-headless-shell
working-directory: e2e
# Each target either boots its own shared dev server or lets each file own
# its server. Ports and data paths are hermetic in both cases.
# Do not retry scenarios: retries hide flakes and multiply slow timeout
# failures. The fixtures and process lifecycle are deterministic enough
# that the first result is the result.
- name: Run cloud shard
if: matrix.target == 'cloud'
env:
MCP_SESSION_TIMEOUT_MS: "3000"
MCP_PAUSED_SESSION_IDLE_TIMEOUT_MS: "6000"
run: bun scripts/run-ci-shard.ts cloud ${{ matrix['shard-index'] }}
working-directory: e2e
- name: Run scenarios
if: matrix.target != 'cloud'
run: bun scripts/run-ci-shard.ts ${{ matrix.target }} ${{ matrix['shard-index'] }}
working-directory: e2e
# Failed runs keep their trace.zip / session.mp4 / step screenshots in
# runs/<target>/<slug>/ — surface them instead of a bare red X.
- name: Upload run artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: e2e-runs-${{ matrix.target }}${{ matrix['shard-name'] && format('-{0}', matrix['shard-name']) || '' }}
path: e2e/runs/
retention-days: 7
cloud-closure:
name: Cloud evaluated closure
needs: changes
if: needs.changes.outputs.cloud_closure == 'true'
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 20
env:
TURBO_API: ${{ vars.TURBO_API }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
# Every cold isolate evaluates this closure before it can answer a
# request, and it only ever grows by accident - a barrel export or a new
# module-scope import quietly pulls megabytes into the server graph. The
# budget is a ratchet just above today's size, not a discovered limit:
# when it trips, make the new dependency lazy rather than raising it.
START_CLOSURE_BUDGET_MB: 13.5
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
- name: Cache Bun package cache
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-1.3.11-${{ hashFiles('bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-1.3.11-
- uses: actions/setup-node@v4
with:
node-version: 24
- run: bun install --frozen-lockfile
- run: bun run build
working-directory: apps/cloud
- run: node scripts/start-closure.mjs
working-directory: apps/cloud
desktop-smoke:
name: Desktop smoke build
needs: changes
if: needs.changes.outputs.desktop_smoke == 'true'
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
- name: Cache Bun package cache
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-1.3.11-${{ hashFiles('bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-1.3.11-
# No prebuilt better-sqlite3 binary matches this runner, so `bun install`
# builds it from source via node-gyp, whose undici needs Node 22.10+
# (webidl.markAsUncloneable). Pin the same Node 24 CI runtime.
- uses: actions/setup-node@v4
with:
node-version: 24
- run: bun install --frozen-lockfile
- name: Build web app
run: bun run --filter @executor-js/local build
- name: Build bundled executor
env:
BUN_TARGET: bun-linux-x64
run: bun ./scripts/build-sidecar.ts
working-directory: apps/desktop
# Run the compiled sidecar in a clean container where the build
# workspace does not exist. bun --compile bakes build-machine paths
# into __dirname for runtime-loaded assets; running the binary on the
# build machine hides that entire failure class (this exact gap
# shipped a broken 1Password SDK: its sdk-core wasm was read from the
# CI runner's node_modules path at runtime, ENOENT on every user
# machine). Pass condition: the 1Password endpoint reaches real
# credential validation, not a module-load or empty-namespace error.
- name: Run sidecar outside the workspace
working-directory: apps/desktop
run: |
docker run --rm -d --name sidecar-smoke -p 45841:45841 -e HOME=/tmp \
-v "$PWD/resources/executor:/opt/executor:ro" \
debian:bookworm-slim /opt/executor/executor daemon run --foreground \
--port 45841 --hostname 0.0.0.0 --auth-token=ci-smoke
for i in $(seq 1 60); do
code=$(curl -s -o /dev/null -w '%{http_code}' -H "Authorization: Bearer ci-smoke" http://127.0.0.1:45841/api/onepassword/vaults || true)
[ "$code" != "000" ] && break
docker ps -q -f name=sidecar-smoke | grep -q . || { docker logs sidecar-smoke; exit 1; }
sleep 1
done
body=$(curl -s -H "Authorization: Bearer ci-smoke" \
"http://127.0.0.1:45841/api/onepassword/vaults?authKind=service-account&account=ops_ci_fake_token")
docker stop sidecar-smoke
echo "$body"
if echo "$body" | grep -qE "sdk module load|ENOENT|is not a function|not a constructor"; then
echo "::error::1Password SDK failed to load inside the compiled binary (baked build path or missing sibling asset)"
exit 1
fi
if ! echo "$body" | grep -q "invalid service account token"; then
echo "::error::sidecar did not reach 1Password credential validation; unexpected response above"
exit 1
fi
- name: Build Electron main/preload/renderer
run: bunx --bun electron-vite build
working-directory: apps/desktop
selfhost-docker-smoke:
name: Self-host Docker image
needs: changes
if: needs.changes.outputs.selfhost_docker_smoke == 'true'
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: Setup Blacksmith Builder
uses: useblacksmith/setup-docker-builder@v1
- name: Build self-host image
uses: useblacksmith/build-push-action@v2
with:
context: .
file: apps/host-selfhost/Dockerfile
push: false
load: true
tags: executor-selfhost:ci
# Building alone doesn't catch startup crashes (e.g. a runtime-resolved
# package missing from the packaged runtime), so boot the container and
# require a healthy /api/health before calling the image good.
- name: Boot image and check health
shell: bash
run: |
set -euo pipefail
docker run -d --name selfhost-smoke -p 4788:4788 \
-e BETTER_AUTH_SECRET="$(openssl rand -hex 32)" \
-e EXECUTOR_BOOTSTRAP_ADMIN_EMAIL=smoke@example.com \
-e EXECUTOR_BOOTSTRAP_ADMIN_PASSWORD="$(openssl rand -hex 16)" \
-e EXECUTOR_WEB_BASE_URL=http://localhost:4788 \
executor-selfhost:ci
for i in $(seq 1 30); do
if [ "$(docker inspect -f '{{.State.Running}}' selfhost-smoke)" != "true" ]; then
echo "container exited during startup"
docker logs selfhost-smoke
exit 1
fi
if curl -fsS http://localhost:4788/api/health >/dev/null 2>&1; then
echo "healthy after ${i}s"
docker logs selfhost-smoke
exit 0
fi
sleep 1
done
echo "health check never passed"
docker logs selfhost-smoke
exit 1