-
Notifications
You must be signed in to change notification settings - Fork 2
423 lines (413 loc) · 18.4 KB
/
Copy pathci.yml
File metadata and controls
423 lines (413 loc) · 18.4 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
name: ci
on:
push:
branches: [main]
tags: ['v*.*.*']
pull_request:
branches: [main]
workflow_dispatch:
schedule:
# Path classification is deliberately bypassed for this nightly browser
# safety net, catching omissions in the PR path map itself.
- cron: '31 2 * * *'
jobs:
# Keep the workflow itself unconditional: a required workflow skipped by an
# event-level paths filter can remain Pending forever. Conditional jobs are
# reported as successful skips by GitHub. The action is pinned to the v3
# commit rather than its mutable tag.
changes:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
outputs:
examples: ${{ steps.filter.outputs.examples }}
unit: ${{ steps.filter.outputs.unit }}
build: ${{ steps.filter.outputs.build }}
bundle: ${{ steps.filter.outputs.bundle }}
docker: ${{ steps.filter.outputs.docker }}
e2e: ${{ steps.filter.outputs.e2e }}
steps:
- uses: actions/checkout@v7
if: github.event_name == 'pull_request'
- uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706
if: github.event_name == 'pull_request'
id: filter
with:
filters: |
examples:
- 'examples/**'
- 'tests/unit/spec-examples.test.js'
unit:
- 'src/**'
- 'schemas/**'
# Keep this positive-only: paths-filter's default "some"
# quantifier treats a standalone negative rule as matching every
# other repository path. The example contract test intentionally
# overlaps the focused examples job when that test itself changes.
- 'tests/unit/**'
- 'tests/vitest.config.ts'
- 'build/**'
- 'tsconfig.json'
- 'package.json'
- 'package-lock.json'
- '.github/workflows/**'
# #630 Phase 2: the first npm workspace — its source and
# manifest are unit-test/typecheck inputs too.
- 'packages/**'
build:
- 'src/**'
- 'schemas/**'
- 'build/**'
- 'package.json'
- 'package-lock.json'
- '.github/workflows/**'
- 'packages/**'
bundle:
- 'src/**'
- 'schemas/**'
- 'build/**'
- 'deploy/**'
- 'install.sh'
- 'package.json'
- 'package-lock.json'
- '.github/workflows/**'
- 'packages/**'
# #564: the browser suite is the ONLY gate that can see CSS layout,
# container queries, real focus/hover, and IndexedDB — happy-dom sees
# none of it. Anything that can move rendered geometry or the
# harnesses themselves belongs here. `src/styles.css` is inside
# `src/**` and is the single biggest reason this filter exists.
e2e:
- 'src/**'
- 'schemas/**'
- 'tests/e2e/**'
# #630 Phase 1 named the shared fixture server
# (tests/spike/clickhouse-client/fault-server.mjs) here
# explicitly, since the root e2e suite imports it directly — a
# real cross-tree dependency the PR path filter didn't know
# about otherwise. #630 Phase 8 moved that fixture to
# packages/clickhouse-http/test/browser/fault-server.mjs,
# already covered by the blanket 'packages/**' entry below, so
# no dedicated entry is needed here anymore.
- 'playwright.config.js'
- 'build/**'
- 'package.json'
- 'package-lock.json'
- '.github/workflows/**'
- 'packages/**'
docker:
- 'Dockerfile'
- 'deploy/caddy/**'
- 'deploy/config.json.example'
- 'build/**'
- 'src/**'
- 'schemas/**'
- 'package.json'
- 'package-lock.json'
- '.github/workflows/**'
- 'packages/**'
# Example-only PRs still validate every portable bundle, authored Dashboard,
# filter contract, and generator normalization without paying for coverage,
# a production build, or real browsers.
examples:
needs: changes
if: github.event_name == 'pull_request' && needs.changes.outputs.examples == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: '22'
cache: npm
- run: npm ci --no-audit --no-fund
- name: Check normalized examples
run: node examples/mjs/normalize-examples.mjs --check
- name: Test example contracts
run: >-
npx vitest run tests/unit/spec-examples.test.js
--config tests/vitest.config.ts
--coverage.enabled=false
test:
needs: changes
if: >-
github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' && needs.changes.outputs.unit == 'true')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: '22'
cache: npm
# package-lock v3 records esbuild's platform packages as optional; npm ci
# installs only the Linux binary on this runner while keeping the complete
# dependency graph reproducible across local, CI, and release builds.
- run: npm ci --no-audit --no-fund
- name: Test (vitest + coverage gate)
run: npm test
- name: '@altinity/clickhouse-http isolated-package proof (npm pack, install outside the workspace, ESM + TS resolution)'
run: npm run test:clickhouse-http:pack
- name: Build single-file SPA
run: npm run build
- uses: actions/upload-artifact@v7
with:
name: sql-browser-dist
path: dist/sql.html
retention-days: 14
# Bundle-size report (#275): measure the self-contained artifact on every PR and
# upload it as an artifact — raw/gzip/Brotli, per-module + per-package attribution,
# and deltas vs. the PR base when a base report can be produced. Reporting only:
# it builds the same bytes as the release, never a budget that fails the build.
size:
needs: changes
if: >-
github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' && needs.changes.outputs.build == 'true')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
# Full history so the PR base commit is present for the base report below.
fetch-depth: 0
- uses: actions/setup-node@v7
with:
node-version: '22'
cache: npm
- run: npm ci --no-audit --no-fund
# Base report: check the PR base commit out into a worktree and run *its*
# size-report. Fully tolerant — the base predates this tooling on the first
# PR, so a failure here just means "no deltas", never a failed job.
- name: Base report (PR only, best-effort)
if: github.event_name == 'pull_request'
continue-on-error: true
run: |
set +e
base='${{ github.event.pull_request.base.sha }}'
git worktree add /tmp/base-tree "$base" || exit 0
( cd /tmp/base-tree \
&& npm ci --no-audit --no-fund \
&& npm run size-report -- --out /tmp/base-report ) || exit 0
cp /tmp/base-report/bundle-size-report.json base-report.json 2>/dev/null || true
- name: Size report
run: |
set -euo pipefail
if [ -f base-report.json ]; then
npm run size-report -- --base base-report.json
else
echo "No base report available — emitting current report without deltas."
npm run size-report
fi
- uses: actions/upload-artifact@v7
with:
name: bundle-size-report
path: |
bundle-report/bundle-size-report.json
bundle-report/bundle-size-report.md
bundle-report/esbuild-meta.json
retention-days: 14
# Release-bundle smoke test: assemble the curl|sh artifact, extract it, and boot
# the zero-dep Python runner exactly as an end user would — proving the bundle
# layout, the SPA-path discovery, and config.json generation all work before a
# tag ever cuts a real release.
bundle:
needs: changes
if: >-
github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' && needs.changes.outputs.bundle == 'true')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: '22'
cache: npm
- run: npm ci --no-audit --no-fund
- name: Build release bundle
run: build/bundle.sh
- name: Extract + boot the runner (as a user would)
run: |
set -euo pipefail
tmp=$(mktemp -d)
tar -C "$tmp" -xzf dist/altinity-sql-browser.tar.gz --strip-components=1
for f in sql.html local.py sql-browser.xml run.sh VERSION; do
test -f "$tmp/$f" || { echo "missing $f in bundle" >&2; exit 1; }
done
python3 -c "import ast,sys; ast.parse(open(sys.argv[1]).read())" "$tmp/local.py"
# No LOCAL_CH_CONFIG: the runner discovers the bundled sql-browser.xml
# next to local.py, proving the merge/discovery path end to end.
# SQL_BROWSER_PROBE=0: don't depend on reaching external demo hosts from CI.
SQL_BROWSER_PROBE=0 PORT=8901 "$tmp/run.sh" &
pid=$!
for i in $(seq 1 20); do curl -fsS "http://localhost:8901/sql" >/dev/null 2>&1 && break; sleep 0.5; done
curl -fsS "http://localhost:8901/sql" >/dev/null
curl -fsS "http://localhost:8901/config.json" \
| python3 -c "import sys,json; assert json.load(sys.stdin)['hosts'], 'no hosts parsed'"
kill "$pid"
- name: Lint the installer (shellcheck)
run: |
sudo apt-get update -qq && sudo apt-get install -y -qq shellcheck
# #630 Phase 8 modifies both build wrappers (each now composes the
# package build explicitly) — lint both alongside the existing root
# installer shell script.
shellcheck install.sh build/bundle.sh deploy/install.sh
# Exercise the published runtime shape, including Caddy's static
# Content-Encoding negotiation. Node decodes each selected sidecar, so the
# check does not depend on the runner curl being built with every codec.
docker-smoke:
needs: changes
if: >-
github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' && needs.changes.outputs.docker == 'true')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: '22'
cache: npm
- run: npm ci --no-audit --no-fund
- run: npm run build
- name: Build and run image
run: |
set -euo pipefail
docker build --build-arg ASB_COMMIT="${GITHUB_SHA}" --tag altinity-sql-browser:smoke .
docker run --detach --rm --name asb-smoke --security-opt no-new-privileges \
-p 18080:8080 altinity-sql-browser:smoke
trap 'docker logs asb-smoke; docker stop asb-smoke' EXIT
for i in $(seq 1 20); do
curl --fail --silent http://127.0.0.1:18080/healthz >/dev/null && break
sleep 0.5
done
curl --fail --silent http://127.0.0.1:18080/healthz >/dev/null
# The Docker build receives GitHub's merge-commit stamp while this
# checkout carries the PR-head stamp. Compare negotiated variants to
# the runtime identity response, rather than to the separately built
# checkout file, so the assertion covers exactly the image bytes.
for encoding in identity br zstd gzip; do
curl --fail --silent --show-error \
-H "Accept-Encoding: $encoding" \
-D "headers-$encoding.txt" \
http://127.0.0.1:18080/sql > "wire-$encoding"
grep -qi '^content-type: text/html' "headers-$encoding.txt"
grep -qi '^vary:.*accept-encoding' "headers-$encoding.txt"
grep -qi "^content-security-policy: default-src 'none'" "headers-$encoding.txt"
grep -qi '^x-content-type-options: nosniff' "headers-$encoding.txt"
grep -qi '^referrer-policy: no-referrer' "headers-$encoding.txt"
if [ "$encoding" = identity ]; then
! grep -qi '^content-encoding:' "headers-$encoding.txt"
else
grep -qi "^content-encoding: $encoding" "headers-$encoding.txt"
fi
node --input-type=module - "$encoding" "wire-$encoding" "body-$encoding.html" <<'NODE'
import { readFile, writeFile } from 'node:fs/promises';
import { brotliDecompress, gunzip, zstdDecompress } from 'node:zlib';
import { promisify } from 'node:util';
const [encoding, input, output] = process.argv.slice(2);
const compressed = await readFile(input);
const decompress = {
br: promisify(brotliDecompress),
zstd: promisify(zstdDecompress),
gzip: promisify(gunzip),
}[encoding];
await writeFile(output, decompress ? await decompress(compressed) : compressed);
NODE
if [ "$encoding" = identity ]; then
cp "body-$encoding.html" expected-body.html
else
cmp expected-body.html "body-$encoding.html"
fi
done
curl --fail --silent -H 'Accept-Encoding: gzip, zstd, br' -D preferred-headers.txt \
http://127.0.0.1:18080/sql > /dev/null
grep -qi '^content-encoding: br' preferred-headers.txt
curl --fail --silent -D healthz-headers.txt http://127.0.0.1:18080/healthz > healthz-body.txt
test "$(cat healthz-body.txt)" = ok
grep -qi '^cache-control: no-store' healthz-headers.txt
curl --fail --silent -D config-headers.txt http://127.0.0.1:18080/sql/config.json > config-body.json
grep -qi '^content-type: application/json' config-headers.txt
grep -qi '^cache-control: no-store' config-headers.txt
grep -qi '^x-content-type-options: nosniff' config-headers.txt
grep -qi '^referrer-policy: no-referrer' config-headers.txt
node -e 'const chunks=[]; process.stdin.on("data", c => chunks.push(c)); process.stdin.on("end", () => JSON.parse(Buffer.concat(chunks)));' < config-body.json
# Nightly/manual/release real-browser regression tests (Playwright). Keep the
# costly browser matrix out of ordinary PR and branch-push CI; unit tests
# cover each commit, while release tags validate the shipped image/artifact.
# The harness imports /src directly over a python http.server (started by the
# Playwright config's webServer), so no build step is needed.
# #564: until now this ran ONLY for tags, schedule and manual dispatch, so it
# reported `skipped` on every pull request and every push to `main` while the
# overall run still went green. A PR check that never runs is not a check: two
# `tile-open-workbench` specs had been failing on all three engines on `main`
# (#565) and nothing surfaced it until the workflow was dispatched by hand.
#
# Pull requests now get a **Chromium-only** run, path-gated on anything that
# can move rendered geometry — cheap enough to sit on every relevant PR, and
# both #565 failures reproduce on Chromium, so one engine would have caught
# them. Tags, nightly and manual dispatch keep the full three-engine matrix,
# because engine-specific breakage is real here (WebKit focus/IndexedDB
# behaviour especially) and that is what a release must clear.
e2e:
needs: changes
if: >-
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) ||
github.event_name == 'schedule' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' && needs.changes.outputs.e2e == 'true')
runs-on: ubuntu-latest
env:
# Root SQL Browser e2e: one engine on PRs, all three everywhere else
# (unchanged cost policy — #630 Phase 8 does not widen this).
PR_ONLY_CHROMIUM: ${{ github.event_name == 'pull_request' }}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: '22'
cache: npm
- run: npm ci --no-audit --no-fund
# #630 Phase 8: the @altinity/clickhouse-http package's own Chromium/
# WebKit regression suite runs on every applicable CI event alongside
# the root suite (issue #630's own required acceptance engines for the
# package), so WebKit is installed on PRs too now, not just Chromium —
# widened from Chromium-only specifically for this package suite.
- name: Install Playwright browsers
run: |
if [ "$PR_ONLY_CHROMIUM" = "true" ]; then
npx playwright install --with-deps chromium webkit
else
npx playwright install --with-deps chromium firefox webkit
fi
- name: E2E (Playwright)
run: |
if [ "$PR_ONLY_CHROMIUM" = "true" ]; then
npm run test:e2e -- --project=chromium
else
npm run test:e2e
fi
- name: '@altinity/clickhouse-http Chromium+WebKit regression suite'
run: npm run test:clickhouse-http:browser -- --project=chromium --project=webkit
- uses: actions/upload-artifact@v7
if: ${{ failure() }}
with:
name: playwright-results
path: test-results/
retention-days: 14
# Require this stable check in branch protection. It runs even when some
# expensive jobs were correctly skipped and fails if any applicable job did.
gate:
name: CI gate
if: always()
needs: [changes, examples, test, size, bundle, docker-smoke, e2e]
runs-on: ubuntu-latest
steps:
- name: Verify applicable jobs
if: >-
contains(needs.*.result, 'failure') ||
contains(needs.*.result, 'cancelled')
run: exit 1
- run: echo 'All applicable CI jobs passed.'