-
Notifications
You must be signed in to change notification settings - Fork 163
410 lines (354 loc) · 14.2 KB
/
Copy pathci.yml
File metadata and controls
410 lines (354 loc) · 14.2 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
name: CI
on:
pull_request:
push:
branches:
- main
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
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' }}
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/**"
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
test:
name: Test
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 15
# Tuned for Blacksmith's 4 vCPU runners.
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: 4
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
- run: bun run test
e2e:
name: E2E (${{ matrix.target }}${{ matrix['shard-name'] && format(' {0}', matrix['shard-name']) || '' }})
strategy:
fail-fast: false
matrix:
include:
# Each cloud shard boots its own fresh dev stack. On 4 vCPU runners,
# four fatter shards keep the longest shard below selfhost while saving
# four runner boots and four warm cache restores.
- { target: cloud, shard: 1/4, shard-name: 1of4 }
- { target: cloud, shard: 2/4, shard-name: 2of4 }
- { target: cloud, shard: 3/4, shard-name: 3of4 }
- { target: cloud, shard: 4/4, shard-name: 4of4 }
- target: selfhost
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.
- name: Install Playwright Chromium
run: bunx playwright install --with-deps chromium chromium-headless-shell
working-directory: e2e
# The globalsetup boots the target's own dev server (ports are claimed
# per checkout, so this is hermetic) and tears it down after the run.
# --retry=2: browser scenarios can still hit isolated waitFor timeouts
# (single-test waitFor timeouts, not systemic failures); a retry on the
# same booted stack clears them.
- name: Run cloud scenarios
if: matrix.target == 'cloud'
env:
MCP_SESSION_TIMEOUT_MS: "3000"
MCP_PAUSED_SESSION_IDLE_TIMEOUT_MS: "6000"
run: bunx vitest run --project cloud --retry=2 ${{ matrix.shard && format('--shard={0}', matrix.shard) || '' }}
working-directory: e2e
- name: Run selfhost scenarios
if: matrix.target == 'selfhost'
run: bunx vitest run --project selfhost --retry=2
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
e2e-local:
name: E2E (stdio MCP)
# Skipped on pull_request: the local scenario boots a real `executor web`
# plus a browser and is currently flaky on PRs. Still runs on push to main.
if: github.event_name != 'pull_request'
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-
# The local scenarios boot a real `executor web` (which spawns a Node
# sidecar) and some drive a browser, so pin Node 24 and install Chromium.
- 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-
# `chromium` and the new `chromium-headless-shell` ship as separate
# downloads; the browser-driven scenarios launch the headless shell.
# Install from e2e so bunx resolves ITS pinned playwright (the version the
# tests run against) rather than floating to the latest, which would fetch
# a browser build the test runtime does not look for.
- name: Install Playwright Chromium
run: bunx playwright install --with-deps chromium chromium-headless-shell
working-directory: e2e
# The `local` project is excluded from the default `test` chain (each
# scenario boots its own `executor web`). Run just the stdio MCP scenario
# here: it is the auto-connect / env-as-secret regression guard, and
# running it alone avoids the boot-resource accumulation and the
# pre-existing browser flakiness of the rest of the local suite. Expanding
# to the full `local` project (bun run test:local) is a follow-up once
# those are stabilized.
- name: Run the stdio MCP scenario
run: bunx vitest run --project local local/stdio-mcp.test.ts
working-directory: e2e
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
tags: executor-selfhost:ci