flakeradar 0.1.0

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 (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +309 -0
  3. package/bin/flakeradar.js +11 -0
  4. package/dist/analysis/classify.d.ts +10 -0
  5. package/dist/analysis/classify.d.ts.map +1 -0
  6. package/dist/analysis/classify.js +163 -0
  7. package/dist/analysis/classify.js.map +1 -0
  8. package/dist/analysis/flakiness.d.ts +11 -0
  9. package/dist/analysis/flakiness.d.ts.map +1 -0
  10. package/dist/analysis/flakiness.js +177 -0
  11. package/dist/analysis/flakiness.js.map +1 -0
  12. package/dist/cli.d.ts +5 -0
  13. package/dist/cli.d.ts.map +1 -0
  14. package/dist/cli.js +77 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/color.d.ts +19 -0
  17. package/dist/color.d.ts.map +1 -0
  18. package/dist/color.js +36 -0
  19. package/dist/color.js.map +1 -0
  20. package/dist/commands/analyze.d.ts +4 -0
  21. package/dist/commands/analyze.d.ts.map +1 -0
  22. package/dist/commands/analyze.js +106 -0
  23. package/dist/commands/analyze.js.map +1 -0
  24. package/dist/commands/run.d.ts +4 -0
  25. package/dist/commands/run.d.ts.map +1 -0
  26. package/dist/commands/run.js +116 -0
  27. package/dist/commands/run.js.map +1 -0
  28. package/dist/commands/shared.d.ts +16 -0
  29. package/dist/commands/shared.d.ts.map +1 -0
  30. package/dist/commands/shared.js +44 -0
  31. package/dist/commands/shared.js.map +1 -0
  32. package/dist/index.d.ts +14 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +13 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/junit/parser.d.ts +11 -0
  37. package/dist/junit/parser.d.ts.map +1 -0
  38. package/dist/junit/parser.js +98 -0
  39. package/dist/junit/parser.js.map +1 -0
  40. package/dist/junit/xml.d.ts +27 -0
  41. package/dist/junit/xml.d.ts.map +1 -0
  42. package/dist/junit/xml.js +238 -0
  43. package/dist/junit/xml.js.map +1 -0
  44. package/dist/report/json.d.ts +7 -0
  45. package/dist/report/json.d.ts.map +1 -0
  46. package/dist/report/json.js +38 -0
  47. package/dist/report/json.js.map +1 -0
  48. package/dist/report/markdown.d.ts +7 -0
  49. package/dist/report/markdown.d.ts.map +1 -0
  50. package/dist/report/markdown.js +75 -0
  51. package/dist/report/markdown.js.map +1 -0
  52. package/dist/report/terminal.d.ts +4 -0
  53. package/dist/report/terminal.d.ts.map +1 -0
  54. package/dist/report/terminal.js +101 -0
  55. package/dist/report/terminal.js.map +1 -0
  56. package/dist/runner/runner.d.ts +45 -0
  57. package/dist/runner/runner.d.ts.map +1 -0
  58. package/dist/runner/runner.js +125 -0
  59. package/dist/runner/runner.js.map +1 -0
  60. package/dist/types.d.ts +87 -0
  61. package/dist/types.d.ts.map +1 -0
  62. package/dist/types.js +2 -0
  63. package/dist/types.js.map +1 -0
  64. package/dist/util/args.d.ts +23 -0
  65. package/dist/util/args.d.ts.map +1 -0
  66. package/dist/util/args.js +83 -0
  67. package/dist/util/args.js.map +1 -0
  68. package/dist/util/glob.d.ts +11 -0
  69. package/dist/util/glob.d.ts.map +1 -0
  70. package/dist/util/glob.js +104 -0
  71. package/dist/util/glob.js.map +1 -0
  72. package/package.json +62 -0
@@ -0,0 +1,4 @@
1
+ import type { AnalysisReport } from "../types.js";
2
+ /** Render the full report as colored text for a terminal. */
3
+ export declare function renderTerminal(report: AnalysisReport): string;
4
+ //# sourceMappingURL=terminal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../../src/report/terminal.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAe,MAAM,aAAa,CAAC;AAa/D,6DAA6D;AAC7D,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CA2C7D"}
@@ -0,0 +1,101 @@
1
+ import { causeLabel } from "../analysis/classify.js";
2
+ import { color } from "../color.js";
3
+ const pct = (n) => `${Math.round(n * 100)}%`;
4
+ function ratio(v) {
5
+ return `${v.failures}/${v.passes + v.failures}`;
6
+ }
7
+ function shorten(s, max) {
8
+ if (s.length <= max)
9
+ return s;
10
+ return `${s.slice(0, max - 1)}…`;
11
+ }
12
+ /** Render the full report as colored text for a terminal. */
13
+ export function renderTerminal(report) {
14
+ const { summary, flaky, alwaysFailing } = report;
15
+ const lines = [];
16
+ lines.push(color.bold(color.cyan("flakeradar")) + color.dim(" — flakiness report"));
17
+ lines.push("");
18
+ const greenColor = summary.greenRunRate >= 0.9 ? color.green : summary.greenRunRate >= 0.5 ? color.yellow : color.red;
19
+ lines.push([
20
+ `${color.dim("Runs analyzed:")} ${color.bold(String(summary.totalRuns))}`,
21
+ `${color.dim("Green runs:")} ${greenColor(pct(summary.greenRunRate))}`,
22
+ `${color.dim("Crashed:")} ${summary.crashedRuns > 0 ? color.red(String(summary.crashedRuns)) : "0"}`,
23
+ ].join(" "));
24
+ lines.push([
25
+ `${color.dim("Unique tests:")} ${summary.uniqueTests}`,
26
+ `${color.dim("Flaky:")} ${summary.flakyCount > 0 ? color.yellow(String(summary.flakyCount)) : color.green("0")}`,
27
+ `${color.dim("Always-failing:")} ${summary.alwaysFailCount > 0 ? color.red(String(summary.alwaysFailCount)) : "0"}`,
28
+ ].join(" "));
29
+ lines.push("");
30
+ if (flaky.length === 0) {
31
+ lines.push(color.green("✔ No flaky tests detected. ") + color.dim("Every test was consistent across all runs."));
32
+ }
33
+ else {
34
+ lines.push(color.bold(`Flaky tests (ranked, worst first):`));
35
+ lines.push(renderFlakyTable(flaky));
36
+ lines.push("");
37
+ lines.push(renderTopDetail(flaky[0]));
38
+ }
39
+ if (alwaysFailing.length > 0) {
40
+ lines.push("");
41
+ lines.push(color.bold(color.red("Always-failing")) + color.dim(" (broken every run — likely a real bug, not flakiness):"));
42
+ for (const v of alwaysFailing.slice(0, 10)) {
43
+ lines.push(` ${color.red("✗")} ${shorten(v.id, 70)} ${color.dim(`(${v.failures}/${v.failures} failed)`)}`);
44
+ }
45
+ if (alwaysFailing.length > 10)
46
+ lines.push(color.dim(` …and ${alwaysFailing.length - 10} more`));
47
+ }
48
+ return lines.join("\n");
49
+ }
50
+ function renderFlakyTable(flaky) {
51
+ const header = ["#", "Test", "Fails", "Rate", "Flips", "Likely cause", "Conf"];
52
+ const rows = flaky.map((v, i) => [
53
+ String(i + 1),
54
+ shorten(v.id, 48),
55
+ ratio(v),
56
+ pct(v.failureRate),
57
+ String(v.flips),
58
+ v.cause ? causeLabel(v.cause.category) : "-",
59
+ v.cause ? pct(v.cause.confidence) : "-",
60
+ ]);
61
+ const widths = header.map((h, col) => Math.max(h.length, ...rows.map((r) => r[col].length)));
62
+ const sep = " ";
63
+ const fmtRow = (cells, colorize, v) => cells
64
+ .map((c, col) => {
65
+ const padded = col === 1 || col === 5 ? c.padEnd(widths[col]) : c.padStart(widths[col]);
66
+ if (colorize && v)
67
+ return colorize(v, col, padded);
68
+ return padded;
69
+ })
70
+ .join(sep);
71
+ const out = [];
72
+ out.push(color.dim(fmtRow(header)));
73
+ flaky.forEach((v, i) => {
74
+ out.push(fmtRow(rows[i], (verdict, col, text) => {
75
+ if (col === 3) {
76
+ // failure rate
77
+ return verdict.failureRate >= 0.5 ? color.red(text) : color.yellow(text);
78
+ }
79
+ if (col === 5)
80
+ return color.magenta(text);
81
+ return text;
82
+ }, v));
83
+ });
84
+ return out.join("\n");
85
+ }
86
+ function renderTopDetail(top) {
87
+ const lines = [];
88
+ lines.push(color.bold("Top offender: ") + color.yellow(top.id));
89
+ if (top.cause) {
90
+ lines.push(` ${color.dim("likely:")} ${color.magenta(causeLabel(top.cause.category))} ${color.dim(`(${pct(top.cause.confidence)} confident)`)}`);
91
+ lines.push(` ${color.dim("→")} ${top.cause.hint}`);
92
+ }
93
+ if (top.sampleMessages.length > 0) {
94
+ lines.push(` ${color.dim("sample failures:")}`);
95
+ for (const m of top.sampleMessages) {
96
+ lines.push(` ${color.red("•")} ${color.dim(shorten(m, 100))}`);
97
+ }
98
+ }
99
+ return lines.join("\n");
100
+ }
101
+ //# sourceMappingURL=terminal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal.js","sourceRoot":"","sources":["../../src/report/terminal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import type { Run } from "../types.js";
2
+ declare const OUT_TOKEN = "{out}";
3
+ export interface ProgressInfo {
4
+ index: number;
5
+ total: number;
6
+ passed: number;
7
+ failed: number;
8
+ crashed: boolean;
9
+ durationMs: number;
10
+ }
11
+ export interface RunnerOptions {
12
+ /** Command argv (everything after `--`). */
13
+ command: string[];
14
+ /** How many times to execute the command. */
15
+ runs: number;
16
+ /**
17
+ * Glob for the JUnit XML the command writes each run. Ignored when the
18
+ * command contains the `{out}` token.
19
+ */
20
+ reportGlob?: string;
21
+ /** Directory used for `{out}` per-run report files. */
22
+ outputDir: string;
23
+ /** Keep running remaining iterations even if one crashes. */
24
+ keepGoing: boolean;
25
+ cwd?: string;
26
+ onProgress?: (info: ProgressInfo) => void;
27
+ /**
28
+ * Called after each run with all runs so far. Return `true` to stop early
29
+ * (used by `--stop-on-first-flaky`).
30
+ */
31
+ afterRun?: (runs: Run[]) => boolean;
32
+ }
33
+ /** Execute the whole run loop and return the collected runs. */
34
+ export declare function executeRuns(opts: RunnerOptions): Promise<Run[]>;
35
+ /**
36
+ * Quote a single argument so a shell re-parses it as one token. We receive
37
+ * already-split argv (the user's shell split it), so we must re-quote args
38
+ * that contain spaces/metacharacters — otherwise `pytest -k "a or b"` breaks
39
+ * under cmd.exe on Windows.
40
+ */
41
+ export declare function quoteArg(arg: string): string;
42
+ /** Join argv into a single shell command line with proper quoting. */
43
+ export declare function buildCommandLine(argv: string[]): string;
44
+ export { OUT_TOKEN };
45
+ //# sourceMappingURL=runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/runner/runner.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,GAAG,EAAc,MAAM,aAAa,CAAC;AAGnD,QAAA,MAAM,SAAS,UAAU,CAAC;AAE1B,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,4CAA4C;IAC5C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,SAAS,EAAE,OAAO,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IAC1C;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC;CACrC;AASD,gEAAgE;AAChE,wBAAsB,WAAW,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAyCrE;AAqCD;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI5C;AAED,sEAAsE;AACtE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAEvD;AAuCD,OAAO,EAAE,SAAS,EAAE,CAAC"}
@@ -0,0 +1,125 @@
1
+ import { spawn } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { parseJUnitXml } from "../junit/parser.js";
5
+ import { glob } from "../util/glob.js";
6
+ const OUT_TOKEN = "{out}";
7
+ /** Execute the whole run loop and return the collected runs. */
8
+ export async function executeRuns(opts) {
9
+ const usesToken = opts.command.some((a) => a.includes(OUT_TOKEN));
10
+ if (usesToken) {
11
+ fs.mkdirSync(opts.outputDir, { recursive: true });
12
+ }
13
+ const runs = [];
14
+ for (let i = 0; i < opts.runs; i++) {
15
+ const index = i + 1;
16
+ const outFile = path.join(opts.outputDir, `run-${index}.xml`);
17
+ // Prepare command for this iteration.
18
+ const argv = usesToken
19
+ ? opts.command.map((a) => a.split(OUT_TOKEN).join(outFile))
20
+ : opts.command;
21
+ // Clear stale report so we only read this run's output.
22
+ if (usesToken)
23
+ safeUnlink(outFile);
24
+ const exec = await runOnce(argv, opts.cwd);
25
+ const results = collectResults({ usesToken, outFile, reportGlob: opts.reportGlob, cwd: opts.cwd });
26
+ const passed = results.filter((r) => r.status === "passed").length;
27
+ const failed = results.filter((r) => r.status === "failed" || r.status === "error").length;
28
+ const noReport = results.length === 0;
29
+ const crashed = noReport && (exec.exitCode !== 0 || exec.signal !== null);
30
+ const note = buildNote(noReport, exec);
31
+ runs.push({ label: `run ${index}`, results, crashed, note });
32
+ opts.onProgress?.({ index, total: opts.runs, passed, failed, crashed, durationMs: exec.durationMs });
33
+ if (crashed && !opts.keepGoing) {
34
+ break;
35
+ }
36
+ if (opts.afterRun?.(runs)) {
37
+ break;
38
+ }
39
+ }
40
+ return runs;
41
+ }
42
+ function buildNote(noReport, exec) {
43
+ if (!noReport)
44
+ return undefined;
45
+ if (exec.exitCode !== 0 || exec.signal !== null) {
46
+ const reason = exec.signal ? `signal ${exec.signal}` : `exit code ${exec.exitCode}`;
47
+ const tail = exec.stderrTail ? ` — ${exec.stderrTail}` : "";
48
+ return `no JUnit report produced; command ended with ${reason}${tail}`;
49
+ }
50
+ return "command succeeded but produced no JUnit report (check --report path or the {out} token)";
51
+ }
52
+ function collectResults(args) {
53
+ const files = [];
54
+ if (args.usesToken) {
55
+ if (fs.existsSync(args.outFile))
56
+ files.push(args.outFile);
57
+ }
58
+ else if (args.reportGlob) {
59
+ files.push(...glob(args.reportGlob, args.cwd));
60
+ }
61
+ const results = [];
62
+ for (const file of files) {
63
+ try {
64
+ const xml = fs.readFileSync(file, "utf8");
65
+ results.push(...parseJUnitXml(xml, file));
66
+ }
67
+ catch {
68
+ // Unreadable file — skip; a crashed run will be flagged separately.
69
+ }
70
+ }
71
+ return results;
72
+ }
73
+ /**
74
+ * Quote a single argument so a shell re-parses it as one token. We receive
75
+ * already-split argv (the user's shell split it), so we must re-quote args
76
+ * that contain spaces/metacharacters — otherwise `pytest -k "a or b"` breaks
77
+ * under cmd.exe on Windows.
78
+ */
79
+ export function quoteArg(arg) {
80
+ if (arg.length === 0)
81
+ return '""';
82
+ if (/^[A-Za-z0-9_@%+=:,./\\-]+$/.test(arg))
83
+ return arg;
84
+ return `"${arg.replace(/"/g, '\\"')}"`;
85
+ }
86
+ /** Join argv into a single shell command line with proper quoting. */
87
+ export function buildCommandLine(argv) {
88
+ return argv.map(quoteArg).join(" ");
89
+ }
90
+ function runOnce(argv, cwd) {
91
+ const start = Date.now();
92
+ return new Promise((resolve) => {
93
+ const child = spawn(buildCommandLine(argv), {
94
+ cwd,
95
+ shell: true,
96
+ stdio: ["ignore", "ignore", "pipe"],
97
+ });
98
+ let stderr = "";
99
+ child.stderr?.on("data", (chunk) => {
100
+ stderr += chunk.toString();
101
+ if (stderr.length > 4000)
102
+ stderr = stderr.slice(-4000);
103
+ });
104
+ child.on("error", () => {
105
+ resolve({ exitCode: 127, signal: null, stderrTail: lastLine(stderr) || "failed to spawn command", durationMs: Date.now() - start });
106
+ });
107
+ child.on("close", (code, signal) => {
108
+ resolve({ exitCode: code, signal, stderrTail: lastLine(stderr), durationMs: Date.now() - start });
109
+ });
110
+ });
111
+ }
112
+ function lastLine(text) {
113
+ const lines = text.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
114
+ return lines.length > 0 ? lines[lines.length - 1] : "";
115
+ }
116
+ function safeUnlink(p) {
117
+ try {
118
+ fs.unlinkSync(p);
119
+ }
120
+ catch {
121
+ /* ignore */
122
+ }
123
+ }
124
+ export { OUT_TOKEN };
125
+ //# sourceMappingURL=runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/runner/runner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ /** Normalized outcome of a single test case within a single run. */
2
+ export type TestStatus = "passed" | "failed" | "error" | "skipped";
3
+ /** A single test case result parsed from one JUnit XML report. */
4
+ export interface TestResult {
5
+ /** Stable identity across runs: `classname::name` (or just `name`). */
6
+ id: string;
7
+ name: string;
8
+ classname: string;
9
+ status: TestStatus;
10
+ /** Duration in milliseconds, if reported. */
11
+ durationMs: number | null;
12
+ /** Short failure/error message, if any. */
13
+ message: string | null;
14
+ /** Full failure/error body (stack trace / assertion detail), if any. */
15
+ details: string | null;
16
+ }
17
+ /**
18
+ * One "run" of a suite: the collection of test results produced by a single
19
+ * execution (or a single ingested report file/directory).
20
+ */
21
+ export interface Run {
22
+ /** Human-friendly label, e.g. "run 3" or a file path. */
23
+ label: string;
24
+ results: TestResult[];
25
+ /**
26
+ * True when the run itself failed to produce parseable results
27
+ * (e.g. the test command crashed before writing a report).
28
+ */
29
+ crashed: boolean;
30
+ /** Optional note explaining a crash or anomaly. */
31
+ note?: string;
32
+ }
33
+ /** A single observed failure sample used for root-cause classification. */
34
+ export interface FailureSample {
35
+ message: string | null;
36
+ details: string | null;
37
+ }
38
+ /** Root-cause category assigned by the heuristic classifier. */
39
+ export type CauseCategory = "timing" | "concurrency" | "resource" | "external" | "order-dependency" | "randomness" | "unknown";
40
+ export interface CauseGuess {
41
+ category: CauseCategory;
42
+ /** 0..1 rough confidence in the guess. */
43
+ confidence: number;
44
+ /** One-line human-readable hint about the likely cause and what to check. */
45
+ hint: string;
46
+ }
47
+ /** Aggregated verdict for a single test across all runs. */
48
+ export interface TestVerdict {
49
+ id: string;
50
+ name: string;
51
+ classname: string;
52
+ runsSeen: number;
53
+ passes: number;
54
+ failures: number;
55
+ skips: number;
56
+ /** failures / (passes + failures); NaN-safe (0 when no non-skip runs). */
57
+ failureRate: number;
58
+ /** Number of pass<->fail transitions across the ordered runs (ignoring skips). */
59
+ flips: number;
60
+ /** True when the test both passed and failed at least once. */
61
+ flaky: boolean;
62
+ /** True when every non-skip run failed. */
63
+ alwaysFails: boolean;
64
+ /** Ranking score; higher = more worth investigating. */
65
+ score: number;
66
+ cause: CauseGuess | null;
67
+ /** Up to a few representative failure messages. */
68
+ sampleMessages: string[];
69
+ }
70
+ export interface SuiteSummary {
71
+ totalRuns: number;
72
+ crashedRuns: number;
73
+ uniqueTests: number;
74
+ flakyCount: number;
75
+ alwaysFailCount: number;
76
+ alwaysPassCount: number;
77
+ /** Fraction of runs with zero failures (a "green" run). */
78
+ greenRunRate: number;
79
+ }
80
+ export interface AnalysisReport {
81
+ summary: SuiteSummary;
82
+ /** Flaky tests, ranked by score (desc). */
83
+ flaky: TestVerdict[];
84
+ /** Consistently failing tests (not flaky, but broken). */
85
+ alwaysFailing: TestVerdict[];
86
+ }
87
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAEnE,kEAAkE;AAClE,MAAM,WAAW,UAAU;IACzB,uEAAuE;IACvE,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,UAAU,CAAC;IACnB,6CAA6C;IAC7C,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,2CAA2C;IAC3C,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,wEAAwE;IACxE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,GAAG;IAClB,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB,mDAAmD;IACnD,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,2EAA2E;AAC3E,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,gEAAgE;AAChE,MAAM,MAAM,aAAa,GACrB,QAAQ,GACR,aAAa,GACb,UAAU,GACV,UAAU,GACV,kBAAkB,GAClB,YAAY,GACZ,SAAS,CAAC;AAEd,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,aAAa,CAAC;IACxB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;CACd;AAED,4DAA4D;AAC5D,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,WAAW,EAAE,MAAM,CAAC;IACpB,kFAAkF;IAClF,KAAK,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,KAAK,EAAE,OAAO,CAAC;IACf,2CAA2C;IAC3C,WAAW,EAAE,OAAO,CAAC;IACrB,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACzB,mDAAmD;IACnD,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,YAAY,CAAC;IACtB,2CAA2C;IAC3C,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,0DAA0D;IAC1D,aAAa,EAAE,WAAW,EAAE,CAAC;CAC9B"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Tiny zero-dependency argument parser. Supports `--flag`, `--key value`,
3
+ * `--key=value`, short aliases, and a `--` separator whose remainder is
4
+ * returned verbatim (used to capture the user's test command).
5
+ */
6
+ export interface ArgSpec {
7
+ /** Long names of boolean flags (no value). */
8
+ bools?: string[];
9
+ /** Long names of flags that take a value. */
10
+ values?: string[];
11
+ /** Map of short/alternate name -> canonical long name. */
12
+ aliases?: Record<string, string>;
13
+ }
14
+ export interface ParsedArgs {
15
+ bools: Set<string>;
16
+ values: Map<string, string>;
17
+ positionals: string[];
18
+ /** Tokens after a literal `--`, unparsed. */
19
+ rest: string[];
20
+ errors: string[];
21
+ }
22
+ export declare function parseArgs(argv: string[], spec: ArgSpec): ParsedArgs;
23
+ //# sourceMappingURL=args.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/util/args.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,OAAO;IACtB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,0DAA0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACnB,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,6CAA6C;IAC7C,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,GAAG,UAAU,CAwEnE"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Tiny zero-dependency argument parser. Supports `--flag`, `--key value`,
3
+ * `--key=value`, short aliases, and a `--` separator whose remainder is
4
+ * returned verbatim (used to capture the user's test command).
5
+ */
6
+ export function parseArgs(argv, spec) {
7
+ const bools = new Set();
8
+ const values = new Map();
9
+ const positionals = [];
10
+ const errors = [];
11
+ let rest = [];
12
+ const boolSet = new Set(spec.bools ?? []);
13
+ const valueSet = new Set(spec.values ?? []);
14
+ const aliases = spec.aliases ?? {};
15
+ const canonical = (name) => aliases[name] ?? name;
16
+ for (let i = 0; i < argv.length; i++) {
17
+ const token = argv[i];
18
+ if (token === "--") {
19
+ rest = argv.slice(i + 1);
20
+ break;
21
+ }
22
+ if (token.startsWith("--")) {
23
+ const eq = token.indexOf("=");
24
+ const rawName = eq === -1 ? token.slice(2) : token.slice(2, eq);
25
+ const name = canonical(rawName);
26
+ if (boolSet.has(name)) {
27
+ bools.add(name);
28
+ }
29
+ else if (valueSet.has(name)) {
30
+ if (eq !== -1) {
31
+ values.set(name, token.slice(eq + 1));
32
+ }
33
+ else if (i + 1 < argv.length) {
34
+ values.set(name, argv[++i]);
35
+ }
36
+ else {
37
+ errors.push(`missing value for --${rawName}`);
38
+ }
39
+ }
40
+ else {
41
+ errors.push(`unknown option --${rawName}`);
42
+ }
43
+ continue;
44
+ }
45
+ if (token.startsWith("-") && token.length > 1 && !isNegativeNumber(token)) {
46
+ const eq = token.indexOf("=");
47
+ const rawName = eq === -1 ? token.slice(1, 2) : token.slice(1, eq);
48
+ const name = canonical(rawName);
49
+ // Allow `-n5` compact form for value flags.
50
+ const inlineValue = eq === -1 ? token.slice(2) : token.slice(eq + 1);
51
+ if (boolSet.has(name)) {
52
+ bools.add(name);
53
+ // Support stacked bool shorts like -ab
54
+ for (const ch of token.slice(2)) {
55
+ const n = canonical(ch);
56
+ if (boolSet.has(n))
57
+ bools.add(n);
58
+ }
59
+ }
60
+ else if (valueSet.has(name)) {
61
+ if (inlineValue) {
62
+ values.set(name, inlineValue);
63
+ }
64
+ else if (i + 1 < argv.length) {
65
+ values.set(name, argv[++i]);
66
+ }
67
+ else {
68
+ errors.push(`missing value for -${rawName}`);
69
+ }
70
+ }
71
+ else {
72
+ errors.push(`unknown option -${rawName}`);
73
+ }
74
+ continue;
75
+ }
76
+ positionals.push(token);
77
+ }
78
+ return { bools, values, positionals, rest, errors };
79
+ }
80
+ function isNegativeNumber(token) {
81
+ return /^-\d/.test(token);
82
+ }
83
+ //# sourceMappingURL=args.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/util/args.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Minimal, dependency-free glob supporting `**`, `*`, and `?`. Needed because
3
+ * shells (especially on Windows) don't expand globs for us, and both `analyze`
4
+ * and `run` accept glob patterns for JUnit report locations.
5
+ *
6
+ * Returns absolute file paths, sorted and de-duplicated.
7
+ */
8
+ export declare function glob(pattern: string, cwd?: string): string[];
9
+ /** Expand many patterns and merge results (de-duplicated). */
10
+ export declare function globMany(patterns: string[], cwd?: string): string[];
11
+ //# sourceMappingURL=glob.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../../src/util/glob.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,SAAgB,GAAG,MAAM,EAAE,CAwBnE;AAED,8DAA8D;AAC9D,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,GAAG,SAAgB,GAAG,MAAM,EAAE,CAM1E"}
@@ -0,0 +1,104 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ /**
4
+ * Minimal, dependency-free glob supporting `**`, `*`, and `?`. Needed because
5
+ * shells (especially on Windows) don't expand globs for us, and both `analyze`
6
+ * and `run` accept glob patterns for JUnit report locations.
7
+ *
8
+ * Returns absolute file paths, sorted and de-duplicated.
9
+ */
10
+ export function glob(pattern, cwd = process.cwd()) {
11
+ const norm = pattern.replace(/\\/g, "/");
12
+ const parts = norm.split("/");
13
+ const baseParts = [];
14
+ let i = 0;
15
+ for (; i < parts.length; i++) {
16
+ if (hasMagic(parts[i]))
17
+ break;
18
+ baseParts.push(parts[i]);
19
+ }
20
+ const globParts = parts.slice(i).filter((s) => s.length > 0);
21
+ if (globParts.length === 0) {
22
+ const resolved = path.resolve(cwd, norm);
23
+ return isFile(resolved) ? [resolved] : [];
24
+ }
25
+ const isAbsolute = norm.startsWith("/") || /^[A-Za-z]:\//.test(norm);
26
+ const baseJoined = baseParts.join("/");
27
+ const baseDir = isAbsolute ? baseJoined || "/" : path.resolve(cwd, baseJoined || ".");
28
+ const out = new Set();
29
+ walk(baseDir, globParts, out);
30
+ return [...out].sort();
31
+ }
32
+ /** Expand many patterns and merge results (de-duplicated). */
33
+ export function globMany(patterns, cwd = process.cwd()) {
34
+ const set = new Set();
35
+ for (const pat of patterns) {
36
+ for (const file of glob(pat, cwd))
37
+ set.add(file);
38
+ }
39
+ return [...set].sort();
40
+ }
41
+ function hasMagic(segment) {
42
+ return /[*?[]/.test(segment);
43
+ }
44
+ function segmentToRegex(seg) {
45
+ let re = "^";
46
+ for (const ch of seg) {
47
+ if (ch === "*")
48
+ re += "[^/]*";
49
+ else if (ch === "?")
50
+ re += "[^/]";
51
+ else
52
+ re += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
53
+ }
54
+ re += "$";
55
+ return new RegExp(re);
56
+ }
57
+ function walk(dir, segs, out) {
58
+ if (segs.length === 0) {
59
+ if (isFile(dir))
60
+ out.add(dir);
61
+ return;
62
+ }
63
+ const [seg, ...rest] = segs;
64
+ if (seg === "**") {
65
+ // `**` matches zero path segments...
66
+ walk(dir, rest, out);
67
+ // ...or one-or-more: recurse into each subdirectory, keeping `**`.
68
+ for (const entry of readDir(dir)) {
69
+ if (entry.isDirectory())
70
+ walk(path.join(dir, entry.name), segs, out);
71
+ }
72
+ return;
73
+ }
74
+ const re = segmentToRegex(seg);
75
+ for (const entry of readDir(dir)) {
76
+ if (!re.test(entry.name))
77
+ continue;
78
+ const full = path.join(dir, entry.name);
79
+ if (rest.length === 0) {
80
+ if (entry.isFile())
81
+ out.add(full);
82
+ }
83
+ else if (entry.isDirectory()) {
84
+ walk(full, rest, out);
85
+ }
86
+ }
87
+ }
88
+ function readDir(dir) {
89
+ try {
90
+ return fs.readdirSync(dir, { withFileTypes: true });
91
+ }
92
+ catch {
93
+ return [];
94
+ }
95
+ }
96
+ function isFile(p) {
97
+ try {
98
+ return fs.statSync(p).isFile();
99
+ }
100
+ catch {
101
+ return false;
102
+ }
103
+ }
104
+ //# sourceMappingURL=glob.js.map