Skip to content

chore(deps): Bump the npm-minor-patch group with 2 updates (#258) #487

chore(deps): Bump the npm-minor-patch group with 2 updates (#258)

chore(deps): Bump the npm-minor-patch group with 2 updates (#258) #487

Workflow file for this run

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