Skip to content

feat: Add Node.js built-in test coverage per package #85

@adrianbrowning

Description

@adrianbrowning

Summary

Add code coverage reporting to each package in the monorepo using Node.js's built-in test runner (node:test) and the --experimental-test-coverage flag — no extra dependencies required.

Background

Node.js 20+ includes a built-in coverage tool via --experimental-test-coverage. Since the repo already uses node:test, this is a zero-dependency way to get line, branch, and function coverage per package.

Reference: https://nodejs.org/api/test.html#collecting-code-coverage


Tasks

Per-package (packages/*/package.json)

Add the following scripts to each package:

"scripts": {
  "test": "node --experimental-strip-types --test",
  "test:coverage": "node --experimental-strip-types --test --experimental-test-coverage --test-coverage-include='src/**/*.ts' --test-coverage-exclude='src/**/*.test.ts'",
  "test:coverage:ci": "node --experimental-strip-types --test --experimental-test-coverage --test-reporter=spec --test-reporter-destination=stdout --test-reporter=lcov --test-reporter-destination=lcov.info --test-coverage-include='src/**/*.ts'"
}

Use --experimental-strip-types for TypeScript support (Node 22.6+), or --import tsx/esm for older Node versions.

Root package.json

"scripts": {
  "test": "pnpm -r test",
  "test:coverage": "pnpm -r test:coverage"
}

Optional: Coverage thresholds

Enforce minimum coverage levels per package to prevent regressions:

--test-coverage-lines=80 --test-coverage-branches=70 --test-coverage-functions=80

Optional: node.test.config.mjs per package

For cleaner config (requires Node 22.8+):

// packages/*/node.test.config.mjs
export default {
  coverage: true,
  coverageIncludeGlobs: ['src/**/*.ts'],
  coverageExcludeGlobs: ['src/**/*.test.ts', '**/*.d.ts'],
  lineCoverage: 80,
  branchCoverage: 70,
  functionCoverage: 80,
};

Optional: CI integration

Upload lcov.info to Codecov or Coveralls in the GitHub Actions workflow for per-PR coverage diff reporting.


Notes

  • The lcov reporter produces no human-readable output on its own — pair it with spec or tap using --test-reporter twice
  • node_modules/ and core modules are excluded from coverage by default
  • Test files themselves are excluded from the coverage report by default; use --test-coverage-exclude to be explicit

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions