@c9up/helix 0.1.3 → 0.1.5
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.
- package/dist/cli/coverage/aggregate.d.ts +24 -0
- package/dist/cli/coverage/aggregate.d.ts.map +1 -0
- package/dist/cli/coverage/aggregate.js +215 -0
- package/dist/cli/coverage/aggregate.js.map +1 -0
- package/dist/cli/coverage/collect.d.ts +13 -0
- package/dist/cli/coverage/collect.d.ts.map +1 -0
- package/dist/cli/coverage/collect.js +66 -0
- package/dist/cli/coverage/collect.js.map +1 -0
- package/dist/cli/coverage/diff/base.d.ts +17 -0
- package/dist/cli/coverage/diff/base.d.ts.map +1 -0
- package/dist/cli/coverage/diff/base.js +44 -0
- package/dist/cli/coverage/diff/base.js.map +1 -0
- package/dist/cli/coverage/diff/index.d.ts +25 -0
- package/dist/cli/coverage/diff/index.d.ts.map +1 -0
- package/dist/cli/coverage/diff/index.js +119 -0
- package/dist/cli/coverage/diff/index.js.map +1 -0
- package/dist/cli/coverage/diff/overlay.d.ts +19 -0
- package/dist/cli/coverage/diff/overlay.d.ts.map +1 -0
- package/dist/cli/coverage/diff/overlay.js +58 -0
- package/dist/cli/coverage/diff/overlay.js.map +1 -0
- package/dist/cli/coverage/diff/parse.d.ts +27 -0
- package/dist/cli/coverage/diff/parse.d.ts.map +1 -0
- package/dist/cli/coverage/diff/parse.js +105 -0
- package/dist/cli/coverage/diff/parse.js.map +1 -0
- package/dist/cli/coverage/diff/reporters.d.ts +12 -0
- package/dist/cli/coverage/diff/reporters.d.ts.map +1 -0
- package/dist/cli/coverage/diff/reporters.js +59 -0
- package/dist/cli/coverage/diff/reporters.js.map +1 -0
- package/dist/cli/coverage/diff/types.d.ts +46 -0
- package/dist/cli/coverage/diff/types.d.ts.map +1 -0
- package/dist/cli/coverage/diff/types.js +15 -0
- package/dist/cli/coverage/diff/types.js.map +1 -0
- package/dist/cli/coverage/filter.d.ts +13 -0
- package/dist/cli/coverage/filter.d.ts.map +1 -0
- package/dist/cli/coverage/filter.js +60 -0
- package/dist/cli/coverage/filter.js.map +1 -0
- package/dist/cli/coverage/glob.d.ts +4 -0
- package/dist/cli/coverage/glob.d.ts.map +1 -0
- package/dist/cli/coverage/glob.js +0 -0
- package/dist/cli/coverage/glob.js.map +1 -0
- package/dist/cli/coverage/index.d.ts +40 -0
- package/dist/cli/coverage/index.d.ts.map +1 -0
- package/dist/cli/coverage/index.js +85 -0
- package/dist/cli/coverage/index.js.map +1 -0
- package/dist/cli/coverage/reporters/json.d.ts +13 -0
- package/dist/cli/coverage/reporters/json.d.ts.map +1 -0
- package/dist/cli/coverage/reporters/json.js +37 -0
- package/dist/cli/coverage/reporters/json.js.map +1 -0
- package/dist/cli/coverage/reporters/lcov.d.ts +13 -0
- package/dist/cli/coverage/reporters/lcov.d.ts.map +1 -0
- package/dist/cli/coverage/reporters/lcov.js +51 -0
- package/dist/cli/coverage/reporters/lcov.js.map +1 -0
- package/dist/cli/coverage/reporters/text.d.ts +8 -0
- package/dist/cli/coverage/reporters/text.d.ts.map +1 -0
- package/dist/cli/coverage/reporters/text.js +39 -0
- package/dist/cli/coverage/reporters/text.js.map +1 -0
- package/dist/cli/coverage/thresholds.d.ts +14 -0
- package/dist/cli/coverage/thresholds.d.ts.map +1 -0
- package/dist/cli/coverage/thresholds.js +58 -0
- package/dist/cli/coverage/thresholds.js.map +1 -0
- package/dist/cli/coverage/types.d.ts +118 -0
- package/dist/cli/coverage/types.d.ts.map +1 -0
- package/dist/cli/coverage/types.js +8 -0
- package/dist/cli/coverage/types.js.map +1 -0
- package/dist/cli/discover.d.ts +27 -0
- package/dist/cli/discover.d.ts.map +1 -0
- package/dist/cli/discover.js +142 -0
- package/dist/cli/discover.js.map +1 -0
- package/dist/cli/native.d.ts +43 -0
- package/dist/cli/native.d.ts.map +1 -0
- package/dist/cli/native.js +64 -0
- package/dist/cli/native.js.map +1 -0
- package/dist/cli/pool.d.ts +55 -0
- package/dist/cli/pool.d.ts.map +1 -0
- package/dist/cli/pool.js +404 -0
- package/dist/cli/pool.js.map +1 -0
- package/dist/cli/reporter.d.ts +51 -0
- package/dist/cli/reporter.d.ts.map +1 -0
- package/dist/cli/reporter.js +124 -0
- package/dist/cli/reporter.js.map +1 -0
- package/dist/cli/run.d.ts +61 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +302 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/summary.d.ts +18 -0
- package/dist/cli/summary.d.ts.map +1 -0
- package/dist/cli/summary.js +20 -0
- package/dist/cli/summary.js.map +1 -0
- package/dist/cli/watch/loop.d.ts +26 -0
- package/dist/cli/watch/loop.d.ts.map +1 -0
- package/dist/cli/watch/loop.js +133 -0
- package/dist/cli/watch/loop.js.map +1 -0
- package/dist/cli/watch/types.d.ts +22 -0
- package/dist/cli/watch/types.d.ts.map +1 -0
- package/dist/cli/watch/types.js +6 -0
- package/dist/cli/watch/types.js.map +1 -0
- package/dist/cli/watch/watcher.d.ts +25 -0
- package/dist/cli/watch/watcher.d.ts.map +1 -0
- package/dist/cli/watch/watcher.js +122 -0
- package/dist/cli/watch/watcher.js.map +1 -0
- package/dist/container/index.d.ts +7 -0
- package/dist/container/index.d.ts.map +1 -0
- package/dist/container/index.js +6 -0
- package/dist/container/index.js.map +1 -0
- package/{src/container/override.ts → dist/container/override.d.ts} +6 -46
- package/dist/container/override.d.ts.map +1 -0
- package/dist/container/override.js +67 -0
- package/dist/container/override.js.map +1 -0
- package/{src/container/spy.ts → dist/container/spy.d.ts} +2 -6
- package/dist/container/spy.d.ts.map +1 -0
- package/dist/container/spy.js +23 -0
- package/dist/container/spy.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/{src/index.ts → dist/index.js} +2 -25
- package/dist/index.js.map +1 -0
- package/dist/runtime/assertion-error.d.ts +20 -0
- package/dist/runtime/assertion-error.d.ts.map +1 -0
- package/dist/runtime/assertion-error.js +23 -0
- package/dist/runtime/assertion-error.js.map +1 -0
- package/dist/runtime/cli-worker.d.ts +25 -0
- package/dist/runtime/cli-worker.d.ts.map +1 -0
- package/dist/runtime/cli-worker.js +128 -0
- package/dist/runtime/cli-worker.js.map +1 -0
- package/dist/runtime/equals.d.ts +25 -0
- package/dist/runtime/equals.d.ts.map +1 -0
- package/dist/runtime/equals.js +343 -0
- package/dist/runtime/equals.js.map +1 -0
- package/dist/runtime/expect.d.ts +32 -0
- package/dist/runtime/expect.d.ts.map +1 -0
- package/dist/runtime/expect.js +121 -0
- package/dist/runtime/expect.js.map +1 -0
- package/{src/runtime/index.ts → dist/runtime/index.d.ts} +5 -27
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +17 -0
- package/dist/runtime/index.js.map +1 -0
- package/{src/runtime/lifecycle.ts → dist/runtime/lifecycle.d.ts} +2 -8
- package/dist/runtime/lifecycle.d.ts.map +1 -0
- package/dist/runtime/lifecycle.js +10 -0
- package/dist/runtime/lifecycle.js.map +1 -0
- package/dist/runtime/matchers.d.ts +42 -0
- package/dist/runtime/matchers.d.ts.map +1 -0
- package/dist/runtime/matchers.js +375 -0
- package/dist/runtime/matchers.js.map +1 -0
- package/dist/runtime/run.d.ts +55 -0
- package/dist/runtime/run.d.ts.map +1 -0
- package/dist/runtime/run.js +456 -0
- package/dist/runtime/run.js.map +1 -0
- package/dist/runtime/suite.d.ts +91 -0
- package/dist/runtime/suite.d.ts.map +1 -0
- package/dist/runtime/suite.js +206 -0
- package/dist/runtime/suite.js.map +1 -0
- package/dist/runtime/test-context.d.ts +25 -0
- package/dist/runtime/test-context.d.ts.map +1 -0
- package/dist/runtime/test-context.js +52 -0
- package/dist/runtime/test-context.js.map +1 -0
- package/dist/runtime/vi/fake-timers.d.ts +48 -0
- package/dist/runtime/vi/fake-timers.d.ts.map +1 -0
- package/dist/runtime/vi/fake-timers.js +317 -0
- package/dist/runtime/vi/fake-timers.js.map +1 -0
- package/dist/runtime/vi/index.d.ts +70 -0
- package/dist/runtime/vi/index.d.ts.map +1 -0
- package/dist/runtime/vi/index.js +209 -0
- package/dist/runtime/vi/index.js.map +1 -0
- package/dist/runtime/vi/spy.d.ts +54 -0
- package/dist/runtime/vi/spy.d.ts.map +1 -0
- package/dist/runtime/vi/spy.js +159 -0
- package/dist/runtime/vi/spy.js.map +1 -0
- package/dist/runtime/vi/spyOn.d.ts +28 -0
- package/dist/runtime/vi/spyOn.d.ts.map +1 -0
- package/dist/runtime/vi/spyOn.js +121 -0
- package/dist/runtime/vi/spyOn.js.map +1 -0
- package/dist/runtime/vi/system-time.d.ts +30 -0
- package/dist/runtime/vi/system-time.d.ts.map +1 -0
- package/dist/runtime/vi/system-time.js +100 -0
- package/dist/runtime/vi/system-time.js.map +1 -0
- package/dist/runtime/worker.d.ts +26 -0
- package/dist/runtime/worker.d.ts.map +1 -0
- package/dist/runtime/worker.js +195 -0
- package/dist/runtime/worker.js.map +1 -0
- package/dist/time/freeze.d.ts +80 -0
- package/dist/time/freeze.d.ts.map +1 -0
- package/dist/time/freeze.js +184 -0
- package/dist/time/freeze.js.map +1 -0
- package/{src/time/index.ts → dist/time/index.d.ts} +1 -1
- package/dist/time/index.d.ts.map +1 -0
- package/dist/time/index.js +15 -0
- package/dist/time/index.js.map +1 -0
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +2 -2
- package/src/cli/coverage/aggregate.ts +0 -231
- package/src/cli/coverage/collect.ts +0 -63
- package/src/cli/coverage/diff/base.ts +0 -46
- package/src/cli/coverage/diff/index.ts +0 -160
- package/src/cli/coverage/diff/overlay.ts +0 -62
- package/src/cli/coverage/diff/parse.ts +0 -121
- package/src/cli/coverage/diff/reporters.ts +0 -82
- package/src/cli/coverage/diff/types.ts +0 -46
- package/src/cli/coverage/filter.ts +0 -71
- package/src/cli/coverage/glob.ts +0 -0
- package/src/cli/coverage/index.ts +0 -126
- package/src/cli/coverage/reporters/json.ts +0 -40
- package/src/cli/coverage/reporters/lcov.ts +0 -54
- package/src/cli/coverage/reporters/text.ts +0 -48
- package/src/cli/coverage/thresholds.ts +0 -73
- package/src/cli/coverage/types.ts +0 -93
- package/src/cli/discover.ts +0 -174
- package/src/cli/native.ts +0 -104
- package/src/cli/pool.ts +0 -486
- package/src/cli/reporter.ts +0 -155
- package/src/cli/run.ts +0 -440
- package/src/cli/summary.ts +0 -42
- package/src/cli/watch/loop.ts +0 -159
- package/src/cli/watch/types.ts +0 -22
- package/src/cli/watch/watcher.ts +0 -145
- package/src/container/index.ts +0 -16
- package/src/runtime/assertion-error.ts +0 -38
- package/src/runtime/cli-worker.ts +0 -140
- package/src/runtime/equals.ts +0 -400
- package/src/runtime/expect.ts +0 -173
- package/src/runtime/matchers.ts +0 -452
- package/src/runtime/run.ts +0 -573
- package/src/runtime/suite.ts +0 -310
- package/src/runtime/test-context.ts +0 -59
- package/src/runtime/vi/fake-timers.ts +0 -410
- package/src/runtime/vi/index.ts +0 -254
- package/src/runtime/vi/spy.ts +0 -224
- package/src/runtime/vi/spyOn.ts +0 -155
- package/src/runtime/vi/system-time.ts +0 -121
- package/src/runtime/worker.ts +0 -239
- package/src/time/freeze.ts +0 -229
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `coverage-summary.json` — Istanbul-compatible shape consumed by CI
|
|
3
|
-
* tooling (lcov-reporter-action, Codecov uploaders that prefer JSON).
|
|
4
|
-
*
|
|
5
|
-
* File keys are emitted as paths RELATIVE to `root` so the artifact is
|
|
6
|
-
* portable across machines. The reserved `total` key holds the
|
|
7
|
-
* aggregate; if a real file path collides with it (extraordinarily
|
|
8
|
-
* unlikely after relativisation), we keep the totals row and skip the
|
|
9
|
-
* collision rather than overwrite — protecting the consumer's invariant.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import path from "node:path";
|
|
13
|
-
import type { CoverageSummary } from "../types.js";
|
|
14
|
-
|
|
15
|
-
function rel(file: string, root: string): string {
|
|
16
|
-
const r = path.relative(root, file).split(path.sep).join("/");
|
|
17
|
-
return r.length > 0 && !r.startsWith("..") ? r : file;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function jsonSummary(summary: CoverageSummary, root: string): string {
|
|
21
|
-
// Use a fresh prototype-less object so a pathological file path of
|
|
22
|
-
// "__proto__" can't pollute the global Object prototype.
|
|
23
|
-
const out: Record<string, unknown> = Object.create(null);
|
|
24
|
-
out.total = summary.total;
|
|
25
|
-
for (const f of summary.files) {
|
|
26
|
-
const key = rel(f.file, root);
|
|
27
|
-
if (key === "total") {
|
|
28
|
-
// Astronomical edge: a file literally named "total" at the root.
|
|
29
|
-
// Prefer correctness of the totals row over reporting that file.
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
out[key] = {
|
|
33
|
-
lines: f.lines,
|
|
34
|
-
statements: f.statements,
|
|
35
|
-
functions: f.functions,
|
|
36
|
-
branches: f.branches,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
return `${JSON.stringify(out, null, 2)}\n`;
|
|
40
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LCOV reporter — writes standard `lcov.info` that Codecov, Coveralls,
|
|
3
|
-
* and IDE coverage gutters consume directly.
|
|
4
|
-
*
|
|
5
|
-
* Format reference: `man geninfo` /
|
|
6
|
-
* http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php
|
|
7
|
-
*
|
|
8
|
-
* Paths in `SF:` are emitted RELATIVE TO `root` so the same artifact
|
|
9
|
-
* lines up across machines (developer laptop, CI runner, Codecov server).
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import path from "node:path";
|
|
13
|
-
import type { CoverageSummary } from "../types.js";
|
|
14
|
-
|
|
15
|
-
function rel(file: string, root: string): string {
|
|
16
|
-
const r = path.relative(root, file).split(path.sep).join("/");
|
|
17
|
-
return r.length > 0 && !r.startsWith("..") ? r : file;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function lcov(summary: CoverageSummary, root: string): string {
|
|
21
|
-
const out: string[] = [];
|
|
22
|
-
for (const f of summary.files) {
|
|
23
|
-
out.push(`TN:`);
|
|
24
|
-
out.push(`SF:${rel(f.file, root)}`);
|
|
25
|
-
|
|
26
|
-
let fnf = 0;
|
|
27
|
-
let fnh = 0;
|
|
28
|
-
for (const fn of f.functionHits) {
|
|
29
|
-
// Skip records lcov rejects (line < 1).
|
|
30
|
-
if (fn.line < 1) continue;
|
|
31
|
-
out.push(`FN:${fn.line},${fn.name}`);
|
|
32
|
-
out.push(`FNDA:${fn.count},${fn.name}`);
|
|
33
|
-
fnf += 1;
|
|
34
|
-
if (fn.count > 0) fnh += 1;
|
|
35
|
-
}
|
|
36
|
-
out.push(`FNF:${fnf}`);
|
|
37
|
-
out.push(`FNH:${fnh}`);
|
|
38
|
-
|
|
39
|
-
let lf = 0;
|
|
40
|
-
let lh = 0;
|
|
41
|
-
for (const l of f.lineHits) {
|
|
42
|
-
out.push(`DA:${l.line},${l.count}`);
|
|
43
|
-
lf += 1;
|
|
44
|
-
if (l.count > 0) lh += 1;
|
|
45
|
-
}
|
|
46
|
-
out.push(`LF:${lf}`);
|
|
47
|
-
out.push(`LH:${lh}`);
|
|
48
|
-
|
|
49
|
-
out.push(`BRF:0`);
|
|
50
|
-
out.push(`BRH:0`);
|
|
51
|
-
out.push(`end_of_record`);
|
|
52
|
-
}
|
|
53
|
-
return `${out.join("\n")}\n`;
|
|
54
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Text summary reporter — one line per file + totals, written to stdout.
|
|
3
|
-
* Deliberately terse (mirrors c8's default output) so it fits alongside
|
|
4
|
-
* the test reporter's summary without overwhelming.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { CoverageSummary } from "../types.js";
|
|
8
|
-
|
|
9
|
-
function fmtPct(n: number): string {
|
|
10
|
-
return `${n.toFixed(2).padStart(6)}%`;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function padRight(s: string, w: number): string {
|
|
14
|
-
if (s.length === w) return s;
|
|
15
|
-
if (s.length > w) {
|
|
16
|
-
// Ellipsize from the LEFT — preserves the filename tail which is
|
|
17
|
-
// usually more informative than the leading directory.
|
|
18
|
-
return `…${s.slice(s.length - w + 1)}`;
|
|
19
|
-
}
|
|
20
|
-
return s + " ".repeat(w - s.length);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function textSummary(summary: CoverageSummary, root: string): string {
|
|
24
|
-
const lines: string[] = [];
|
|
25
|
-
lines.push("");
|
|
26
|
-
lines.push("── Coverage ─────────────────────────────────────────────────");
|
|
27
|
-
lines.push(
|
|
28
|
-
`${padRight("File", 50)} ${"Lines".padStart(8)} ${"Funcs".padStart(8)}`,
|
|
29
|
-
);
|
|
30
|
-
lines.push("─".repeat(70));
|
|
31
|
-
for (const f of summary.files) {
|
|
32
|
-
const rel = f.file.startsWith(root)
|
|
33
|
-
? f.file.slice(root.length + 1)
|
|
34
|
-
: f.file;
|
|
35
|
-
const linesPct =
|
|
36
|
-
f.lines.total > 0 ? (f.lines.covered / f.lines.total) * 100 : 100;
|
|
37
|
-
const fnPct =
|
|
38
|
-
f.functions.total > 0
|
|
39
|
-
? (f.functions.covered / f.functions.total) * 100
|
|
40
|
-
: 100;
|
|
41
|
-
lines.push(`${padRight(rel, 50)} ${fmtPct(linesPct)} ${fmtPct(fnPct)}`);
|
|
42
|
-
}
|
|
43
|
-
lines.push("─".repeat(70));
|
|
44
|
-
lines.push(
|
|
45
|
-
`${padRight("All files", 50)} ${fmtPct(summary.total.lines.pct)} ${fmtPct(summary.total.functions.pct)}`,
|
|
46
|
-
);
|
|
47
|
-
return `${lines.join("\n")}\n`;
|
|
48
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Threshold enforcement — compare each configured metric (`lines` /
|
|
3
|
-
* `functions` / `statements` / `branches`) against the aggregate totals.
|
|
4
|
-
* Returns the list of violations; caller decides whether to set
|
|
5
|
-
* `exitCode = 1` (today: yes, always).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type {
|
|
9
|
-
CoverageSummary,
|
|
10
|
-
Thresholds,
|
|
11
|
-
ThresholdViolation,
|
|
12
|
-
} from "./types.js";
|
|
13
|
-
|
|
14
|
-
const METRICS: Array<keyof Thresholds> = [
|
|
15
|
-
"lines",
|
|
16
|
-
"functions",
|
|
17
|
-
"statements",
|
|
18
|
-
"branches",
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Validate user-supplied thresholds. Throws on NaN / negative / >100 so
|
|
23
|
-
* config typos fail loudly. Empty / undefined values are accepted (no
|
|
24
|
-
* gate on that metric).
|
|
25
|
-
*/
|
|
26
|
-
function validate(thresholds: Thresholds): void {
|
|
27
|
-
for (const metric of METRICS) {
|
|
28
|
-
const v = thresholds[metric];
|
|
29
|
-
if (v === undefined) continue;
|
|
30
|
-
if (typeof v !== "number" || !Number.isFinite(v)) {
|
|
31
|
-
throw new Error(
|
|
32
|
-
`coverage threshold ${metric}: expected a finite number, got ${v}`,
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
if (v < 0 || v > 100) {
|
|
36
|
-
throw new Error(`coverage threshold ${metric}: expected 0–100, got ${v}`);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function enforce(
|
|
42
|
-
summary: CoverageSummary,
|
|
43
|
-
thresholds: Thresholds,
|
|
44
|
-
): ThresholdViolation[] {
|
|
45
|
-
validate(thresholds);
|
|
46
|
-
const violations: ThresholdViolation[] = [];
|
|
47
|
-
for (const metric of METRICS) {
|
|
48
|
-
const threshold = thresholds[metric];
|
|
49
|
-
if (threshold === undefined) continue;
|
|
50
|
-
// Compute the actual ratio fresh from totals (without the rounded
|
|
51
|
-
// `pct`). A threshold of 100 then catches 99.999% correctly instead
|
|
52
|
-
// of being defeated by `pct`'s 2-decimal rounding.
|
|
53
|
-
const tot = summary.total[metric];
|
|
54
|
-
const actual = tot.total === 0 ? 100 : (tot.covered / tot.total) * 100;
|
|
55
|
-
if (actual < threshold) {
|
|
56
|
-
violations.push({ metric, actual, threshold });
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return violations;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Spec format (AC #5): `coverage: lines 84.2 < threshold 88` — one
|
|
64
|
-
* decimal place, no percent signs.
|
|
65
|
-
*/
|
|
66
|
-
export function violationSummary(violations: ThresholdViolation[]): string {
|
|
67
|
-
return violations
|
|
68
|
-
.map(
|
|
69
|
-
(v) =>
|
|
70
|
-
`coverage: ${v.metric} ${v.actual.toFixed(1)} < threshold ${v.threshold}`,
|
|
71
|
-
)
|
|
72
|
-
.join("\n");
|
|
73
|
-
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared types for the coverage pipeline.
|
|
3
|
-
*
|
|
4
|
-
* Stages: raw V8 JSON → `RawFileCoverage` (per-file after filter) →
|
|
5
|
-
* `AggregateCoverage` (merged across workers) → reporters / thresholds.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/** V8 function-range entry as emitted in `coverage-*.json` files. */
|
|
9
|
-
export interface V8Range {
|
|
10
|
-
startOffset: number;
|
|
11
|
-
endOffset: number;
|
|
12
|
-
count: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface V8Function {
|
|
16
|
-
functionName: string;
|
|
17
|
-
ranges: V8Range[];
|
|
18
|
-
isBlockCoverage: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface V8Script {
|
|
22
|
-
scriptId: string;
|
|
23
|
-
url: string;
|
|
24
|
-
functions: V8Function[];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface V8CoverageFile {
|
|
28
|
-
result: V8Script[];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Normalised per-file coverage — the unit passed between the collector,
|
|
33
|
-
* filter, aggregator, reporter and threshold stages.
|
|
34
|
-
*/
|
|
35
|
-
export interface RawFileCoverage {
|
|
36
|
-
/** Absolute file path (decoded from file:// URL). */
|
|
37
|
-
file: string;
|
|
38
|
-
/** Source text at the time V8 recorded coverage — needed to compute line
|
|
39
|
-
* offsets. We fall back to reading from disk if absent. */
|
|
40
|
-
source: string;
|
|
41
|
-
functions: V8Function[];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface FileSummary {
|
|
45
|
-
file: string;
|
|
46
|
-
lines: { covered: number; total: number };
|
|
47
|
-
functions: { covered: number; total: number };
|
|
48
|
-
statements: { covered: number; total: number };
|
|
49
|
-
branches: { covered: number; total: number };
|
|
50
|
-
/** Line numbers (1-based) with hit counts — for lcov DA entries. */
|
|
51
|
-
lineHits: Array<{ line: number; count: number }>;
|
|
52
|
-
/** Functions, with `(name, line, count)` — for lcov FN/FNDA entries. */
|
|
53
|
-
functionHits: Array<{ name: string; line: number; count: number }>;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface Totals {
|
|
57
|
-
lines: { covered: number; total: number; pct: number };
|
|
58
|
-
functions: { covered: number; total: number; pct: number };
|
|
59
|
-
statements: { covered: number; total: number; pct: number };
|
|
60
|
-
branches: { covered: number; total: number; pct: number };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export interface CoverageSummary {
|
|
64
|
-
files: FileSummary[];
|
|
65
|
-
total: Totals;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export interface Thresholds {
|
|
69
|
-
lines?: number;
|
|
70
|
-
functions?: number;
|
|
71
|
-
statements?: number;
|
|
72
|
-
branches?: number;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export interface ThresholdViolation {
|
|
76
|
-
metric: keyof Thresholds;
|
|
77
|
-
actual: number;
|
|
78
|
-
threshold: number;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export interface CoverageOptions {
|
|
82
|
-
enabled: boolean;
|
|
83
|
-
include?: string[];
|
|
84
|
-
exclude?: string[];
|
|
85
|
-
reporters?: string[];
|
|
86
|
-
outputDir?: string;
|
|
87
|
-
thresholds?: Thresholds;
|
|
88
|
-
/**
|
|
89
|
-
* Project root coverage paths are relative to (typically the package
|
|
90
|
-
* directory containing `package.json`). Defaults to `RunConfig.root`.
|
|
91
|
-
*/
|
|
92
|
-
root?: string;
|
|
93
|
-
}
|
package/src/cli/discover.ts
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* File-system discovery of test files.
|
|
3
|
-
*
|
|
4
|
-
* Design goals:
|
|
5
|
-
* - Walks a root directory
|
|
6
|
-
* - Honours `.gitignore` (basic pattern subset: basename match, leading-`/`
|
|
7
|
-
* root anchor, directory-trailing `/`, simple `*` glob, path-scoped
|
|
8
|
-
* `a/*.ts` patterns)
|
|
9
|
-
* - Uses `lstat` so symlinks are NOT followed (cycle-safe, matches Rust's
|
|
10
|
-
* `ignore::WalkBuilder::follow_links(false)`)
|
|
11
|
-
* - Tracks visited absolute paths so any escape via junctions is capped
|
|
12
|
-
* - Emits a warning on permission-denied subtrees (so silent tests-gone-missing
|
|
13
|
-
* is visible) while still returning the discoverable set
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
17
|
-
import { lstat, readdir } from "node:fs/promises";
|
|
18
|
-
import path from "node:path";
|
|
19
|
-
|
|
20
|
-
export interface DiscoveryOptions {
|
|
21
|
-
/** Filename suffixes that mark a test file (e.g. `.test.ts`). */
|
|
22
|
-
suffixes?: string[];
|
|
23
|
-
/** Directory basenames pruned from the walk. */
|
|
24
|
-
hardExcludes?: string[];
|
|
25
|
-
/** Read `.gitignore` at `root` + every descendant and apply rules. */
|
|
26
|
-
honourGitignore?: boolean;
|
|
27
|
-
/** Called with a human-readable message when a directory is skipped
|
|
28
|
-
* because of an IO error (ENOENT, EACCES). Default: `console.warn`. */
|
|
29
|
-
onWarn?: (message: string) => void;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const DEFAULT_SUFFIXES = [
|
|
33
|
-
".test.ts",
|
|
34
|
-
".test.tsx",
|
|
35
|
-
".test.js",
|
|
36
|
-
".test.mjs",
|
|
37
|
-
".test.cjs",
|
|
38
|
-
".spec.ts",
|
|
39
|
-
".spec.tsx",
|
|
40
|
-
".spec.js",
|
|
41
|
-
".spec.mjs",
|
|
42
|
-
".spec.cjs",
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
const DEFAULT_HARD_EXCLUDES = [
|
|
46
|
-
"node_modules",
|
|
47
|
-
"dist",
|
|
48
|
-
"build",
|
|
49
|
-
"coverage",
|
|
50
|
-
".git",
|
|
51
|
-
".wolf",
|
|
52
|
-
"target",
|
|
53
|
-
".next",
|
|
54
|
-
];
|
|
55
|
-
|
|
56
|
-
interface GitignorePattern {
|
|
57
|
-
raw: string;
|
|
58
|
-
/** If true, pattern is anchored to the directory that defined it. */
|
|
59
|
-
anchored: boolean;
|
|
60
|
-
/** If true, pattern only matches directories (trailing `/`). */
|
|
61
|
-
dirOnly: boolean;
|
|
62
|
-
/** Regex compiled from the literal pattern, matched against a relative path. */
|
|
63
|
-
regex: RegExp;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function compilePattern(line: string): GitignorePattern | undefined {
|
|
67
|
-
let p = line.trim();
|
|
68
|
-
if (!p || p.startsWith("#")) return undefined;
|
|
69
|
-
const anchored = p.startsWith("/");
|
|
70
|
-
if (anchored) p = p.slice(1);
|
|
71
|
-
const dirOnly = p.endsWith("/");
|
|
72
|
-
if (dirOnly) p = p.slice(0, -1);
|
|
73
|
-
if (!p) return undefined;
|
|
74
|
-
// Translate a tiny glob subset to regex:
|
|
75
|
-
// `*` → `[^/]*`
|
|
76
|
-
// `**` → `.*`
|
|
77
|
-
const escaped = p
|
|
78
|
-
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
79
|
-
.replace(/\*\*/g, "__DOUBLESTAR__")
|
|
80
|
-
.replace(/\*/g, "[^/]*")
|
|
81
|
-
.replace(/__DOUBLESTAR__/g, ".*");
|
|
82
|
-
const regex = anchored
|
|
83
|
-
? new RegExp(`^${escaped}(?:/.*)?$`)
|
|
84
|
-
: new RegExp(`(?:^|/)${escaped}(?:/.*)?$`);
|
|
85
|
-
return { raw: line, anchored, dirOnly, regex };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function readGitignore(dir: string): GitignorePattern[] {
|
|
89
|
-
const file = path.join(dir, ".gitignore");
|
|
90
|
-
if (!existsSync(file)) return [];
|
|
91
|
-
try {
|
|
92
|
-
return readFileSync(file, "utf8")
|
|
93
|
-
.split("\n")
|
|
94
|
-
.map(compilePattern)
|
|
95
|
-
.filter((p): p is GitignorePattern => p !== undefined);
|
|
96
|
-
} catch {
|
|
97
|
-
return [];
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function matches(
|
|
102
|
-
pattern: GitignorePattern,
|
|
103
|
-
relPath: string,
|
|
104
|
-
isDir: boolean,
|
|
105
|
-
): boolean {
|
|
106
|
-
if (pattern.dirOnly && !isDir) return false;
|
|
107
|
-
return pattern.regex.test(relPath);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export async function discover(
|
|
111
|
-
root: string,
|
|
112
|
-
options: DiscoveryOptions = {},
|
|
113
|
-
): Promise<string[]> {
|
|
114
|
-
const suffixes = options.suffixes ?? DEFAULT_SUFFIXES;
|
|
115
|
-
const hardExcludes = new Set(options.hardExcludes ?? DEFAULT_HARD_EXCLUDES);
|
|
116
|
-
const honourGitignore = options.honourGitignore ?? true;
|
|
117
|
-
const warn = options.onWarn ?? ((m) => process.stderr.write(`helix: ${m}\n`));
|
|
118
|
-
|
|
119
|
-
const results: string[] = [];
|
|
120
|
-
const absRoot = path.isAbsolute(root) ? root : path.resolve(root);
|
|
121
|
-
const visited = new Set<string>();
|
|
122
|
-
|
|
123
|
-
async function walk(
|
|
124
|
-
dir: string,
|
|
125
|
-
relativeToRoot: string,
|
|
126
|
-
inherited: GitignorePattern[],
|
|
127
|
-
): Promise<void> {
|
|
128
|
-
const realDir = path.resolve(dir);
|
|
129
|
-
if (visited.has(realDir)) return;
|
|
130
|
-
visited.add(realDir);
|
|
131
|
-
|
|
132
|
-
let entries: string[];
|
|
133
|
-
try {
|
|
134
|
-
entries = await readdir(dir);
|
|
135
|
-
} catch (err) {
|
|
136
|
-
warn(`skipping ${dir}: ${(err as NodeJS.ErrnoException).code ?? err}`);
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
const local = honourGitignore
|
|
140
|
-
? [...inherited, ...readGitignore(dir)]
|
|
141
|
-
: inherited;
|
|
142
|
-
|
|
143
|
-
for (const name of entries) {
|
|
144
|
-
if (hardExcludes.has(name)) continue;
|
|
145
|
-
const relPath = relativeToRoot ? `${relativeToRoot}/${name}` : name;
|
|
146
|
-
const fullPath = path.join(dir, name);
|
|
147
|
-
let st: Awaited<ReturnType<typeof lstat>>;
|
|
148
|
-
try {
|
|
149
|
-
st = await lstat(fullPath);
|
|
150
|
-
} catch (err) {
|
|
151
|
-
warn(
|
|
152
|
-
`skipping ${fullPath}: ${(err as NodeJS.ErrnoException).code ?? err}`,
|
|
153
|
-
);
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
// Symlinks are NOT followed. Users who need symlink-following can
|
|
157
|
-
// walk the target manually; matches Rust's `follow_links(false)`.
|
|
158
|
-
if (st.isSymbolicLink()) continue;
|
|
159
|
-
if (local.some((p) => matches(p, relPath, st.isDirectory()))) continue;
|
|
160
|
-
if (st.isDirectory()) {
|
|
161
|
-
await walk(fullPath, relPath, local);
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
if (!st.isFile()) continue;
|
|
165
|
-
if (suffixes.some((s) => name.endsWith(s))) {
|
|
166
|
-
results.push(fullPath);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
await walk(absRoot, "", []);
|
|
172
|
-
results.sort();
|
|
173
|
-
return results;
|
|
174
|
-
}
|
package/src/cli/native.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Loads the native `ream-test-napi` binary built by `scripts/copy-napi.mjs`
|
|
3
|
-
* and exposes the Rust orchestrator `run(config)` to the TS CLI.
|
|
4
|
-
*
|
|
5
|
-
* Per the orchestrator design (42-N-orchestrator), the Rust NAPI engine is the
|
|
6
|
-
* canonical discovery + worker-pool + reporter + summary path; the TS `runOnce`
|
|
7
|
-
* delegates to it whenever no TS-only layer (coverage / diff-cov / watch / a
|
|
8
|
-
* pluggable reporter instance) is in play. There is NO JS fallback for a failed
|
|
9
|
-
* load — the caller gets a typed error pointing at `build:napi`.
|
|
10
|
-
*
|
|
11
|
-
* Field names are camelCase: napi-rs converts the Rust struct's snake_case
|
|
12
|
-
* fields automatically (`timeout_ms` → `timeoutMs`, etc.).
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { createRequire } from "node:module";
|
|
16
|
-
import { arch, platform } from "node:process";
|
|
17
|
-
import { fileURLToPath } from "node:url";
|
|
18
|
-
|
|
19
|
-
const SUFFIX_MAP: Readonly<Record<string, string>> = {
|
|
20
|
-
"linux-x64": "linux-x64-gnu",
|
|
21
|
-
"linux-arm64": "linux-arm64-gnu",
|
|
22
|
-
"darwin-x64": "darwin-x64",
|
|
23
|
-
"darwin-arm64": "darwin-arm64",
|
|
24
|
-
"win32-x64": "win32-x64-msvc",
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
function platformSuffix(): string {
|
|
28
|
-
const key = `${platform}-${arch}`;
|
|
29
|
-
const suffix = SUFFIX_MAP[key];
|
|
30
|
-
if (typeof suffix !== "string") {
|
|
31
|
-
throw new Error(
|
|
32
|
-
`Unsupported platform/arch '${key}' for @c9up/helix native binary. Supported: ${Object.keys(SUFFIX_MAP).join(", ")}.`,
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
return suffix;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** Mirror of the Rust `RunConfig` (camelCase). */
|
|
39
|
-
export interface NativeRunConfig {
|
|
40
|
-
readonly root: string;
|
|
41
|
-
readonly files?: readonly string[];
|
|
42
|
-
readonly threads?: number;
|
|
43
|
-
readonly timeoutMs?: number;
|
|
44
|
-
readonly reporter?: string;
|
|
45
|
-
readonly workerEntry: string;
|
|
46
|
-
readonly nodeBin?: string;
|
|
47
|
-
readonly nodeArgs?: readonly string[];
|
|
48
|
-
readonly useColors?: boolean;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** Mirror of the Rust `SummaryPayload` (camelCase). */
|
|
52
|
-
export interface NativeSummaryPayload {
|
|
53
|
-
readonly pass: number;
|
|
54
|
-
readonly fail: number;
|
|
55
|
-
readonly skip: number;
|
|
56
|
-
readonly todo: number;
|
|
57
|
-
readonly fileErrors: number;
|
|
58
|
-
readonly durationMs: number;
|
|
59
|
-
readonly exitCode: number;
|
|
60
|
-
/** Full `Summary` serialized as JSON (same shape as TS `Summary`). */
|
|
61
|
-
readonly json: string;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
interface NativeExports {
|
|
65
|
-
readonly run: (config: NativeRunConfig) => Promise<NativeSummaryPayload>;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function isNativeExports(value: unknown): value is NativeExports {
|
|
69
|
-
if (value === null || typeof value !== "object") return false;
|
|
70
|
-
return typeof Reflect.get(value, "run") === "function";
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
let cachedNative: NativeExports | undefined;
|
|
74
|
-
|
|
75
|
-
export function getNative(): NativeExports {
|
|
76
|
-
if (cachedNative !== undefined) return cachedNative;
|
|
77
|
-
|
|
78
|
-
const require = createRequire(import.meta.url);
|
|
79
|
-
const here = fileURLToPath(import.meta.url);
|
|
80
|
-
// `here` is `…/packages/helix/{src,dist}/cli/native.ts|js`. The `.node` lives
|
|
81
|
-
// two levels up at `…/packages/helix/index.<suffix>.node`.
|
|
82
|
-
const suffix = platformSuffix();
|
|
83
|
-
const candidate = `../../index.${suffix}.node`;
|
|
84
|
-
let loaded: unknown;
|
|
85
|
-
try {
|
|
86
|
-
loaded = require(candidate);
|
|
87
|
-
} catch (err) {
|
|
88
|
-
const cause = err instanceof Error ? err.message : String(err);
|
|
89
|
-
const muslHint = suffix.endsWith("-gnu")
|
|
90
|
-
? " If you are on Alpine/musl, note the prebuilt binaries target glibc (musl is not a supported target)."
|
|
91
|
-
: "";
|
|
92
|
-
throw new Error(
|
|
93
|
-
`@c9up/helix native binary 'index.${suffix}.node' not found or failed to load near ${here} — run 'pnpm --filter @c9up/helix build:napi' to build it.${muslHint} Cause: ${cause}`,
|
|
94
|
-
{ cause: err },
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
if (!isNativeExports(loaded)) {
|
|
98
|
-
throw new Error(
|
|
99
|
-
"@c9up/helix native binary loaded but missing the expected 'run' export. Rebuild with 'pnpm --filter @c9up/helix build:napi'.",
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
cachedNative = loaded;
|
|
103
|
-
return cachedNative;
|
|
104
|
-
}
|