Repository navigation
Merge pull request #5 from bugsee/fix/hyphenated-sensitive-keys #118
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| # Runs the full per-change gate on every push to main and every pull request: | |
| # lint (biome) → typecheck (tsc via turbo) → dependency-cycle check (madge) → | |
| # tests with the per-package coverage gate (vitest via turbo — each package | |
| # enforces its own 100% line/fn/stmt + ≥90% branch threshold). | |
| # | |
| # ⚠️ BOTH JOBS RUN ON A SELF-HOSTED arm64 macOS RUNNER (hosted Actions minutes are | |
| # exhausted — the appserver repo moved for the same reason). Three things follow, and | |
| # every one of them has a counterpart below: | |
| # | |
| # 1. THE HOST OUTLIVES THE RUN. Leaked processes, held ports and stale directories | |
| # survive into the NEXT run instead of dying with a VM, and show up there as | |
| # flakiness. See the port-hygiene steps around the e2e suites. | |
| # 2. THE HOST IS SHARED. A hung job holds the runner against every other repo | |
| # queued behind it, so both jobs carry an explicit `timeout-minutes`. | |
| # 3. IT IS FAST, and that is not purely good news. The old ubuntu runner was | |
| # roughly 7-18x slower than a dev machine, which papered over races by never | |
| # losing them; a runner at dev-machine speed can lose races the hosted one | |
| # always won. (That is not hypothetical — it is exactly how a start-up race | |
| # surfaced in the appserver deploy job after its move.) A test that newly fails | |
| # here is more likely a real race than a bad runner. | |
| # | |
| # The platform changed with the move: linux-x64 → darwin-arm64. That is deliberate | |
| # parity with how the gate is actually developed — `pnpm ci:local` has always run it | |
| # on macOS — but it does mean CI no longer exercises Linux at all. | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| # One in-flight run per ref; a new push cancels the previous run. | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| check: | |
| name: Lint · Typecheck · Cycles · Coverage | |
| runs-on: [self-hosted, macOS, ARM64] | |
| # The runner is shared. Without a bound, a hung vitest worker holds it against | |
| # every other repo's queue until someone notices by hand. | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # pnpm first (setup-node's pnpm cache needs pnpm on PATH). Version comes from | |
| # the root package.json `packageManager` field (pnpm@11.3.0). | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v4 | |
| # vitest 4 (rolldown) requires Node ≥ 20; the SDK targets Node ≥ 18 at runtime, | |
| # but the toolchain runs on 22. | |
| # | |
| # NO `cache: pnpm` since the move. That option round-trips the pnpm store through | |
| # GitHub's cache service, which only pays off when the machine is thrown away. Here | |
| # the store lives at ~/Library/pnpm/store and survives every run — actions/checkout | |
| # wipes node_modules (`git clean -ffdx`) but never touches the store — so the cache | |
| # would be uploading and re-downloading something already on disk. | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Lint (biome) | |
| run: pnpm lint | |
| - name: Typecheck (tsc) | |
| run: pnpm typecheck | |
| - name: Dependency cycles (madge) | |
| run: pnpm check:cycles | |
| # Per-package coverage gate (turbo runs each package's own test:coverage, | |
| # each enforcing 100% line/fn/stmt + ≥90% branch). NOT the root merged | |
| # `pnpm test:coverage`, whose single aggregate threshold the scaffold-only | |
| # stub packages would drag below 100%. | |
| - name: Test + per-package coverage gate (vitest) | |
| run: pnpm exec turbo run test:coverage | |
| # Harness-owned unit tests (e.g. the bundle-assertion library's negative suite). These live in a | |
| # package with no test:coverage script, so `turbo run test:coverage` skipped them silently — the same | |
| # <NONEXISTENT> hole the e2e job exists to close. | |
| - name: Harness unit tests (vitest) | |
| run: pnpm exec turbo run test:unit | |
| # End-to-end harnesses: the REAL SDK booted in real node/bun/deno processes against a | |
| # mock collector, plus the real-framework suites (nuxt / sveltekit / astro / next). | |
| # | |
| # These existed but were OUTSIDE the gate until 2026-07-27: the `check` job runs | |
| # `turbo run test:coverage`, and the four harness packages define only `test:e2e`, so | |
| # turbo reported `<NONEXISTENT>` and skipped them silently. The adversarial review | |
| # (docs/review/e2e-harnesses.md, SEV1 #1) identified that as the root cause of most of | |
| # its ~90 SEV1 findings surviving ~100% unit coverage. | |
| # | |
| # Separate job so it runs alongside `check` and so its extra runtime setup | |
| # (bun, deno) is isolated. Bun and Deno are installed deliberately: the harness SKIPS | |
| # a runtime whose binary is absent (packages/instrumentation-tests/test/runtimes.ts), | |
| # so without them CI would go green while testing strictly less. | |
| # | |
| # "Alongside" is now aspirational rather than guaranteed: two jobs only run at once if | |
| # the pool has two free runners, and a single self-hosted runner serialises them. That | |
| # costs wall-clock, not correctness, and adding a runner restores the parallelism | |
| # without touching this file. | |
| e2e: | |
| name: E2E (node · bun · deno · real frameworks) | |
| runs-on: [self-hosted, macOS, ARM64] | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v4 | |
| # No `cache: pnpm` — see the note in the `check` job. | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| - name: Setup Deno | |
| uses: denoland/setup-deno@v2 | |
| with: | |
| deno-version: v2.x | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| # Fail loudly if a runtime the harness expects is missing, rather than letting | |
| # runtimes.ts silently skip it and report green. | |
| # | |
| # ⚠️ These three commands were never the thing that could go missing. What the node target | |
| # actually needs is `tsx`, and the harness resolved it only from the WORKSPACE ROOT's | |
| # node_modules/.bin — where pnpm does not put a package's own devDependency, and where | |
| # `git clean -ffdx` removes the stale copy a developer's machine happens to have. So node | |
| # resolved to `undefined` here for as long as this job has existed, its whole instrumentation | |
| # suite was skipped, and the run went green on 133 tests where a developer ran 195. The real | |
| # guard is now inside the harness (instrumentation.e2e.ts THROWS when node does not resolve); | |
| # these version checks remain as a fast, readable failure for a genuinely missing runtime. | |
| - name: Verify the runtime matrix is complete | |
| run: | | |
| node --version | |
| bun --version | |
| deno --version | |
| # Playwright ships a downloader, not the browsers: `pnpm install` does not fetch them, so | |
| # `browser.e2e.ts` fails with "Executable doesn't exist at .../chrome-headless-shell". | |
| # Scoped to chromium — it is the only engine the browser harness drives. Like the pnpm store, | |
| # ~/Library/Caches/ms-playwright survives on this self-hosted machine, so after the first run | |
| # this step is a no-op rather than a download. | |
| - name: Install Playwright's Chromium | |
| run: pnpm --filter @bugsee/instrumentation-tests exec playwright install chromium | |
| # ⚠️ SELF-HOSTED HYGIENE. The nuxt/sveltekit/astro harnesses each boot a real dev | |
| # server on a FIXED port. On a hosted VM a leaked server died with the VM; here the | |
| # machine persists, and this workflow sets `cancel-in-progress`, so a superseded | |
| # run's dev-server child can outlive its parent and keep holding the port. The next | |
| # run then fails with EADDRINUSE on a change that is perfectly fine — leftover state | |
| # wearing the costume of a flaky suite. The script reads the ports out of the | |
| # harnesses and fails if it can no longer find them, so moving a port cannot quietly | |
| # disarm it. | |
| - name: Free the harness ports | |
| run: scripts/free-e2e-ports.sh | |
| - name: E2E suites (turbo run test:e2e) | |
| run: pnpm exec turbo run test:e2e | |
| # `always()` so a CANCELLED or failed run cleans up after itself instead of leaving | |
| # the mess for whoever runs next — which is the run that would get blamed for it. | |
| - name: Release the harness ports | |
| if: always() | |
| run: scripts/free-e2e-ports.sh |