Skip to content

Publish ChatGPT review skill documentation #1367

Publish ChatGPT review skill documentation

Publish ChatGPT review skill documentation #1367

Workflow file for this run

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/**'
build:
- 'src/**'
- 'schemas/**'
- 'build/**'
- 'package.json'
- 'package-lock.json'
- '.github/workflows/**'
bundle:
- 'src/**'
- 'schemas/**'
- 'build/**'
- 'deploy/**'
- 'install.sh'
- 'package.json'
- 'package-lock.json'
- '.github/workflows/**'
# #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/**'
- 'playwright.config.js'
- 'build/**'
- 'package.json'
- 'package-lock.json'
- '.github/workflows/**'
docker:
- 'Dockerfile'
- 'deploy/caddy/**'
- 'deploy/config.json.example'
- 'build/**'
- 'src/**'
- 'schemas/**'
- 'package.json'
- 'package-lock.json'
- '.github/workflows/**'
# 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: 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
shellcheck install.sh build/bundle.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:
# One engine on PRs, all three everywhere else.
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
- name: Install Playwright browsers
run: |
if [ "$PR_ONLY_CHROMIUM" = "true" ]; then
npx playwright install --with-deps chromium
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
- 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.'