@c9up/helix 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/cli/coverage/diff/overlay.d.ts.map +1 -1
  2. package/dist/cli/coverage/diff/overlay.js +19 -6
  3. package/dist/cli/coverage/diff/overlay.js.map +1 -1
  4. package/dist/cli/pool.js +10 -0
  5. package/dist/cli/pool.js.map +1 -1
  6. package/dist/runtime/equals.d.ts.map +1 -1
  7. package/dist/runtime/equals.js +6 -2
  8. package/dist/runtime/equals.js.map +1 -1
  9. package/dist/runtime/suite.d.ts +26 -0
  10. package/dist/runtime/suite.d.ts.map +1 -1
  11. package/dist/runtime/suite.js +21 -18
  12. package/dist/runtime/suite.js.map +1 -1
  13. package/index.darwin-arm64.node +0 -0
  14. package/index.darwin-x64.node +0 -0
  15. package/index.linux-arm64-gnu.node +0 -0
  16. package/index.linux-x64-gnu.node +0 -0
  17. package/index.win32-x64-msvc.node +0 -0
  18. package/package.json +2 -2
  19. package/src/cli/coverage/aggregate.ts +0 -231
  20. package/src/cli/coverage/collect.ts +0 -63
  21. package/src/cli/coverage/diff/base.ts +0 -46
  22. package/src/cli/coverage/diff/index.ts +0 -160
  23. package/src/cli/coverage/diff/overlay.ts +0 -62
  24. package/src/cli/coverage/diff/parse.ts +0 -121
  25. package/src/cli/coverage/diff/reporters.ts +0 -82
  26. package/src/cli/coverage/diff/types.ts +0 -46
  27. package/src/cli/coverage/filter.ts +0 -71
  28. package/src/cli/coverage/glob.ts +0 -0
  29. package/src/cli/coverage/index.ts +0 -126
  30. package/src/cli/coverage/reporters/json.ts +0 -40
  31. package/src/cli/coverage/reporters/lcov.ts +0 -54
  32. package/src/cli/coverage/reporters/text.ts +0 -48
  33. package/src/cli/coverage/thresholds.ts +0 -73
  34. package/src/cli/coverage/types.ts +0 -93
  35. package/src/cli/discover.ts +0 -174
  36. package/src/cli/native.ts +0 -104
  37. package/src/cli/pool.ts +0 -486
  38. package/src/cli/reporter.ts +0 -155
  39. package/src/cli/run.ts +0 -440
  40. package/src/cli/summary.ts +0 -42
  41. package/src/cli/watch/loop.ts +0 -159
  42. package/src/cli/watch/types.ts +0 -22
  43. package/src/cli/watch/watcher.ts +0 -145
  44. package/src/container/index.ts +0 -16
  45. package/src/container/override.ts +0 -86
  46. package/src/container/spy.ts +0 -25
  47. package/src/index.ts +0 -42
  48. package/src/runtime/assertion-error.ts +0 -38
  49. package/src/runtime/cli-worker.ts +0 -140
  50. package/src/runtime/equals.ts +0 -400
  51. package/src/runtime/expect.ts +0 -173
  52. package/src/runtime/index.ts +0 -50
  53. package/src/runtime/lifecycle.ts +0 -17
  54. package/src/runtime/matchers.ts +0 -452
  55. package/src/runtime/run.ts +0 -573
  56. package/src/runtime/suite.ts +0 -310
  57. package/src/runtime/test-context.ts +0 -59
  58. package/src/runtime/vi/fake-timers.ts +0 -410
  59. package/src/runtime/vi/index.ts +0 -254
  60. package/src/runtime/vi/spy.ts +0 -224
  61. package/src/runtime/vi/spyOn.ts +0 -155
  62. package/src/runtime/vi/system-time.ts +0 -121
  63. package/src/runtime/worker.ts +0 -239
  64. package/src/time/freeze.ts +0 -229
  65. package/src/time/index.ts +0 -16
