Run the test suite against TypeScript sources behind an uncheatable coverage gate

Merged
UN
3mo ago
+418-17

Why

Coverage was unmeasured here. The suite imported the compiled output from dist/, so the only obtainable numbers pointed at generated JavaScript offsets an author cannot act on, and no threshold was wired into any gate.

What changed

  • Tests import the TypeScript source directly (../index.ts) instead of ../dist/index.js, so the coverage report lands on editable source lines. Node strips types natively from 22.18.0, the floor this package already declares in engines — no new tooling.
  • tsconfig.json enables rewriteRelativeImportExtensions + allowImportingTsExtensions. This package has no relative source imports today, so the published dist/ artifact is byte-identical; the flag keeps the door open for future multi-file sources.
  • scripts/coverage-gate.ts (copied verbatim from pm-changelog) enforces per-dimension thresholds and reconciles the reported file list against the configured source list. That second check is why it is a script, not a bare flag: Node omits files that never load from its coverage report entirely rather than reporting them at zero, so a threshold alone is satisfiable by narrowing what the suite imports.
  • release:check runs the gate in place of a bare test run; CI runs it on a Node 22 + 26 matrix.

Measured baseline (single source file index.ts)

dimensionmeasuredthreshold (floor)
lines70.5370
branches80.4280
functions80.4380

Thresholds are pinned at the measured floor so the number can only ratchet up.

Negative control

A throwaway __probe.ts source was added to coverageGate.sources. No test imported it, so the gate exited 1 and named __probe.ts as never loaded. After removing the probe the gate exited 0 again. The presence check is not vacuous.

pm item

Resolves pm-linear-v618 after merge.

Summary by Sourcery

Enforce a coverage gate over the TypeScript sources by running Node’s built-in test coverage against .ts test files and wiring the gate into local release checks and CI.

New Features:

  • Introduce a reusable coverage gate script that runs the Node test runner with coverage against TypeScript sources and enforces per-dimension thresholds plus source presence checks.

Enhancements:

  • Switch tests to import the TypeScript entrypoint instead of compiled JavaScript so coverage maps directly to authored source.
  • Add coverageGate configuration to package.json to declare covered sources, test entry patterns, thresholds, and ignores.
  • Enable rewriteRelativeImportExtensions and allowImportingTsExtensions in tsconfig to support direct .ts imports while keeping the emitted dist output stable.

Build:

  • Add a coverage npm script that builds the project and runs the coverage gate instead of a plain test run.
  • Update the release:check script to run the coverage gate in place of the bare test suite.

CI:

  • Update the CI workflow to run the coverage-gated test command on a Node 22 and 26 matrix instead of a simple Node 22 test run.

Chores:

  • Register the associated pm-linear item files under .agents/pm to track this change.

Summary by cubic

Run tests against the TypeScript source and enforce a coverage gate that also fails when untested files don’t appear in the report. Uses a dynamic source walk, pins TZ=UTC, and runs in CI on Node 22 and 26; satisfies Linear pm-linear-v618.

  • New Features

    • Tests import ../index.ts so coverage maps to authored lines; Node 22+ executes .ts.
    • Gate enforces thresholds (lines 70, branches 80, functions 80) and reconciles the report with a directory walk from coverageGate.sources; only required files are included.
    • npm run coverage builds, typechecks tests and the gate, then runs the gate; CI runs this job; coverage/ is gitignored; tsconfig enables rewriteRelativeImportExtensions and allowImportingTsExtensions; tests are noEmit with repo-root rootDir.
    • Baseline: lines 70.53, branches 80.42, functions 80.43.
  • Bug Fixes

    • Propagate the runner’s exit status before reading the coverage report, so real test failures show first.
    • Normalize lcov SF: paths to repo-relative POSIX to avoid false “never loaded” on Windows.
    • Clear error when a coverageGate.sources entry does not exist.
    • Delete previous coverage/lcov.info before the run to prevent passing on stale data.
    • Validate coverageGate.ignore entries are type-only against emitted JS (stripping block comments); resolve outDir/rootDir via tsc --showConfig and fail closed if it cannot be resolved; reject non-TS or .d.ts entries in coverageGate.sources.