🔀 Merge chore/coverage-threshold into main
This commit is contained in:
commit
eba60f6569
9 files changed
+75
-7
No files matched your search
@@ -77,6 +77,9 @@ jobs:
|
|||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
- run: npm run check
|
- run: npm run check
|
||||||
|
# Fails the build below 100% coverage on `src/` (`c8 --all --100`);
|
||||||
|
# the same run produces the report published below. See
|
||||||
|
# development/ci.md § Coverage threshold.
|
||||||
- run: npm run test:ci
|
- run: npm run test:ci
|
||||||
# Publish this tag's coverage to the self-hosted pages server,
|
# Publish this tag's coverage to the self-hosted pages server,
|
||||||
# served read-only at
|
# served read-only at
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
- gate CI at 100% coverage: `test:ci` runs c8 with `--all --100` over `src/`,
|
||||||
|
and a test loads the `index.ts` barrel so it is measured
|
||||||
- pin the duplicate-tag union collapse (members sharing a tag dispatch through
|
- pin the duplicate-tag union collapse (members sharing a tag dispatch through
|
||||||
one handler) with tests
|
one handler) with tests
|
||||||
- track a TypeScript 5.0 baseline in the backlog
|
- track a TypeScript 5.0 baseline in the backlog
|
||||||
|
|||||||
+3
-2
@@ -41,7 +41,7 @@ faster tiers catch less, slower tiers are more thorough":
|
|||||||
| `npm run verify` | manual | Definition of done: `npm run check` + unit tests, one shot | ~6s |
|
| `npm run verify` | manual | Definition of done: `npm run check` + unit tests, one shot | ~6s |
|
||||||
| `npm run fix` | manual | Auto-resolve fixable issues (lint, format) | ~3s |
|
| `npm run fix` | manual | Auto-resolve fixable issues (lint, format) | ~3s |
|
||||||
| `npm run maintain` | manual / CI (advisory) | `maintain:knip` + `maintain:outdated` (whole-project + network scans) | ~10s |
|
| `npm run maintain` | manual / CI (advisory) | `maintain:knip` + `maintain:outdated` (whole-project + network scans) | ~10s |
|
||||||
| CI build (auto) | on push to `main` / tag | `build` job (build + correctness + packaging) — see [.gitea/workflows/ci.yml](./.gitea/workflows/ci.yml) | ~30s+ |
|
| CI build (auto) | on push to `main` / tag | `build` job (build + correctness + coverage + packaging) — see [.gitea/workflows/ci.yml](./.gitea/workflows/ci.yml) | ~30s+ |
|
||||||
| CI maintain (auto, non-blocking) | on push to `main` | `npm run maintain` — reports, never fails the build | ~10s |
|
| CI maintain (auto, non-blocking) | on push to `main` | `npm run maintain` — reports, never fails the build | ~10s |
|
||||||
| CI publish (auto) | on tag | packaging checks + `publish:publint` / `publish:attw`, then the Gitea release page and `npm publish` (skipped, and the job failed, without `NPM_TOKEN`) | ~15s |
|
| CI publish (auto) | on tag | packaging checks + `publish:publint` / `publish:attw`, then the Gitea release page and `npm publish` (skipped, and the job failed, without `NPM_TOKEN`) | ~15s |
|
||||||
|
|
||||||
@@ -125,7 +125,8 @@ CI step. Pick the prefix that matches the script's lifecycle:
|
|||||||
`npm run fix`; the diff is the review surface.
|
`npm run fix`; the diff is the review surface.
|
||||||
- `test:*` — test scripts. `test` is the canonical entry point (`check:tsc` +
|
- `test:*` — test scripts. `test` is the canonical entry point (`check:tsc` +
|
||||||
unit tests); `test:unit` skips the typecheck for fast local iteration;
|
unit tests); `test:unit` skips the typecheck for fast local iteration;
|
||||||
`test:ci` adds c8 coverage.
|
`test:ci` runs the suite under c8 and fails below 100% coverage on `src/`
|
||||||
|
(CI-only; `verify` stays coverage-free).
|
||||||
- `watch:*` — long-running watchers for the manual inner dev loop. Aggregated by
|
- `watch:*` — long-running watchers for the manual inner dev loop. Aggregated by
|
||||||
`watch`.
|
`watch`.
|
||||||
- `maintain:*` — advisory repo-maintenance scans: read-only, but whole-project
|
- `maintain:*` — advisory repo-maintenance scans: read-only, but whole-project
|
||||||
|
|||||||
+3
-3
@@ -12,9 +12,9 @@ v1.0:
|
|||||||
☐ Finalize public exports in `src/index.ts`
|
☐ Finalize public exports in `src/index.ts`
|
||||||
☐ Document all exported types and functions
|
☐ Document all exported types and functions
|
||||||
☐ Add JSDoc for public APIs
|
☐ Add JSDoc for public APIs
|
||||||
☐ Test coverage meets threshold
|
✔ Test coverage meets threshold @done
|
||||||
☐ Achieve 100% branch coverage on `src/primitive-union.ts`
|
✔ Achieve 100% branch coverage on `src/primitive-union.ts` @done
|
||||||
☐ Achieve 100% branch coverage on `src/index.ts`
|
✔ Achieve 100% branch coverage on `src/index.ts` @done
|
||||||
|
|
||||||
Matcher:
|
Matcher:
|
||||||
✔ when using a union type as a property, the current behavior of tagged union matcher is @done
|
✔ when using a union type as a property, the current behavior of tagged union matcher is @done
|
||||||
|
|||||||
@@ -97,6 +97,39 @@ Leave `act_runner`'s `force_pull` disabled.
|
|||||||
(`docker rmi gitea.e1nsnull.de/tmu/act-ci:<version>`); do not reach for
|
(`docker rmi gitea.e1nsnull.de/tmu/act-ci:<version>`); do not reach for
|
||||||
force-pull.
|
force-pull.
|
||||||
|
|
||||||
|
## Coverage threshold
|
||||||
|
|
||||||
|
#### Decision (2026-09)
|
||||||
|
|
||||||
|
`npm run test:ci` fails below 100% statements / branches / functions / lines
|
||||||
|
across `src/**/*.ts` (`c8 --all --include "src/**/*.ts" --100`). The gate rides
|
||||||
|
the `build` job; `npm run verify` stays coverage-free.
|
||||||
|
|
||||||
|
#### Why
|
||||||
|
|
||||||
|
- The types are the feature, so an untested branch is a hole in the contract,
|
||||||
|
not a metric to trade off; 100% is the only threshold that means "no hole".
|
||||||
|
- `--all` counts a `src/` file no test imports. Without it c8 reports only the
|
||||||
|
files the suite happened to load, so a new untested module is invisible and
|
||||||
|
the threshold passes vacuously.
|
||||||
|
- The gate rides `test:ci`, which `build` already runs — no new job or step.
|
||||||
|
- `verify` stays fast and local; the slower coverage run is a CI-only tier (see
|
||||||
|
[workflow.md § Feedback tiers](./workflow.md#feedback-tiers)).
|
||||||
|
|
||||||
|
#### Rejected
|
||||||
|
|
||||||
|
- Per-file thresholds: a global 100% already forces every counted file to 100%.
|
||||||
|
- A `check:coverage` script: it would re-run the suite or read c8's temp dir,
|
||||||
|
and no `check:*` script runs tests.
|
||||||
|
- `--all` without `--include`: it would also sweep `scripts/`, which is not the
|
||||||
|
shipped surface.
|
||||||
|
|
||||||
|
#### Known issue
|
||||||
|
|
||||||
|
- `src/matcher-shared.ts` is types only, so its runtime image is empty; c8 still
|
||||||
|
lists it under `--all`. It carries a file-level `/* c8 ignore start */` with
|
||||||
|
the reason. Adding runtime code there means removing that directive.
|
||||||
|
|
||||||
## Coverage serving
|
## Coverage serving
|
||||||
|
|
||||||
#### Decision (2026-09)
|
#### Decision (2026-09)
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ in
|
|||||||
[CONTRIBUTING.md § Development commands](../CONTRIBUTING.md#development-commands).
|
[CONTRIBUTING.md § Development commands](../CONTRIBUTING.md#development-commands).
|
||||||
`c8` uses V8 coverage, so the `--strip-types` source is instrumented without a
|
`c8` uses V8 coverage, so the `--strip-types` source is instrumented without a
|
||||||
build step, and the runner relies on the `.ts` import-extension convention (see
|
build step, and the runner relies on the `.ts` import-extension convention (see
|
||||||
[tooling.md](./tooling.md#source-imports-use-ts-extensions)).
|
[tooling.md](./tooling.md#source-imports-use-ts-extensions)). CI gates that
|
||||||
|
coverage at 100% (see [ci.md § Coverage threshold](./ci.md#coverage-threshold)).
|
||||||
|
|
||||||
## Handler arguments
|
## Handler arguments
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -58,7 +58,7 @@
|
|||||||
"maintain:knip": "knip --include dependencies,exports,files",
|
"maintain:knip": "knip --include dependencies,exports,files",
|
||||||
"maintain:outdated": "check-outdated --ignore-pre-releases --ignore-packages @types/node",
|
"maintain:outdated": "check-outdated --ignore-pre-releases --ignore-packages @types/node",
|
||||||
"test": "npm run check:tsc && node --test --strip-types \"src/**/*.test.ts\"",
|
"test": "npm run check:tsc && node --test --strip-types \"src/**/*.test.ts\"",
|
||||||
"test:ci": "c8 --reporter=text --reporter=lcov --reporter=html node --test --strip-types \"src/**/*.test.ts\"",
|
"test:ci": "c8 --all --include \"src/**/*.ts\" --reporter=text --reporter=lcov --reporter=html --100 node --test --strip-types \"src/**/*.test.ts\"",
|
||||||
"test:unit": "node --test --strip-types \"src/**/*.test.ts\"",
|
"test:unit": "node --test --strip-types \"src/**/*.test.ts\"",
|
||||||
"verify": "npm run check && npm run test:unit",
|
"verify": "npm run check && npm run test:unit",
|
||||||
"watch": "npm run watch:test",
|
"watch": "npm run watch:test",
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { strict as assert } from "node:assert";
|
||||||
|
import { test } from "node:test";
|
||||||
|
|
||||||
|
import { expectTypeOf } from "expect-type";
|
||||||
|
|
||||||
|
import {
|
||||||
|
getPrimitiveUnionMatcher,
|
||||||
|
getPrimitiveUnionMatcherW,
|
||||||
|
getTaggedUnionMatcher,
|
||||||
|
getTaggedUnionMatcherW,
|
||||||
|
} from "./index.ts";
|
||||||
|
|
||||||
|
// The published entry point is the barrel (`package.json` exports
|
||||||
|
// `./dist/index.js`), so every factory must be reachable from here. Importing it
|
||||||
|
// also loads the module, which is what lets c8's `--all` measure it — see
|
||||||
|
// development/ci.md § Coverage threshold.
|
||||||
|
test("index: the public entry point re-exports every matcher factory", () => {
|
||||||
|
// Assert
|
||||||
|
expectTypeOf(getPrimitiveUnionMatcher).toBeFunction();
|
||||||
|
assert.equal(typeof getPrimitiveUnionMatcher, "function");
|
||||||
|
expectTypeOf(getPrimitiveUnionMatcherW).toBeFunction();
|
||||||
|
assert.equal(typeof getPrimitiveUnionMatcherW, "function");
|
||||||
|
expectTypeOf(getTaggedUnionMatcher).toBeFunction();
|
||||||
|
assert.equal(typeof getTaggedUnionMatcher, "function");
|
||||||
|
expectTypeOf(getTaggedUnionMatcherW).toBeFunction();
|
||||||
|
assert.equal(typeof getTaggedUnionMatcherW, "function");
|
||||||
|
});
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* c8 ignore start -- types only: the module has no runtime image to cover */
|
||||||
import type { IsLiteral, IsNever, ValueOf } from "type-fest";
|
import type { IsLiteral, IsNever, ValueOf } from "type-fest";
|
||||||
|
|
||||||
// The primitive-union and tagged-union matchers differ in their universe, but the
|
// The primitive-union and tagged-union matchers differ in their universe, but the
|
||||||
|
|||||||
Reference in new issue
Block a user