@@ -1,63 +0,0 @@
1
- /**
2
- * Reads every `coverage-*.json` produced by the V8 profiler in a given
3
- * directory, decodes `file://` URLs, and returns a normalised list of
4
- * `RawFileCoverage` entries (one per unique script URL).
5
- *
6
- * Workers write coverage via `NODE_V8_COVERAGE=<dir>` — the orchestrator
7
- * sets this env var on every spawn and reads the directory after all
8
- * workers exit. Multiple workers may record the same file; we return ALL
9
- * entries here and let `aggregate.ts` merge.
10
- */
11
-
12
- import { readdir, readFile } from "node:fs/promises";
13
- import path from "node:path";
14
- import { fileURLToPath } from "node:url";
15
- import type { RawFileCoverage, V8CoverageFile } from "./types.js";
16
-
17
- export async function collect(dir: string): Promise<RawFileCoverage[]> {
18
- let entries: string[];
19
- try {
20
- entries = await readdir(dir);
21
- } catch {
22
- return [];
23
- }
24
- const jsonFiles = entries.filter((e) => e.endsWith(".json"));
25
- const all: RawFileCoverage[] = [];
26
- for (const name of jsonFiles) {
27
- const p = path.join(dir, name);
28
- let parsed: V8CoverageFile;
29
- try {
30
- const raw = await readFile(p, "utf8");
31
- parsed = JSON.parse(raw) as V8CoverageFile;
32
- } catch {
33
- continue;
34
- }
35
- for (const script of parsed.result ?? []) {
36
- if (!script.url?.startsWith("file://")) continue;
37
- // V8 sometimes appends `?query` params (our worker uses them as
38
- // cache-busters) — strip them so the same file from two workers
39
- // merges correctly.
40
- const cleanUrl = script.url.split("?")[0];
41
- let file: string;
42
- try {
43
- file = fileURLToPath(cleanUrl);
44
- } catch {
45
- continue;
46
- }
47
- let source = "";
48
- try {
49
- source = await readFile(file, "utf8");
50
- } catch {
51
- // Source read may fail for transient temp files or modules that
52
- // have been GC'd — leave empty; downstream stages fall back to
53
- // offset-only computation.
54
- }
55
- all.push({
56
- file,
57
- source,
58
- functions: script.functions ?? [],
59
- });
60
- }
61
- }
62
- return all;
63
- }
@@ -1,46 +0,0 @@
1
- /**
2
- * Resolve the default base ref to diff against. Tries, in order:
3
- * 1. `origin/main`
4
- * 2. `origin/master`
5
- * 3. local `main`
6
- * 4. local `master`
7
- *
8
- * Returns `undefined` if none of those exist (so the caller can warn
9
- * instead of failing the run). Throws when the `git` binary itself is
10
- * missing — the caller distinguishes "no repo / no ref" (skip
11
- * gracefully) from "git not installed" (configuration error).
12
- */
13
-
14
- import { spawnSync } from "node:child_process";
15
-
16
- const CANDIDATES = ["origin/main", "origin/master", "main", "master"];
17
-
18
- export class GitMissingError extends Error {
19
- constructor() {
20
- super("git binary not found on PATH");
21
- this.name = "GitMissingError";
22
- }
23
- }
24
-
25
- function runGit(cwd: string, args: string[]): { ok: boolean } {
26
- const result = spawnSync("git", args, {
27
- cwd,
28
- stdio: ["ignore", "pipe", "pipe"],
29
- });
30
- const err = result.error as NodeJS.ErrnoException | undefined;
31
- if (err?.code === "ENOENT") throw new GitMissingError();
32
- return { ok: result.status === 0 };
33
- }
34
-
35
- function refExists(cwd: string, ref: string): boolean {
36
- return runGit(cwd, ["rev-parse", "--verify", "--quiet", `${ref}^{commit}`])
37
- .ok;
38
- }
39
-
40
- export function resolveBaseRef(cwd: string): string | undefined {
41
- if (!runGit(cwd, ["rev-parse", "--git-dir"]).ok) return undefined;
42
- for (const cand of CANDIDATES) {
43
- if (refExists(cwd, cand)) return cand;
44
- }
45
- return undefined;
46
- }
@@ -1,160 +0,0 @@
1
- /**
2
- * Diff-coverage facade — wires base resolution → git diff → parser →
3
- * overlay → reporters → thresholds. Called by `run.ts` after the
4
- * full-tree finalise.
5
- */
6
-
7
- import { mkdir, writeFile } from "node:fs/promises";
8
- import path from "node:path";
9
- import { enforce } from "../thresholds.js";
10
- import type {
11
- CoverageSummary,
12
- Thresholds,
13
- ThresholdViolation,
14
- } from "../types.js";
15
- import { GitMissingError, resolveBaseRef } from "./base.js";
16
- import { overlay } from "./overlay.js";
17
- import { parseDiff } from "./parse.js";
18
- import { diffJson, diffTextSummary } from "./reporters.js";
19
- import type { DiffOptions, DiffSummary } from "./types.js";
20
-
21
- export type { DiffOptions, DiffSummary } from "./types.js";
22
-
23
- export interface DiffFinaliseInput extends DiffOptions {
24
- root: string;
25
- coverage: CoverageSummary;
26
- }
27
-
28
- export interface DiffFinaliseResult {
29
- summary: DiffSummary;
30
- violations: ThresholdViolation[];
31
- textReport: string;
32
- reportFiles: string[];
33
- /** When `undefined`, the diff stage was skipped (not in a repo, no
34
- * base ref, etc.) — callers should print `warning` and move on. */
35
- warning?: string;
36
- }
37
-
38
- /**
39
- * Wrap the diff totals into a `CoverageSummary`-shaped object so we can
40
- * reuse the full-tree `enforce()` helper. Only `lines`/`statements` are
41
- * populated; `functions`/`branches` are unsupported in diff mode and are
42
- * rejected upfront in `validateDiffThresholds`.
43
- */
44
- function toCoverageSummary(diff: DiffSummary): CoverageSummary {
45
- return {
46
- files: [],
47
- total: {
48
- lines: {
49
- covered: diff.total.covered,
50
- total: diff.total.added,
51
- pct: diff.total.pct,
52
- },
53
- statements: {
54
- covered: diff.total.covered,
55
- total: diff.total.added,
56
- pct: diff.total.pct,
57
- },
58
- functions: { covered: 0, total: 0, pct: 100 },
59
- branches: { covered: 0, total: 0, pct: 100 },
60
- },
61
- };
62
- }
63
-
64
- function violationLine(
65
- metric: keyof Thresholds,
66
- actual: number,
67
- threshold: number,
68
- ): string {
69
- return `coverage-diff: ${metric} ${actual.toFixed(1)} < threshold ${threshold}`;
70
- }
71
-
72
- /**
73
- * Reject `functions`/`branches` thresholds in diff mode — we only track
74
- * line-level diff coverage today, so silently passing those metrics
75
- * would let users gate on metrics that always read 100%.
76
- */
77
- function validateDiffThresholds(t: Thresholds | undefined): void {
78
- if (!t) return;
79
- const unsupported: string[] = [];
80
- if (typeof t.functions === "number" && t.functions > 0)
81
- unsupported.push("functions");
82
- if (typeof t.branches === "number" && t.branches > 0)
83
- unsupported.push("branches");
84
- if (unsupported.length > 0) {
85
- throw new Error(
86
- `diff-cov thresholds: ${unsupported.join(", ")} are not supported (line-only). Drop those keys.`,
87
- );
88
- }
89
- }
90
-
91
- export async function finaliseDiff(
92
- input: DiffFinaliseInput,
93
- ): Promise<DiffFinaliseResult> {
94
- validateDiffThresholds(input.thresholds);
95
-
96
- const cwd = input.cwd ?? input.root;
97
- let base: string | undefined;
98
- try {
99
- base = input.base ?? resolveBaseRef(cwd);
100
- } catch (err) {
101
- if (err instanceof GitMissingError) {
102
- return {
103
- summary: { files: [], total: { added: 0, covered: 0, pct: 100 } },
104
- violations: [],
105
- textReport: "",
106
- reportFiles: [],
107
- warning: "diff-cov: git binary not found on PATH. Skipping.",
108
- };
109
- }
110
- throw err;
111
- }
112
- if (!base) {
113
- return {
114
- summary: { files: [], total: { added: 0, covered: 0, pct: 100 } },
115
- violations: [],
116
- textReport: "",
117
- reportFiles: [],
118
- warning:
119
- "diff-cov: no git base ref resolved (origin/main, main, master). Skipping.",
120
- };
121
- }
122
-
123
- let diffMap: ReturnType<typeof parseDiff>;
124
- try {
125
- diffMap = parseDiff({ cwd, base });
126
- } catch (err) {
127
- return {
128
- summary: { files: [], total: { added: 0, covered: 0, pct: 100 } },
129
- violations: [],
130
- textReport: "",
131
- reportFiles: [],
132
- warning: `diff-cov: ${err instanceof Error ? err.message : String(err)}`,
133
- };
134
- }
135
-
136
- const summary = overlay(input.coverage, diffMap);
137
-
138
- const reportFiles: string[] = [];
139
- const outputDir = input.outputDir ?? path.join(input.root, "coverage");
140
- await mkdir(outputDir, { recursive: true });
141
- const file = path.join(outputDir, "coverage-diff.json");
142
- await writeFile(file, diffJson(summary, input.root), "utf8");
143
- reportFiles.push(file);
144
-
145
- const textReport = diffTextSummary(summary, input.root);
146
-
147
- let violations: ThresholdViolation[] = [];
148
- if (input.thresholds) {
149
- violations = enforce(toCoverageSummary(summary), input.thresholds);
150
- }
151
-
152
- return { summary, violations, textReport, reportFiles };
153
- }
154
-
155
- /** Reformat threshold violations with the `coverage-diff:` prefix. */
156
- export function diffViolationSummary(violations: ThresholdViolation[]): string {
157
- return violations
158
- .map((v) => violationLine(v.metric, v.actual, v.threshold))
159
- .join("\n");
160
- }
@@ -1,62 +0,0 @@
1
- /**
2
- * Overlay the diff map onto a `CoverageSummary` to compute per-file
3
- * "added vs covered" totals.
4
- *
5
- * For each file in the diff:
6
- * - find the matching coverage entry by absolute path
7
- * - intersect the added line numbers with `lineHits` (count > 0 means
8
- * covered)
9
- * - emit a `DiffFileSummary`
10
- *
11
- * Files in the diff with no coverage entry are still reported (added
12
- * lines, 0 covered) so a freshly-added module without any tests shows
13
- * up red. Files in coverage but not in the diff are skipped (the PR
14
- * didn't touch them, so they don't move the diff-coverage needle).
15
- */
16
-
17
- import type { CoverageSummary } from "../types.js";
18
- import type { DiffFileSummary, DiffMap, DiffSummary } from "./types.js";
19
-
20
- function pct(covered: number, added: number): number {
21
- if (added === 0) return 100;
22
- return Math.round((covered / added) * 10000) / 100;
23
- }
24
-
25
- export function overlay(coverage: CoverageSummary, diff: DiffMap): DiffSummary {
26
- const coverageByFile = new Map(coverage.files.map((f) => [f.file, f]));
27
- const files: DiffFileSummary[] = [];
28
- let totalAdded = 0;
29
- let totalCovered = 0;
30
-
31
- for (const [file, addedLines] of diff) {
32
- if (addedLines.size === 0) continue;
33
- const cov = coverageByFile.get(file);
34
- const hitsByLine = cov
35
- ? new Map(cov.lineHits.map((h) => [h.line, h.count]))
36
- : undefined;
37
- const sortedAdded = [...addedLines].sort((a, b) => a - b);
38
- const lineHits: DiffFileSummary["lineHits"] = sortedAdded.map((line) => {
39
- const count = hitsByLine?.get(line);
40
- return { line, covered: typeof count === "number" && count > 0 };
41
- });
42
- const covered = lineHits.filter((h) => h.covered).length;
43
- files.push({
44
- file,
45
- added: addedLines.size,
46
- covered,
47
- lineHits,
48
- });
49
- totalAdded += addedLines.size;
50
- totalCovered += covered;
51
- }
52
-
53
- files.sort((a, b) => a.file.localeCompare(b.file));
54
- return {
55
- files,
56
- total: {
57
- added: totalAdded,
58
- covered: totalCovered,
59
- pct: pct(totalCovered, totalAdded),
60
- },
61
- };
62
- }
@@ -1,121 +0,0 @@
1
- /**
2
- * Parse `git diff --unified=0 --no-color <base>...HEAD` output into a
3
- * `DiffMap`. Why three-dot: it computes the diff at the merge-base, so
4
- * commits made on `base` AFTER our branch diverged are excluded. That's
5
- * the "what this PR contributes" semantic users expect.
6
- *
7
- * We force `--src-prefix=a/ --dst-prefix=b/` and `core.quotepath=false`
8
- * so the parser doesn't have to guess at user-side `diff.noprefix` or
9
- * non-ASCII path quoting.
10
- *
11
- * Renames produce `+++ b/<new path>` so they're picked up at the new
12
- * location. Deletes produce `+++ /dev/null` and are skipped. Binary
13
- * files produce no `+` lines so they contribute nothing.
14
- */
15
-
16
- import { spawnSync } from "node:child_process";
17
- import { realpathSync } from "node:fs";
18
- import path from "node:path";
19
- import type { DiffMap } from "./types.js";
20
-
21
- const HUNK_HEADER = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/;
22
- const FILE_HEADER_PLUS = /^\+\+\+ (?:b\/(.*)|\/dev\/null)$/;
23
-
24
- export interface ParseOptions {
25
- cwd: string;
26
- base: string;
27
- }
28
-
29
- function tryRealpath(p: string): string {
30
- try {
31
- return realpathSync(p);
32
- } catch {
33
- return p;
34
- }
35
- }
36
-
37
- export function parseDiff(opts: ParseOptions): DiffMap {
38
- const result = spawnSync(
39
- "git",
40
- [
41
- "-c",
42
- "core.quotepath=false",
43
- "diff",
44
- "--unified=0",
45
- "--no-color",
46
- "--src-prefix=a/",
47
- "--dst-prefix=b/",
48
- `${opts.base}...HEAD`,
49
- ],
50
- {
51
- cwd: opts.cwd,
52
- encoding: "utf8",
53
- maxBuffer: 64 * 1024 * 1024,
54
- },
55
- );
56
- if (result.error) {
57
- const err = result.error as NodeJS.ErrnoException;
58
- if (err.code === "ENOENT") {
59
- throw new Error("git binary not found on PATH");
60
- }
61
- if (err.code === "ENOBUFS") {
62
- throw new Error(
63
- "diff output exceeded 64 MB — branch is too divergent to overlay",
64
- );
65
- }
66
- throw new Error(`git diff failed: ${err.message}`);
67
- }
68
- if (result.status !== 0) {
69
- throw new Error(
70
- `git diff exited ${result.status}: ${result.stderr?.trim() || "(no stderr — likely shallow clone or pruned ref)"}`,
71
- );
72
- }
73
- return parseDiffString(result.stdout, opts.cwd);
74
- }
75
-
76
- /**
77
- * Pure parser — exposed separately so tests can feed canned diff
78
- * strings without spawning git. Keys are canonicalised via realpath so
79
- * they line up with V8 coverage paths (which `filter.ts` realpaths).
80
- */
81
- export function parseDiffString(diff: string, cwd: string): DiffMap {
82
- const map: DiffMap = new Map();
83
- let currentFile: string | undefined;
84
- let nextAddedLine = 0;
85
- const lines = diff.split(/\r?\n/);
86
- for (const line of lines) {
87
- const fileMatch = line.match(FILE_HEADER_PLUS);
88
- if (fileMatch) {
89
- const newPath = fileMatch[1];
90
- if (!newPath) {
91
- currentFile = undefined;
92
- nextAddedLine = 0;
93
- continue;
94
- }
95
- currentFile = tryRealpath(path.resolve(cwd, newPath));
96
- nextAddedLine = 0;
97
- continue;
98
- }
99
- if (!currentFile) continue;
100
- const hunkMatch = line.match(HUNK_HEADER);
101
- if (hunkMatch) {
102
- nextAddedLine = Number.parseInt(hunkMatch[1], 10);
103
- continue;
104
- }
105
- if (line.startsWith("+") && !line.startsWith("+++")) {
106
- if (nextAddedLine < 1) {
107
- // `+0,0` deletion-only hunks set nextAddedLine = 0; ignore stray
108
- // `+` lines rather than emit invalid line numbers.
109
- continue;
110
- }
111
- let set = map.get(currentFile);
112
- if (!set) {
113
- set = new Set();
114
- map.set(currentFile, set);
115
- }
116
- set.add(nextAddedLine);
117
- nextAddedLine += 1;
118
- }
119
- }
120
- return map;
121
- }
@@ -1,82 +0,0 @@
1
- /**
2
- * Diff-coverage reporters: text summary + JSON.
3
- *
4
- * The text reporter mirrors the full-tree text format so users can read
5
- * both side-by-side. JSON is namespaced under `files` to avoid
6
- * collisions with the literal `total` key (in case a file is named
7
- * `total`).
8
- */
9
-
10
- import path from "node:path";
11
- import type { DiffFileSummary, DiffSummary } from "./types.js";
12
-
13
- function rel(file: string, root: string): string {
14
- const r = path.relative(root, file).split(path.sep).join("/");
15
- return r.length > 0 && !r.startsWith("..") ? r : file;
16
- }
17
-
18
- function padRight(s: string, w: number): string {
19
- if (s.length === w) return s;
20
- if (s.length > w) return `…${s.slice(s.length - w + 1)}`;
21
- return s + " ".repeat(w - s.length);
22
- }
23
-
24
- function fmtPct(n: number): string {
25
- return `${n.toFixed(2).padStart(6)}%`;
26
- }
27
-
28
- export function diffTextSummary(summary: DiffSummary, root: string): string {
29
- const lines: string[] = [];
30
- lines.push("");
31
- lines.push("── Diff coverage ───────────────────────────────────────────");
32
- if (summary.files.length === 0) {
33
- lines.push(" No instrumented files were touched by the diff.");
34
- lines.push(
35
- ` ${summary.total.covered}/${summary.total.added} lines covered`,
36
- );
37
- return `${lines.join("\n")}\n`;
38
- }
39
- lines.push(
40
- `${padRight("File", 50)} ${"Covered".padStart(10)} ${"%".padStart(8)}`,
41
- );
42
- lines.push("─".repeat(72));
43
- for (const f of summary.files) {
44
- const ratio = `${f.covered}/${f.added}`;
45
- const pct = f.added > 0 ? (f.covered / f.added) * 100 : 100;
46
- lines.push(
47
- `${padRight(rel(f.file, root), 50)} ${ratio.padStart(10)} ${fmtPct(pct)}`,
48
- );
49
- }
50
- lines.push("─".repeat(72));
51
- lines.push(
52
- `${padRight("Total", 50)} ${`${summary.total.covered}/${summary.total.added}`.padStart(10)} ${fmtPct(summary.total.pct)}`,
53
- );
54
- return `${lines.join("\n")}\n`;
55
- }
56
-
57
- interface DiffJsonFileEntry {
58
- added: number;
59
- covered: number;
60
- lineHits: DiffFileSummary["lineHits"];
61
- }
62
-
63
- interface DiffJsonOutput {
64
- total: DiffSummary["total"];
65
- files: Record<string, DiffJsonFileEntry>;
66
- }
67
-
68
- export function diffJson(summary: DiffSummary, root: string): string {
69
- const filesOut: Record<string, DiffJsonFileEntry> = Object.create(null);
70
- for (const f of summary.files) {
71
- filesOut[rel(f.file, root)] = {
72
- added: f.added,
73
- covered: f.covered,
74
- lineHits: f.lineHits,
75
- };
76
- }
77
- const out: DiffJsonOutput = {
78
- total: summary.total,
79
- files: filesOut,
80
- };
81
- return `${JSON.stringify(out, null, 2)}\n`;
82
- }
@@ -1,46 +0,0 @@
1
- /**
2
- * Types for the diff-coverage pipeline.
3
- *
4
- * `DiffMap` is the output of `parse.ts` — for every file changed in
5
- * `<base>...HEAD`, the set of 1-based line numbers added or modified
6
- * (i.e. lines starting with `+` in unified diff, excluding the `+++`
7
- * header). Deleted lines aren't relevant: we only care whether NEW
8
- * code is covered.
9
- *
10
- * `DiffFileSummary` and `DiffSummary` mirror the shape of the full-tree
11
- * `FileSummary` / `CoverageSummary` so the existing threshold
12
- * enforcement machinery applies unchanged.
13
- */
14
-
15
- export type DiffMap = Map<string, Set<number>>;
16
-
17
- export interface DiffFileSummary {
18
- file: string;
19
- /** Lines added by the PR (1-based). */
20
- added: number;
21
- /** Subset of `added` that have a hit count > 0 in the coverage summary. */
22
- covered: number;
23
- /** Per-line breakdown for reporters. */
24
- lineHits: Array<{ line: number; covered: boolean }>;
25
- }
26
-
27
- export interface DiffSummary {
28
- files: DiffFileSummary[];
29
- total: {
30
- added: number;
31
- covered: number;
32
- pct: number;
33
- };
34
- }
35
-
36
- export interface DiffOptions {
37
- enabled: boolean;
38
- /** Git revision to diff against. Default: resolved by `base.ts`. */
39
- base?: string;
40
- /** Inline thresholds. Reuses the full-tree shape. */
41
- thresholds?: import("../types.js").Thresholds;
42
- /** Output dir for `coverage-diff.json`. Defaults to coverage outputDir. */
43
- outputDir?: string;
44
- /** Where to spawn `git diff`. Defaults to `process.cwd()`. */
45
- cwd?: string;
46
- }
@@ -1,71 +0,0 @@
1
- // Include / exclude filter for coverage files. Glob subset is
2
- // implemented in `./glob.ts` (shared with the watch-mode filter).
3
- //
4
- // Symlinks are NOT followed: `path.realpath` is used to verify the file
5
- // actually lives under `root`. Anything pointing outside (off-tree
6
- // symlinks) is dropped — defence in depth against a malicious source
7
- // tree dragging unrelated files into the coverage report.
8
-
9
- import { realpathSync } from "node:fs";
10
- import path from "node:path";
11
- import { compileGlobs, globToRegex, matchesAnyGlob } from "./glob.js";
12
- import type { RawFileCoverage } from "./types.js";
13
-
14
- const DEFAULT_INCLUDE = ["src/**/*.{ts,tsx,js,mjs,cjs}"];
15
- const DEFAULT_EXCLUDE = [
16
- "node_modules/**",
17
- "dist/**",
18
- "build/**",
19
- "coverage/**",
20
- ".git/**",
21
- ".wolf/**",
22
- "target/**",
23
- ".next/**",
24
- "tests/**",
25
- "test/**",
26
- "**/*.test.*",
27
- "**/*.spec.*",
28
- ];
29
-
30
- export interface FilterConfig {
31
- root: string;
32
- include?: string[];
33
- exclude?: string[];
34
- }
35
-
36
- /**
37
- * Resolve a path's real (canonical) absolute location. Falls back to
38
- * the lexical path if the file no longer exists (already deleted by the
39
- * worker exit). Symlinks point at the target's real path.
40
- */
41
- function tryRealpath(p: string): string {
42
- try {
43
- return realpathSync(p);
44
- } catch {
45
- return p;
46
- }
47
- }
48
-
49
- export function filter(
50
- raw: RawFileCoverage[],
51
- config: FilterConfig,
52
- ): RawFileCoverage[] {
53
- const include = compileGlobs(config.include ?? DEFAULT_INCLUDE);
54
- const exclude = compileGlobs(config.exclude ?? DEFAULT_EXCLUDE);
55
- const root = tryRealpath(path.resolve(config.root));
56
- const out: RawFileCoverage[] = [];
57
- for (const entry of raw) {
58
- const real = tryRealpath(entry.file);
59
- const rel = path.relative(root, real).split(path.sep).join("/");
60
- if (rel.length === 0 || rel.startsWith("..") || path.isAbsolute(rel)) {
61
- continue;
62
- }
63
- if (!matchesAnyGlob(include, rel)) continue;
64
- if (matchesAnyGlob(exclude, rel)) continue;
65
- out.push({ ...entry, file: real });
66
- }
67
- return out;
68
- }
69
-
70
- /** Exposed for tests. */
71
- export const __test__ = { globToRegex };
Binary file