headlamp 0.1.8 → 0.1.10
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/README.md +49 -0
- package/dist/cli.cjs +109 -12
- package/dist/cli.cjs.map +3 -3
- package/dist/index.js +109 -12
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,11 +52,60 @@ npx headlamp --coverage
|
|
|
52
52
|
npx headlamp --coverage src/services/user.ts src/components/UserCard.tsx
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
## Output flags
|
|
56
|
+
|
|
57
|
+
- `--onlyFailures[=true|false]`:
|
|
58
|
+
- When enabled, the CLI prints only failing tests during execution across all views, while still printing the final test summary (files/tests/time) at the end.
|
|
59
|
+
- Supported forms: `--onlyFailures`, `--onlyFailures=true`, `--onlyFailures=false`.
|
|
60
|
+
- Works with other selection flags (e.g., `-t`, `--changed`).
|
|
61
|
+
|
|
62
|
+
Examples:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# Show only failures during the run, but still print the final summary
|
|
66
|
+
npx headlamp --onlyFailures
|
|
67
|
+
|
|
68
|
+
# Combine with changed-file selection
|
|
69
|
+
npx headlamp --changed --onlyFailures
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Changed-file selection
|
|
73
|
+
|
|
74
|
+
- `--changed[=mode]` selects tests by files changed in your working tree or branch.
|
|
75
|
+
- Modes:
|
|
76
|
+
- `all` (default when `--changed` is passed without a value): includes staged + unstaged + untracked files.
|
|
77
|
+
- `staged`: only staged changes.
|
|
78
|
+
- `unstaged`: only unstaged + untracked files.
|
|
79
|
+
- `branch`: union of
|
|
80
|
+
- files changed on the current branch relative to the default branch (via merge-base), and
|
|
81
|
+
- your current uncommitted changes (staged, unstaged tracked, and untracked files).
|
|
82
|
+
- Default branch is resolved via `origin/HEAD` when available, falling back to `origin/main` or `origin/master`.
|
|
83
|
+
- Effects:
|
|
84
|
+
- Uses changed production files as seeds to discover related tests by import-graph.
|
|
85
|
+
- Coverage tables prioritize and annotate files related to selection/changed files.
|
|
86
|
+
|
|
87
|
+
Examples:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# Staged changes only
|
|
91
|
+
npx headlamp --changed=staged
|
|
92
|
+
|
|
93
|
+
# All working tree changes
|
|
94
|
+
npx headlamp --changed
|
|
95
|
+
|
|
96
|
+
# Diff current branch against default branch (merge-base)
|
|
97
|
+
npx headlamp --changed=branch
|
|
98
|
+
|
|
99
|
+
# Combine with coverage
|
|
100
|
+
npx headlamp --coverage --changed=branch
|
|
101
|
+
```
|
|
102
|
+
|
|
55
103
|
## Coverage flags
|
|
56
104
|
|
|
57
105
|
- `--coverage`: enables coverage collection and prints merged coverage output after test execution. Uses your project's Jest/Vitest setup and reads coverage JSON from Jest.
|
|
58
106
|
- Prints a compact per-file table with hotspots and optionally detailed per-file breakdowns.
|
|
59
107
|
- Honors file selection and include/exclude globs when rendering coverage tables.
|
|
108
|
+
- When `--changed` is specified, coverage views factor in those changed files as selection seeds, influencing relevancy ordering and the “changed-related” highlighting.
|
|
60
109
|
- `--coverage.abortOnFailure`: if tests fail, exit immediately with the test exit code and skip coverage printing. Useful in CI when failures should short-circuit.
|
|
61
110
|
- `--coverage.ui=jest|both`:
|
|
62
111
|
- `jest`: write Istanbul text report to `coverage/merged/coverage.txt` only.
|
package/dist/cli.cjs
CHANGED
|
@@ -248,6 +248,7 @@ var init_args = __esm({
|
|
|
248
248
|
coverage: (coverageValue) => ({ type: "coverage", coverageValue }),
|
|
249
249
|
coverageUi: (value) => ({ type: "coverageUi", value }),
|
|
250
250
|
coverageAbortOnFailure: (value) => ({ type: "coverageAbortOnFailure", value }),
|
|
251
|
+
onlyFailures: (value) => ({ type: "onlyFailures", value }),
|
|
251
252
|
jestArg: (value) => ({ type: "jestArg", value }),
|
|
252
253
|
jestArgs: (values) => ({ type: "jestArgs", values }),
|
|
253
254
|
vitestArg: (value) => ({ type: "vitestArg", value }),
|
|
@@ -401,6 +402,18 @@ var init_args = __esm({
|
|
|
401
402
|
"--coverage.pageFit",
|
|
402
403
|
(_flag, lookahead) => step([ActionBuilders.coveragePageFit(isTruthy(String(lookahead)))], true)
|
|
403
404
|
),
|
|
405
|
+
// --onlyFailures flag (boolean)
|
|
406
|
+
rule.eq("--onlyFailures", () => step([ActionBuilders.onlyFailures(true)])),
|
|
407
|
+
rule.startsWith(
|
|
408
|
+
"--onlyFailures=",
|
|
409
|
+
(value) => step([
|
|
410
|
+
ActionBuilders.onlyFailures(isTruthy((value.split("=")[1] ?? "").trim().toLowerCase()))
|
|
411
|
+
])
|
|
412
|
+
),
|
|
413
|
+
rule.withLookahead(
|
|
414
|
+
"--onlyFailures",
|
|
415
|
+
(_flag, lookahead) => step([ActionBuilders.onlyFailures(isTruthy(String(lookahead)))], true)
|
|
416
|
+
),
|
|
404
417
|
rule.withLookahead(
|
|
405
418
|
"--testPathPattern",
|
|
406
419
|
(flag, lookahead) => step([ActionBuilders.jestArgs([flag, lookahead])], true)
|
|
@@ -465,12 +478,12 @@ var init_args = __esm({
|
|
|
465
478
|
rule.eq("--changed", () => step([ActionBuilders.changed("all")])),
|
|
466
479
|
rule.startsWith("--changed=", (value) => {
|
|
467
480
|
const raw = (value.split("=")[1] ?? "").trim().toLowerCase();
|
|
468
|
-
const mode = raw === "staged" ? "staged" : raw === "unstaged" ? "unstaged" : "all";
|
|
481
|
+
const mode = raw === "staged" ? "staged" : raw === "unstaged" ? "unstaged" : raw === "branch" ? "branch" : "all";
|
|
469
482
|
return step([ActionBuilders.changed(mode)]);
|
|
470
483
|
}),
|
|
471
484
|
rule.withLookahead("--changed", (_flag, lookahead) => {
|
|
472
485
|
const raw = String(lookahead).trim().toLowerCase();
|
|
473
|
-
const mode = raw === "staged" ? "staged" : raw === "unstaged" ? "unstaged" : "all";
|
|
486
|
+
const mode = raw === "staged" ? "staged" : raw === "unstaged" ? "unstaged" : raw === "branch" ? "branch" : "all";
|
|
474
487
|
return step([ActionBuilders.changed(mode)], true);
|
|
475
488
|
}),
|
|
476
489
|
rule.withLookahead(
|
|
@@ -547,6 +560,8 @@ var init_args = __esm({
|
|
|
547
560
|
return { vitest: [], jest: [], coverage: false, coverageUi: action.value };
|
|
548
561
|
case "coverageAbortOnFailure":
|
|
549
562
|
return { vitest: [], jest: [], coverage: false, coverageAbortOnFailure: action.value };
|
|
563
|
+
case "onlyFailures":
|
|
564
|
+
return { vitest: [], jest: [], coverage: false, onlyFailures: action.value };
|
|
550
565
|
case "jestArgs":
|
|
551
566
|
return { vitest: [], jest: action.values, coverage: false };
|
|
552
567
|
case "selectionHint":
|
|
@@ -618,6 +633,7 @@ var init_args = __esm({
|
|
|
618
633
|
...next,
|
|
619
634
|
...right.changed !== void 0 || left.changed !== void 0 ? { changed: right.changed ?? left.changed } : {},
|
|
620
635
|
...right.coverageAbortOnFailure !== void 0 || left.coverageAbortOnFailure !== void 0 ? { coverageAbortOnFailure: right.coverageAbortOnFailure ?? left.coverageAbortOnFailure } : {},
|
|
636
|
+
...right.onlyFailures !== void 0 || left.onlyFailures !== void 0 ? { onlyFailures: right.onlyFailures ?? left.onlyFailures } : {},
|
|
621
637
|
...right.coverageDetail !== void 0 || left.coverageDetail !== void 0 ? { coverageDetail: right.coverageDetail ?? left.coverageDetail } : {},
|
|
622
638
|
...right.coverageShowCode !== void 0 || left.coverageShowCode !== void 0 ? { coverageShowCode: right.coverageShowCode ?? left.coverageShowCode } : {},
|
|
623
639
|
...right.coverageMode !== void 0 || left.coverageMode !== void 0 ? { coverageMode: right.coverageMode ?? left.coverageMode } : {},
|
|
@@ -641,6 +657,7 @@ var init_args = __esm({
|
|
|
641
657
|
let collectCoverage = false;
|
|
642
658
|
let coverageUi = "both";
|
|
643
659
|
let coverageAbortOnFailure = false;
|
|
660
|
+
let onlyFailures = false;
|
|
644
661
|
let coverageShowCode = Boolean(process.stdout.isTTY);
|
|
645
662
|
let coverageMode = "auto";
|
|
646
663
|
const coverageMaxFilesLocalInit = void 0;
|
|
@@ -658,6 +675,7 @@ var init_args = __esm({
|
|
|
658
675
|
collectCoverage ||= contrib.coverage;
|
|
659
676
|
coverageUi = contrib.coverageUi ?? coverageUi;
|
|
660
677
|
coverageAbortOnFailure = contrib.coverageAbortOnFailure ?? coverageAbortOnFailure;
|
|
678
|
+
onlyFailures = contrib.onlyFailures ?? onlyFailures;
|
|
661
679
|
coverageShowCode = contrib.coverageShowCode ?? coverageShowCode;
|
|
662
680
|
const coverageDetailComputed = contrib.coverageDetail ?? (contrib.selection ? "auto" : void 0);
|
|
663
681
|
coverageMode = contrib.coverageMode ?? (contrib.selection ? "compact" : "auto");
|
|
@@ -691,6 +709,7 @@ var init_args = __esm({
|
|
|
691
709
|
collectCoverage,
|
|
692
710
|
coverageUi,
|
|
693
711
|
coverageAbortOnFailure,
|
|
712
|
+
onlyFailures,
|
|
694
713
|
selectionSpecified: Boolean(contrib.selection),
|
|
695
714
|
selectionPaths: [...contrib.selectionPaths ?? []],
|
|
696
715
|
includeGlobs,
|
|
@@ -4694,6 +4713,7 @@ var formatJestOutputVitest = (raw, opts) => {
|
|
|
4694
4713
|
const projectHint = new RegExp(
|
|
4695
4714
|
`(${cwd.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")})|(/gigworx-node/)`
|
|
4696
4715
|
);
|
|
4716
|
+
const onlyFailures = Boolean(opts?.onlyFailures);
|
|
4697
4717
|
const lines = raw.split(/\r?\n/);
|
|
4698
4718
|
const out = [];
|
|
4699
4719
|
const seenFailures = /* @__PURE__ */ new Set();
|
|
@@ -4781,8 +4801,10 @@ var formatJestOutputVitest = (raw, opts) => {
|
|
|
4781
4801
|
continue;
|
|
4782
4802
|
}
|
|
4783
4803
|
seenFiles.add(rel);
|
|
4784
|
-
|
|
4785
|
-
|
|
4804
|
+
if (!(onlyFailures && badge === "PASS")) {
|
|
4805
|
+
const pill = badge === "PASS" ? colorTokens.passPill("PASS") : colorTokens.failPill("FAIL");
|
|
4806
|
+
out.push(`${pill} ${ansi.white(rel)}`);
|
|
4807
|
+
}
|
|
4786
4808
|
lineIndex += 1;
|
|
4787
4809
|
continue;
|
|
4788
4810
|
}
|
|
@@ -4895,14 +4917,21 @@ function renderVitestFromJestJSON(data, opts) {
|
|
|
4895
4917
|
`(${cwd.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")})|(/gigworx-node/)`
|
|
4896
4918
|
);
|
|
4897
4919
|
const ctx = { projectHint, editorCmd: opts?.editorCmd, showStacks: true };
|
|
4920
|
+
const onlyFailures = Boolean(opts?.onlyFailures);
|
|
4898
4921
|
const out = [];
|
|
4899
|
-
|
|
4900
|
-
|
|
4922
|
+
if (!onlyFailures) {
|
|
4923
|
+
out.push(renderRunLine(cwd));
|
|
4924
|
+
out.push("");
|
|
4925
|
+
}
|
|
4901
4926
|
for (const file of data.testResults) {
|
|
4902
4927
|
const rel = file.testFilePath.replace(/\\/g, "/").replace(`${cwd}/`, "");
|
|
4903
4928
|
const failed = file.testResults.filter((assertion) => assertion.status === "failed");
|
|
4904
|
-
|
|
4905
|
-
|
|
4929
|
+
if (!onlyFailures) {
|
|
4930
|
+
out.push(...buildPerFileOverview(rel, file.testResults));
|
|
4931
|
+
}
|
|
4932
|
+
if (!(onlyFailures && failed.length === 0)) {
|
|
4933
|
+
out.push(buildFileBadgeLine(rel, failed.length));
|
|
4934
|
+
}
|
|
4906
4935
|
if (file.failureMessage && failed.length === 0) {
|
|
4907
4936
|
const lines = file.failureMessage.split(/\r?\n/);
|
|
4908
4937
|
const details = linesFromDetails(file.failureDetails);
|
|
@@ -5290,6 +5319,7 @@ var program = async () => {
|
|
|
5290
5319
|
collectCoverage,
|
|
5291
5320
|
coverageUi,
|
|
5292
5321
|
coverageAbortOnFailure,
|
|
5322
|
+
onlyFailures,
|
|
5293
5323
|
selectionSpecified,
|
|
5294
5324
|
selectionPaths,
|
|
5295
5325
|
includeGlobs,
|
|
@@ -5317,6 +5347,52 @@ var program = async () => {
|
|
|
5317
5347
|
return [];
|
|
5318
5348
|
}
|
|
5319
5349
|
};
|
|
5350
|
+
if (mode === "branch") {
|
|
5351
|
+
const resolveDefaultBranch = async () => {
|
|
5352
|
+
const candidates = [];
|
|
5353
|
+
try {
|
|
5354
|
+
const sym = await collect("git", ["symbolic-ref", "refs/remotes/origin/HEAD"]);
|
|
5355
|
+
const headRef = sym.find((ln) => ln.includes("refs/remotes/origin/"));
|
|
5356
|
+
if (headRef) {
|
|
5357
|
+
const m = /refs\/remotes\/(.+)/.exec(headRef);
|
|
5358
|
+
if (m && m[1]) {
|
|
5359
|
+
candidates.push(m[1]);
|
|
5360
|
+
}
|
|
5361
|
+
}
|
|
5362
|
+
} catch {
|
|
5363
|
+
}
|
|
5364
|
+
candidates.push("origin/main", "origin/master");
|
|
5365
|
+
for (const cand of candidates) {
|
|
5366
|
+
const exists = await collect("git", ["rev-parse", "--verify", cand]);
|
|
5367
|
+
if (exists.length > 0) {
|
|
5368
|
+
return cand;
|
|
5369
|
+
}
|
|
5370
|
+
}
|
|
5371
|
+
return void 0;
|
|
5372
|
+
};
|
|
5373
|
+
const defaultBranch = await resolveDefaultBranch();
|
|
5374
|
+
const mergeBase = defaultBranch ? (await collect("git", ["merge-base", "HEAD", defaultBranch]))[0] : void 0;
|
|
5375
|
+
const diffBase = mergeBase ?? "HEAD^";
|
|
5376
|
+
const branchDiff = await collect("git", [
|
|
5377
|
+
"diff",
|
|
5378
|
+
"--name-only",
|
|
5379
|
+
"--diff-filter=ACMRTUXB",
|
|
5380
|
+
diffBase,
|
|
5381
|
+
"HEAD"
|
|
5382
|
+
]);
|
|
5383
|
+
const stagedNow = await collect("git", [
|
|
5384
|
+
"diff",
|
|
5385
|
+
"--name-only",
|
|
5386
|
+
"--diff-filter=ACMRTUXB",
|
|
5387
|
+
"--cached"
|
|
5388
|
+
]);
|
|
5389
|
+
const unstagedNow = await collect("git", ["diff", "--name-only", "--diff-filter=ACMRTUXB"]);
|
|
5390
|
+
const untrackedNow = await collect("git", ["ls-files", "--others", "--exclude-standard"]);
|
|
5391
|
+
const rels2 = Array.from(
|
|
5392
|
+
/* @__PURE__ */ new Set([...branchDiff, ...stagedNow, ...unstagedNow, ...untrackedNow])
|
|
5393
|
+
);
|
|
5394
|
+
return rels2.map((rel) => path10.resolve(cwd, rel).replace(/\\/g, "/")).filter((abs) => !abs.includes("/node_modules/") && !abs.includes("/coverage/"));
|
|
5395
|
+
}
|
|
5320
5396
|
const staged = mode === "staged" || mode === "all" ? await collect("git", ["diff", "--name-only", "--diff-filter=ACMRTUXB", "--cached"]) : [];
|
|
5321
5397
|
const unstagedTracked = mode === "unstaged" || mode === "all" ? await collect("git", ["diff", "--name-only", "--diff-filter=ACMRTUXB"]) : [];
|
|
5322
5398
|
const untracked = mode === "unstaged" || mode === "all" ? await collect("git", ["ls-files", "--others", "--exclude-standard"]) : [];
|
|
@@ -5628,6 +5704,23 @@ var program = async () => {
|
|
|
5628
5704
|
}
|
|
5629
5705
|
if (effectiveJestFiles.length === 0) {
|
|
5630
5706
|
const repoRoot = repoRootForRefinement;
|
|
5707
|
+
if (jestFiles.length === 0) {
|
|
5708
|
+
try {
|
|
5709
|
+
const allAcross = [];
|
|
5710
|
+
for (const cfg of projectConfigs) {
|
|
5711
|
+
const cfgCwd = path10.dirname(cfg);
|
|
5712
|
+
const listed = await discoverJestResilient([...jestDiscoveryArgs, "--config", cfg], {
|
|
5713
|
+
cwd: cfgCwd
|
|
5714
|
+
});
|
|
5715
|
+
allAcross.push(...listed);
|
|
5716
|
+
}
|
|
5717
|
+
const uniqAll = Array.from(new Set(allAcross.map((p) => p.replace(/\\/g, "/"))));
|
|
5718
|
+
if (uniqAll.length > 0) {
|
|
5719
|
+
jestFiles = uniqAll;
|
|
5720
|
+
}
|
|
5721
|
+
} catch {
|
|
5722
|
+
}
|
|
5723
|
+
}
|
|
5631
5724
|
const seeds = prodSelections.map(
|
|
5632
5725
|
(abs) => path10.relative(repoRoot, abs).replace(/\\/g, "/").replace(/\.(m?[tj]sx?)$/i, "")
|
|
5633
5726
|
).flatMap((rel) => {
|
|
@@ -5906,12 +5999,14 @@ var program = async () => {
|
|
|
5906
5999
|
};
|
|
5907
6000
|
pretty = renderVitestFromJestJSON(reordered, {
|
|
5908
6001
|
cwd: repoRootForDiscovery,
|
|
5909
|
-
...editorCmd !== void 0 ? { editorCmd } : {}
|
|
6002
|
+
...editorCmd !== void 0 ? { editorCmd } : {},
|
|
6003
|
+
onlyFailures
|
|
5910
6004
|
});
|
|
5911
6005
|
} catch {
|
|
5912
6006
|
pretty = renderVitestFromJestJSON(bridge, {
|
|
5913
6007
|
cwd: repoRootForDiscovery,
|
|
5914
|
-
...editorCmd !== void 0 ? { editorCmd } : {}
|
|
6008
|
+
...editorCmd !== void 0 ? { editorCmd } : {},
|
|
6009
|
+
onlyFailures
|
|
5915
6010
|
});
|
|
5916
6011
|
}
|
|
5917
6012
|
if (debug) {
|
|
@@ -5928,7 +6023,8 @@ ${preview}${pretty.includes("\n") ? "\n\u2026" : ""}`);
|
|
|
5928
6023
|
}
|
|
5929
6024
|
const renderOpts = {
|
|
5930
6025
|
cwd: repoRootForDiscovery,
|
|
5931
|
-
...editorCmd !== void 0 ? { editorCmd } : {}
|
|
6026
|
+
...editorCmd !== void 0 ? { editorCmd } : {},
|
|
6027
|
+
onlyFailures
|
|
5932
6028
|
};
|
|
5933
6029
|
pretty = formatJestOutputVitest(output, renderOpts);
|
|
5934
6030
|
if (debug) {
|
|
@@ -5994,7 +6090,8 @@ ${preview}${pretty.includes("\n") ? "\n\u2026" : ""}`);
|
|
|
5994
6090
|
}
|
|
5995
6091
|
const text = renderVitestFromJestJSON(unified, {
|
|
5996
6092
|
cwd: repoRootForDiscovery,
|
|
5997
|
-
...editorCmd !== void 0 ? { editorCmd } : {}
|
|
6093
|
+
...editorCmd !== void 0 ? { editorCmd } : {},
|
|
6094
|
+
onlyFailures
|
|
5998
6095
|
});
|
|
5999
6096
|
if (text.trim().length > 0) {
|
|
6000
6097
|
process.stdout.write(text.endsWith("\n") ? text : `${text}
|