chore(deps): Bump the npm-minor-patch group with 2 updates (#258) #487
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 | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| # Least privilege: CI only reads the checked-out code. Overrides any broader | |
| # repository-default GITHUB_TOKEN grant. | |
| permissions: | |
| contents: read | |
| jobs: | |
| # Runner routing: the shared org-level self-hosted `copse-checks` pool (the | |
| # unified runner image in copse-dev/agent-pane's ci-runners/, registered at | |
| # org scope) when the CHECKS_RUNNER Actions variable is set, else | |
| # GitHub-hosted: | |
| # CHECKS_RUNNER=copse-checks -> the org self-hosted pool | |
| # (unset or '') -> ubuntu-latest | |
| # Same variable-based scheme as agent-pane's ci.yml (see its routing note). | |
| # This replaced a `pick-runner` probe job that spent a whole job (and, on | |
| # private repos, a billed minute) per run deciding this. This repo is PUBLIC, | |
| # so hosted minutes are free — the pool is purely a warm-cache/speed win | |
| # here. If the org runner group doesn't allow public repositories, set a | |
| # repo-level CHECKS_RUNNER with an empty value to override the org variable | |
| # and stay hosted (otherwise jobs would queue forever). | |
| # | |
| # Fork-PR safety: fork PRs are forced onto GitHub-hosted in `runs-on` itself, | |
| # so untrusted code never lands on a self-hosted box (GitHub also withholds | |
| # secrets from fork runs). | |
| build: | |
| # Fork PRs always take hosted; otherwise CHECKS_RUNNER decides (see above). | |
| runs-on: >- | |
| ${{ (github.event_name == 'pull_request' && | |
| github.event.pull_request.head.repo.full_name != github.repository) | |
| && 'ubuntu-latest' | |
| || (vars.CHECKS_RUNNER || 'ubuntu-latest') }} | |
| # Runs on Node 22, the primary dev/publish version. The dev toolchain | |
| # itself requires 21+ (`devEngines.runtime >= 21`, which npm enforces at | |
| # install time; the `npm test` glob also relies on the runner expansion | |
| # added in Node 21), so the full workflow cannot run on the Node 20 | |
| # consumer floor. The declared `engines.node >= 20` floor is validated the | |
| # right way by the `smoke` job below: build here, then import the built | |
| # `dist/` entry under Node 20. | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| - run: npm ci | |
| - name: Typecheck | |
| run: npm run typecheck | |
| # Runs the full unit suite under c8, then the coverage-baseline.json | |
| # ratchet (fails if line coverage drops below the committed baseline). The | |
| # two absolute throughput-floor perf checks self-skip under instrumentation | |
| # — the relative O(n²) scaling-regression guard still runs. See | |
| # scripts/coverage-gate.mts. | |
| # Fetch the GFM spec.txt (gitignored, not on npm) from its pinned upstream | |
| # tag and verify its SHA-256 — the GFM conformance test in the suite below | |
| # needs it present. Same fetch-and-verify pattern as the reference normalizer. | |
| - name: Fetch + verify GFM spec | |
| run: npm run check:gfm-spec | |
| - name: Test + coverage | |
| run: npm run coverage:ci | |
| - name: Upload coverage report | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: coverage-report | |
| path: coverage/lcov-report | |
| if-no-files-found: ignore | |
| - name: Build | |
| run: npm run build | |
| # Differentially validate the JS CommonMark normalizer against the | |
| # reference normalize.py. The script fetches normalize.py from a pinned, | |
| # hash-verified upstream commit (not checked in) and runs the diff with | |
| # python3 (preinstalled on the runner; also present in the ci-runners image). | |
| - name: Normalizer parity | |
| run: npm run check:normalizer-parity | |
| # Gate the living docs against the baseline JSONs: fail if any conformance | |
| # number cited in docs/ARCHITECTURE.md no longer matches what the fixtures | |
| # produce. The report generator only helps if someone remembers to run it; | |
| # this makes CI remember, so the prose can't silently drift again. | |
| - name: Conformance numbers in docs | |
| run: npm run report:conformance:check | |
| # Consumer-floor smoke test (#113). `engines.node` is `>=20`, but the dev | |
| # toolchain needs 21+, so the `build` job can't run on 20. This job builds | |
| # with the dev toolchain on Node 22, then switches the runner to Node 20 and | |
| # imports the built `dist/` entry — exercising the declared floor without the | |
| # dev-only workflow. A syntax/API in the SHIPPED output that only lands in 21+ | |
| # reddens here rather than reaching a consumer on the floor. | |
| smoke: | |
| runs-on: >- | |
| ${{ (github.event_name == 'pull_request' && | |
| github.event.pull_request.head.repo.full_name != github.repository) | |
| && 'ubuntu-latest' | |
| || (vars.CHECKS_RUNNER || 'ubuntu-latest') }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Set up dev toolchain (Node 22) and build | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| - run: npm ci | |
| - name: Build | |
| run: npm run build | |
| # Switch the active runtime to the consumer floor. node_modules from the | |
| # Node 22 install persists (the peers are pure JS), and the built entry | |
| # imports its optional peers lazily, so no reinstall is needed on Node 20. | |
| - name: Switch to consumer floor (Node 20) | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 20 | |
| - name: Import built dist entry on Node 20 | |
| run: | | |
| node --version | |
| node -e "import('./dist/index.js').then((m) => { \ | |
| if (typeof m.renderMarkdownUnsafe !== 'function') { \ | |
| console.error('renderMarkdownUnsafe missing from built entry'); process.exit(1) \ | |
| } \ | |
| const html = m.renderMarkdownUnsafe('# hi\n\n**bold**'); \ | |
| if (!html.includes('<h1>') || !html.includes('<strong>')) { \ | |
| console.error('unexpected render output on Node 20:', html); process.exit(1) \ | |
| } \ | |
| console.log('dist entry imports and renders on', process.version) \ | |
| }).catch((e) => { console.error(e); process.exit(1) })" | |
| # Trusted Types e2e (#113). Runs the shipped bundle in a real, headless | |
| # Chromium under an ENFORCED `require-trusted-types-for 'script'` CSP — | |
| # something jsdom cannot simulate — so the "sanitize at the sink / | |
| # Trusted-Types-safe" headline claim has a gate. `tests/trusted-types.e2e.test.ts` | |
| # SKIPS (never fails) when no Chromium is found, so we install the | |
| # playwright-core-matched browser here to guarantee the five assertions | |
| # actually execute; a red assertion then fails the job. | |
| e2e: | |
| runs-on: >- | |
| ${{ (github.event_name == 'pull_request' && | |
| github.event.pull_request.head.repo.full_name != github.repository) | |
| && 'ubuntu-latest' | |
| || (vars.CHECKS_RUNNER || 'ubuntu-latest') }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| - run: npm ci | |
| # Cache the playwright-core Chromium download (~170MB) across runs so it | |
| # isn't re-fetched every time. Keyed on the locked playwright-core version | |
| # so a version bump busts the cache and pulls the matched build. | |
| - name: Cache Playwright browsers | |
| uses: actions/cache@v6 | |
| with: | |
| path: ~/.cache/ms-playwright | |
| key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }} | |
| # Install the Chromium build that matches the locked playwright-core. | |
| # Idempotent and cached; on the self-hosted image (browser preinstalled) | |
| # this is a fast no-op. `findChromium()` then resolves it via | |
| # playwright-core's registry. | |
| - name: Install Chromium (playwright-core) | |
| run: npx --no-install playwright-core install chromium | |
| # `E2E_REQUIRE_BROWSER=1` turns the suite's browser-absent SKIP into a | |
| # hard failure, so the "five assertions actually ran" guarantee is | |
| # explicit here rather than only implied by the install step succeeding. | |
| - name: Trusted Types e2e (headless Chromium) | |
| run: npm run test:e2e | |
| env: | |
| E2E_REQUIRE_BROWSER: '1' | |
| # Streaming performance guard (#113). The coverage job's absolute throughput | |
| # floors self-skip under c8 instrumentation, so streaming perf had no CI | |
| # guard. This runs the bench at full speed on its fixed fixture set; the | |
| # script carries a RELATIVE regression guard (mean DOM-path growth per input | |
| # doubling must stay < 3× — an O(n²) committed-re-render regression pushes it | |
| # back toward 4× and throws, failing the job). The full table is also | |
| # published as an artifact so the numbers are visible per run. | |
| bench: | |
| runs-on: >- | |
| ${{ (github.event_name == 'pull_request' && | |
| github.event.pull_request.head.repo.full_name != github.repository) | |
| && 'ubuntu-latest' | |
| || (vars.CHECKS_RUNNER || 'ubuntu-latest') }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| - run: npm ci | |
| - name: Bench (fixed fixtures, relative-regression guard) | |
| # `shell: bash` runs `bash -eo pipefail`, so a non-zero exit from the | |
| # bench guard propagates through the `tee` pipe and fails the job. The | |
| # default Linux shell (`bash -e`, no pipefail) would take `tee`'s exit | |
| # code (0) and stay green, defeating the guard. | |
| shell: bash | |
| # The bench streams several large fixtures through jsdom back to back | |
| # (footnote #110, incremental-scan #111, lazy-quote sections) — and | |
| # jsdom permanently retains the DOMPurify string path's per-call | |
| # DOMParser document (~1 MB per sanitize at bench-sized inputs; it | |
| # survives forced GC, and bare DOMPurify-on-jsdom reproduces it with | |
| # no library code involved — see #231). Thousands of string-path | |
| # sanitizes accumulate multiple GB over one run, so the ceiling must | |
| # clear the whole FIXED-SIZE workload with margin: 6144 sat within | |
| # ~1 GB of the peak and any allocation growth in a PR tipped it into | |
| # OOM (exit 134). jsdom-only — browsers and the sanitizeInto fragment | |
| # path do not retain. The durable fix (per-section subprocesses) is | |
| # tracked in #231. | |
| env: | |
| NODE_OPTIONS: --max-old-space-size=12288 | |
| run: npm run bench | tee bench-output.txt | |
| - name: Upload bench output | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: bench-output | |
| path: bench-output.txt | |
| if-no-files-found: ignore | |
| # Bundle-size gate (#113). The core value prop is a small main entry | |
| # (~28–30 KB gzipped+minified) with the heavy backends behind lazy subpaths. | |
| # `scripts/check-bundle-size.mts` bundles the main entry and each key subpath | |
| # from dist/ as a consumer would (peers external) and fails if any gzipped | |
| # size exceeds the committed budget in scripts/bundle-size-budget.json — so a | |
| # dependency leaking back into the core bundle reddens the PR. Budgets are | |
| # bumped deliberately with `npm run size:update`. | |
| size: | |
| runs-on: >- | |
| ${{ (github.event_name == 'pull_request' && | |
| github.event.pull_request.head.repo.full_name != github.repository) | |
| && 'ubuntu-latest' | |
| || (vars.CHECKS_RUNNER || 'ubuntu-latest') }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| - run: npm ci | |
| - name: Build | |
| run: npm run build | |
| - name: Bundle-size gate | |
| run: npm run size | |
| # Canary: typecheck + build against the next TypeScript major (7.x, the | |
| # Go-native compiler) before the `typescript` devDependency crosses the | |
| # `^6` cap. `continue-on-error: true` makes this advisory — a break here is | |
| # visible but never blocks a required check or reddens an unrelated PR. | |
| # `npm i typescript@7 --no-save` overrides the resolved compiler for this run | |
| # only, leaving package.json / package-lock.json untouched. Safe because this | |
| # repo touches TypeScript exclusively through the `tsc` CLI (typecheck + | |
| # build) — it uses no programmatic compiler API, which isn't stable until 7.1. | |
| # Remove this job once the `^7` bump lands and `build` covers it directly. | |
| typescript-7-canary: | |
| runs-on: ubuntu-latest | |
| continue-on-error: true | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| - run: npm ci | |
| - name: Install TypeScript 7 (this run only) | |
| run: npm i -D typescript@7 --no-save | |
| - name: Typecheck (TS 7) | |
| run: npm run typecheck | |
| - name: Build (TS 7) | |
| run: npm run build |