Repository navigation
323 lines (268 loc) · 11.8 KB
/
Copy pathci.yml
File metadata and controls
323 lines (268 loc) · 11.8 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
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
test:
name: typecheck + test + coverage gate
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
# voidzero-dev/setup-vp v1.1.0
- name: Setup Vite+
uses: voidzero-dev/setup-vp@e1609b468742ba9232ce5e12b3a77bdf14495277
with:
cache: true
- name: Install dependencies
run: vp install --frozen-lockfile
- name: Typecheck (all packages)
run: vp run --filter './packages/*' typecheck
- name: Lint (Oxlint via Vite+)
run: vp check
- name: shared — tests + 100% coverage gate
run: vp run @ethercalc/shared#test:coverage
- name: worker — Node tests + 100% coverage gate
run: vp run @ethercalc/worker#test:coverage
- name: client — Vite build (needed for assets/static/player.js)
run: vp run @ethercalc/client#build
- name: client-multi — Vite build (needed for assets/multi/)
run: vp run @ethercalc/client-multi#build
- name: Build curated assets/ dir (scripts/build-assets.ts)
# Phase 4.1/11: regenerates `assets/` at repo root from the
# client/client-multi dist output plus the legacy static files.
# Must run BEFORE worker integration tests (miniflare reads
# wrangler.toml `[assets]` at startup) and `wrangler deploy
# --dry-run` (validates the directory exists).
run: vp exec bun scripts/build-assets.ts
- name: Validate worker D1 migrations
# Phase 5.1: smoke-check each SQL file in packages/worker/migrations/
# by replaying it into a fresh in-memory SQLite DB. Catches syntax
# errors before they blow up at miniflare startup or on a real
# `wrangler d1 migrations apply` in prod. ubuntu-latest ships with
# sqlite3 preinstalled.
run: |
for f in packages/worker/migrations/*.sql; do
echo "applying $f"
sqlite3 :memory: < "$f"
done
- name: worker — integration tests (workerd via miniflare)
run: vp run @ethercalc/worker#test:workers
- name: worker — wrangler dry-run build
run: vp run @ethercalc/worker#build:dry
- name: socialcalc-headless — smoke tests (workerd)
run: vp run @ethercalc/socialcalc-headless#test
- name: client — tests + 100% coverage gate
run: vp run @ethercalc/client#test:coverage
- name: client-multi — tests + 100% coverage gate
run: vp run @ethercalc/client-multi#test:coverage
- name: oracle-harness — tests + 100% coverage gate
run: vp run @ethercalc/oracle-harness#test:coverage
- name: cli — tests + 100% coverage gate
run: vp run @ethercalc/cli#test:coverage
- name: socketio-shim — tests + 100% coverage gate
run: vp run @ethercalc/socketio-shim#test:coverage
- name: migrate — tests + 100% coverage gate
run: vp run @ethercalc/migrate#test:coverage
- name: docs — Starlight production build
run: vp run @ethercalc/docs#build
- name: Upload coverage artifacts
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: coverage-lcov
path: |
packages/shared/coverage/
packages/worker/coverage/
packages/client/coverage/
packages/client-multi/coverage/
packages/oracle-harness/coverage/
packages/cli/coverage/
packages/socketio-shim/coverage/
packages/migrate/coverage/
retention-days: 7
if-no-files-found: ignore
build-selfhost:
name: build:selfhost — docker image + smoke
runs-on: ubuntu-latest
timeout-minutes: 20
needs: test
# Mirrors AGENTS.md §11.1 item 10. Uses the Dockerfile + docker-compose
# at the repo root and hits /_health via scripts/smoke-selfhost.sh.
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
- name: Smoke — build image, compose up, curl /_health, compose down
run: ./scripts/smoke-selfhost.sh
- name: Smoke — proxy recipe (nginx -t + compose stack + WS upgrade)
run: ./scripts/smoke-proxy.sh
helm-lint:
name: helm — lint + template
runs-on: ubuntu-latest
timeout-minutes: 5
# Catches chart syntax drift and template bugs on every PR.
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Install Helm
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4
- name: helm lint
run: helm lint ./helm
- name: helm template — default values
run: helm template ci ./helm > /dev/null
- name: helm template — ingress enabled
run: |
helm template ci ./helm \
--set ingress.enabled=true \
--set ingress.hosts[0].host=ci.example.com \
--set ingress.hosts[0].paths[0].path=/ \
--set ingress.hosts[0].paths[0].pathType=Prefix \
> /dev/null
- name: helm template — existingSecret path
run: helm template ci ./helm --set secrets.existingSecret=external > /dev/null
- name: helm template — persistence disabled
run: helm template ci ./helm --set persistence.enabled=false > /dev/null
- name: helm hardening assertions
run: bash scripts/check-helm-hardening.sh
e2e:
name: e2e — Playwright against wrangler dev
runs-on: ubuntu-latest
timeout-minutes: 20
needs: test
# Mirrors AGENTS.md §11.1 item 8. Boots wrangler with the Worker's
# production Workers Assets bundle (both the single-sheet and React 19
# multi-sheet clients are served by that same Worker — no separate Vite
# server) via per-worker fixtures in packages/e2e, and exercises the
# HTTP surface + SPA mounts. Uploads the HTML report on failure so
# traces/screenshots are recoverable.
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
# voidzero-dev/setup-vp v1.1.0
- name: Setup Vite+
uses: voidzero-dev/setup-vp@e1609b468742ba9232ce5e12b3a77bdf14495277
with:
cache: true
- name: Install dependencies
run: vp install --frozen-lockfile
- name: Install Playwright browsers (chromium + system deps)
run: vp run @ethercalc/e2e#install:browsers
# Wrangler 4 reads `[assets] directory = "../../assets"` from
# wrangler.toml; if the dir is missing (or empty, which it is on
# this fresh runner) wrangler hangs on asset-manifest resolution
# — silently, with no stderr — and every fixture setup times out.
# The `test` job builds these before its own suite; `e2e` runs on
# a separate runner with a clean workspace, so rebuild here.
- name: client — Vite build (for assets/static/player.js)
run: vp run @ethercalc/client#build
- name: client-multi — Vite build (for assets/multi/)
run: vp run @ethercalc/client-multi#build
- name: Build curated assets/ dir
run: vp exec bun scripts/build-assets.ts
- name: Run Playwright suite
run: vp run @ethercalc/e2e#test
env:
CI: 'true'
- name: Upload Playwright HTML report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: playwright-report
path: packages/e2e/playwright-report/
retention-days: 7
if-no-files-found: ignore
- name: Upload trace + screenshots on failure
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: playwright-test-results
path: packages/e2e/test-results/
retention-days: 7
if-no-files-found: ignore
mutation-gate:
name: mutation-gate — changed packages only
runs-on: ubuntu-latest
# Budget (2026-08-10): local full six-package ratchet exits 0 in ~828s
# (~13m48s) on M-series; this job mutates only packages with `src/`
# changes vs origin/main (this branch: worker only — robots + rooms).
# Worker alone dominates wall-clock. CI ubuntu runners are slower than
# local, and run 31388973185 cancelled the Stryker step at
# timeout-minutes:20 with zero score output — a cancelled gate is
# indistinguishable from green at a glance and trains people to ignore
# it. 45m ≈ ~2.5× the local full-matrix baseline, deliberate headroom
# so the gate can finish and report a real score rather than timing out.
# Do NOT lower Stryker break thresholds to "fit" a short budget.
timeout-minutes: 45
needs: test
# Fast PR-gate mutation check. Runs Stryker only on packages whose
# `src/` changed in the PR, so docs/test/config-only PRs skip it and
# single-package PRs stay under the budget above. The full matrix lives
# in nightly.yml; see AGENTS.md §5.3 + docs/MUTATION_REPORT.md for the
# ratchet workflow.
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
fetch-depth: 0
# voidzero-dev/setup-vp v1.1.0
- name: Setup Vite+
uses: voidzero-dev/setup-vp@e1609b468742ba9232ce5e12b3a77bdf14495277
with:
cache: true
- name: Install dependencies
run: vp install --frozen-lockfile
- name: Detect changed packages
id: changed
# Override GH Actions' default `bash -eo pipefail` because grep
# exits non-zero when the pattern matches nothing, which is the
# expected case for docs/test/config-only PRs.
shell: bash {0}
run: |
# Compare the PR head against the merge-base with origin/main.
# On a push to main the base still resolves (merge-base with self
# is HEAD) — the diff is empty, so the Stryker step is skipped.
git fetch origin main --depth=1 || true
if git merge-base origin/main HEAD >/dev/null 2>&1; then
BASE=$(git merge-base origin/main HEAD)
else
BASE=HEAD~1
fi
changed=$(git diff --name-only "$BASE" HEAD \
| grep -oE '^packages/[^/]+/src/' \
| sort -u \
| sed 's|packages/||;s|/src/||' \
| tr '\n' ' ')
echo "packages=$changed" >> "$GITHUB_OUTPUT"
echo "Changed packages: ${changed:-<none>}"
- name: Run Stryker on changed packages
if: steps.changed.outputs.packages != ''
env:
CHANGED_PACKAGES: ${{ steps.changed.outputs.packages }}
run: |
while IFS= read -r pkg; do
[ -n "$pkg" ] || continue
if [[ ! "$pkg" =~ ^[A-Za-z0-9_-]+$ ]]; then
echo "::error::Unsafe package name in changed-file output"
exit 1
fi
if [ -f "packages/$pkg/stryker.conf.json" ]; then
echo "::group::mutation test — $pkg"
vp run --filter "./packages/$pkg" --fail-if-no-match mutation
echo "::endgroup::"
else
echo "skip: packages/$pkg has no stryker.conf.json"
fi
done < <(printf '%s' "$CHANGED_PACKAGES" | tr ' ' '\n')
- name: Upload mutation reports on failure
if: failure() && steps.changed.outputs.packages != ''
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: mutation-gate-reports
path: packages/*/reports/mutation/
retention-days: 7
if-no-files-found: ignore