Repository navigation
173 lines (148 loc) · 7.97 KB
/
Copy pathci.yml
File metadata and controls
173 lines (148 loc) · 7.97 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
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).
#
# ⚠️ GITHUB-HOSTED RUNNERS, ON PURPOSE. This repository is PUBLIC. A self-hosted runner must never run
# a public repository's workflows: any fork can open a pull request, and `pull_request` workflows
# execute the PR's own code — on a self-hosted machine that is arbitrary code execution on a host that
# also holds other repositories' checkouts and credentials. The org's runner group refuses public
# repositories for exactly that reason (allows_public_repositories=false), so a self-hosted label here
# would not even start. Standard GitHub-hosted runners are free for public repositories, which also
# removes the old "hosted minutes are exhausted" constraint. Linux runners (ubuntu-latest) are the
# cheapest and most plentiful; `pnpm ci:local` runs the same gate on a developer's Mac.
#
# What follows from hosted runners: every run starts on a clean VM, so there is no leaked-process or
# stale-port hygiene to do between runs (the port steps in the e2e job are kept; they are cheap and
# still guard against a hung harness inside one run).
on:
push:
branches: [main]
pull_request:
# Least privilege: nothing in this workflow writes to the repository. A job that needs more asks for it
# itself.
permissions:
contents: read
# 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: ubuntu-latest
# 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: 45
steps:
- uses: actions/checkout@v7
# 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@v6
# 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@v7
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
# Every publishable package is public, carries npm's metadata and ships the one version the SDK
# reports about itself — so a release never discovers a drift at publish time.
- name: Publishable packages
run: pnpm check:publishable
# 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. An adversarial review
# identified that as the root cause of most of its ~90 severe 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.
#
# Hosted runners are provisioned per job, so the two jobs run in parallel.
e2e:
name: E2E (node · bun · deno · real frameworks)
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v7
- name: Install pnpm
uses: pnpm/action-setup@v6
# No `cache: pnpm` — see the note in the `check` job.
- name: Setup Node
uses: actions/setup-node@v7
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.
# On a fresh hosted VM this always downloads; it is a few seconds, not a failure.
- name: Install Playwright's Chromium
run: pnpm --filter @bugsee/instrumentation-tests exec playwright install chromium
# The nuxt/sveltekit/astro harnesses each boot a real dev server on a FIXED port. A fresh hosted VM
# starts clean, so this only guards a hung harness within one run (and a developer's machine, where
# the same script is used). 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