headlamp 0.1.9 → 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 CHANGED
@@ -69,11 +69,43 @@ npx headlamp --onlyFailures
69
69
  npx headlamp --changed --onlyFailures
70
70
  ```
71
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
+
72
103
  ## Coverage flags
73
104
 
74
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.
75
106
  - Prints a compact per-file table with hotspots and optionally detailed per-file breakdowns.
76
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.
77
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.
78
110
  - `--coverage.ui=jest|both`:
79
111
  - `jest`: write Istanbul text report to `coverage/merged/coverage.txt` only.
package/dist/cli.cjs CHANGED
@@ -478,12 +478,12 @@ var init_args = __esm({
478
478
  rule.eq("--changed", () => step([ActionBuilders.changed("all")])),
479
479
  rule.startsWith("--changed=", (value) => {
480
480
  const raw = (value.split("=")[1] ?? "").trim().toLowerCase();
481
- const mode = raw === "staged" ? "staged" : raw === "unstaged" ? "unstaged" : "all";
481
+ const mode = raw === "staged" ? "staged" : raw === "unstaged" ? "unstaged" : raw === "branch" ? "branch" : "all";
482
482
  return step([ActionBuilders.changed(mode)]);
483
483
  }),
484
484
  rule.withLookahead("--changed", (_flag, lookahead) => {
485
485
  const raw = String(lookahead).trim().toLowerCase();
486
- const mode = raw === "staged" ? "staged" : raw === "unstaged" ? "unstaged" : "all";
486
+ const mode = raw === "staged" ? "staged" : raw === "unstaged" ? "unstaged" : raw === "branch" ? "branch" : "all";
487
487
  return step([ActionBuilders.changed(mode)], true);
488
488
  }),
489
489
  rule.withLookahead(
@@ -5347,6 +5347,52 @@ var program = async () => {
5347
5347
  return [];
5348
5348
  }
5349
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
+ }
5350
5396
  const staged = mode === "staged" || mode === "all" ? await collect("git", ["diff", "--name-only", "--diff-filter=ACMRTUXB", "--cached"]) : [];
5351
5397
  const unstagedTracked = mode === "unstaged" || mode === "all" ? await collect("git", ["diff", "--name-only", "--diff-filter=ACMRTUXB"]) : [];
5352
5398
  const untracked = mode === "unstaged" || mode === "all" ? await collect("git", ["ls-files", "--others", "--exclude-standard"]) : [];
@@ -5658,6 +5704,23 @@ var program = async () => {
5658
5704
  }
5659
5705
  if (effectiveJestFiles.length === 0) {
5660
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
+ }
5661
5724
  const seeds = prodSelections.map(
5662
5725
  (abs) => path10.relative(repoRoot, abs).replace(/\\/g, "/").replace(/\.(m?[tj]sx?)$/i, "")
5663
5726
  ).flatMap((rel) => {