headlamp 0.1.13 → 0.1.15

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.cjs CHANGED
@@ -265,7 +265,8 @@ var init_args = __esm({
265
265
  coverageMaxFiles: (value) => ({ type: "coverageMaxFiles", value }),
266
266
  coverageMaxHotspots: (value) => ({ type: "coverageMaxHotspots", value }),
267
267
  coveragePageFit: (value) => ({ type: "coveragePageFit", value }),
268
- changed: (value) => ({ type: "changed", value })
268
+ changed: (value) => ({ type: "changed", value }),
269
+ changedDepth: (value) => ({ type: "changedDepth", value })
269
270
  };
270
271
  Some = (value) => ({ _tag: "some", value });
271
272
  None = { _tag: "none" };
@@ -486,6 +487,16 @@ var init_args = __esm({
486
487
  const mode = raw === "staged" ? "staged" : raw === "unstaged" ? "unstaged" : raw === "branch" ? "branch" : "all";
487
488
  return step([ActionBuilders.changed(mode)], true);
488
489
  }),
490
+ // --changed.depth flag: maximum transitive import depth for changed selection refinement
491
+ rule.startsWith("--changed.depth=", (value) => {
492
+ const raw = (value.split("=")[1] ?? "").trim();
493
+ const num = Number(raw);
494
+ return step(Number.isFinite(num) && num > 0 ? [ActionBuilders.changedDepth(num)] : []);
495
+ }),
496
+ rule.withLookahead("--changed.depth", (_flag, lookahead) => {
497
+ const num = Number(String(lookahead).trim());
498
+ return step(Number.isFinite(num) && num > 0 ? [ActionBuilders.changedDepth(num)] : [], true);
499
+ }),
489
500
  rule.withLookahead(
490
501
  "-t",
491
502
  (flag, lookahead) => step(
@@ -592,6 +603,8 @@ var init_args = __esm({
592
603
  return { vitest: [], jest: [], coverage: false, coveragePageFit: action.value };
593
604
  case "changed":
594
605
  return { vitest: [], jest: [], coverage: false, changed: action.value };
606
+ case "changedDepth":
607
+ return { vitest: [], jest: [], coverage: false, changedDepth: action.value };
595
608
  case "jestArg":
596
609
  return { vitest: [], jest: [action.value], coverage: false };
597
610
  case "vitestArg":
@@ -632,6 +645,7 @@ var init_args = __esm({
632
645
  return {
633
646
  ...next,
634
647
  ...right.changed !== void 0 || left.changed !== void 0 ? { changed: right.changed ?? left.changed } : {},
648
+ ...right.changedDepth !== void 0 || left.changedDepth !== void 0 ? { changedDepth: right.changedDepth ?? left.changedDepth } : {},
635
649
  ...right.coverageAbortOnFailure !== void 0 || left.coverageAbortOnFailure !== void 0 ? { coverageAbortOnFailure: right.coverageAbortOnFailure ?? left.coverageAbortOnFailure } : {},
636
650
  ...right.onlyFailures !== void 0 || left.onlyFailures !== void 0 ? { onlyFailures: right.onlyFailures ?? left.onlyFailures } : {},
637
651
  ...right.coverageDetail !== void 0 || left.coverageDetail !== void 0 ? { coverageDetail: right.coverageDetail ?? left.coverageDetail } : {},
@@ -722,7 +736,8 @@ var init_args = __esm({
722
736
  coveragePageFit,
723
737
  ...contrib.editorCmd !== void 0 ? { editorCmd: contrib.editorCmd } : {},
724
738
  ...contrib.workspaceRoot !== void 0 ? { workspaceRoot: contrib.workspaceRoot } : {},
725
- ...contrib.changed !== void 0 ? { changed: contrib.changed } : {}
739
+ ...contrib.changed !== void 0 ? { changed: contrib.changed } : {},
740
+ ...contrib.changedDepth !== void 0 ? { changedDepth: contrib.changedDepth } : {}
726
741
  };
727
742
  return out;
728
743
  };
@@ -3047,11 +3062,11 @@ var tintPct = (pct) => {
3047
3062
  var bar = (pct, width = DEFAULT_BAR_WIDTH) => {
3048
3063
  const filled = Math.round(pct / PERCENT_MAX * width);
3049
3064
  const solid = supportsUnicode() ? "\u2588" : "#";
3050
- const empty = supportsUnicode() ? "\u2591" : "-";
3065
+ const empty2 = supportsUnicode() ? "\u2591" : "-";
3051
3066
  const good = tintPct(pct);
3052
3067
  const MIN_REMAINING = 0;
3053
3068
  return `${good(solid.repeat(filled))}${ansi.gray(
3054
- empty.repeat(Math.max(MIN_REMAINING, width - filled))
3069
+ empty2.repeat(Math.max(MIN_REMAINING, width - filled))
3055
3070
  )}`;
3056
3071
  };
3057
3072
 
@@ -5387,11 +5402,25 @@ var extractBridgePath2 = (raw, cwd) => {
5387
5402
  const jsonPath = (matches[matches.length - 1][1] ?? "").trim().replace(/^['"`]|['"`]$/g, "");
5388
5403
  return path10.isAbsolute(jsonPath) ? jsonPath : path10.resolve(cwd, jsonPath).replace(/\\/g, "/");
5389
5404
  };
5390
- var isTransportError = (msg) => {
5391
- const lowercaseMessage = (msg ?? "").toLowerCase();
5392
- return /\bsocket hang up\b|\beconnreset\b|\betimedout\b|\beconnrefused\b|\bwrite epipe\b/.test(
5393
- lowercaseMessage
5394
- );
5405
+
5406
+ // src/lib/formatter/bridge/utils.ts
5407
+ var import_json52 = __toESM(require_lib(), 1);
5408
+
5409
+ // src/lib/formatter/bridge/http.ts
5410
+ var envNumber = (name, fallback) => {
5411
+ const parsed = Number(process.env[name]);
5412
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
5413
+ };
5414
+ var HEADLAMP_HTTP_WINDOW_MS = () => envNumber("HEADLAMP_HTTP_WINDOW_MS", 3e3);
5415
+ var HEADLAMP_HTTP_STRICT_WINDOW_MS = () => envNumber("HEADLAMP_HTTP_STRICT_WINDOW_MS", 600);
5416
+ var HEADLAMP_HTTP_MIN_SCORE = () => envNumber("HEADLAMP_HTTP_MIN_SCORE", 1200);
5417
+ var HEADLAMP_HTTP_DIFF_LIMIT = () => envNumber("HEADLAMP_HTTP_DIFF_LIMIT", 6);
5418
+ var HEADLAMP_HTTP_SHOW_MISS = () => process.env.HEADLAMP_HTTP_MISS === "1";
5419
+ var asHttpList = (candidateValue) => Array.isArray(candidateValue) ? candidateValue : [];
5420
+ var summarizeUrl = (method, url, route) => {
5421
+ const base = route || url || "";
5422
+ const qs = url && url.includes("?") ? ` ? ${url.split("?")[1]}` : "";
5423
+ return [method || "", base, qs].filter(Boolean).join(" ").trim();
5395
5424
  };
5396
5425
  var parseMethodPathFromTitle = (title) => {
5397
5426
  if (!title) {
@@ -5400,6 +5429,42 @@ var parseMethodPathFromTitle = (title) => {
5400
5429
  const matchResult = title.match(/\b(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s+([^\s)]+)/i);
5401
5430
  return matchResult ? { method: matchResult[1]?.toUpperCase(), path: matchResult[2] } : {};
5402
5431
  };
5432
+ var eventsNear = (http, ts, testPath, windowMs = HEADLAMP_HTTP_WINDOW_MS()) => {
5433
+ if (typeof ts !== "number" || !Number.isFinite(ts)) {
5434
+ return [];
5435
+ }
5436
+ return http.filter((event) => {
5437
+ const timeOk = typeof event.timestampMs === "number" && Math.abs(event.timestampMs - ts) <= windowMs;
5438
+ const pathOk = !testPath || event.testPath === testPath;
5439
+ return timeOk && pathOk;
5440
+ });
5441
+ };
5442
+ var isHttpStatusNumber = (statusNumber) => typeof statusNumber === "number" && statusNumber >= 100 && statusNumber <= 599;
5443
+ var inferHttpNumbersFromText = (lines) => {
5444
+ const text = lines.join("\n");
5445
+ const match = text.match(/Expected:\s*(\d{3})[\s\S]*?Received:\s*(\d{3})/i);
5446
+ if (match) {
5447
+ return { expectedNumber: Number(match[1]), receivedNumber: Number(match[2]) };
5448
+ }
5449
+ return {};
5450
+ };
5451
+ var titleSuggestsHttp = (title) => {
5452
+ const { method, path: parsedPath } = parseMethodPathFromTitle(title);
5453
+ return Boolean(method || parsedPath && parsedPath.startsWith("/"));
5454
+ };
5455
+ var hasStatusSemantics = (assertionLike) => {
5456
+ if (!assertionLike) {
5457
+ return false;
5458
+ }
5459
+ if (isHttpStatusNumber(assertionLike.expectedNumber) || isHttpStatusNumber(assertionLike.receivedNumber)) {
5460
+ return true;
5461
+ }
5462
+ const combinedRaw = `${assertionLike.matcher ?? ""} ${assertionLike.message ?? ""}`;
5463
+ const combinedMessage = combinedRaw.toLowerCase();
5464
+ return /\bstatus(code)?\b|\btohaves(tatus|tatuscode)\b/.test(combinedMessage);
5465
+ };
5466
+ var fileSuggestsHttp = (relPath2) => /(?:^|\/)(routes?|api|controllers?|e2e|integration)(?:\/|\.test\.)/i.test(relPath2);
5467
+ var isHttpRelevant = (ctx) => ctx.hasTransportSignal || ctx.httpCountInSameTest > 0 || titleSuggestsHttp(ctx.title) || hasStatusSemantics(ctx.assertion) || fileSuggestsHttp(ctx.relPath);
5403
5468
  var routeSimilarityScore = (hint, evt) => {
5404
5469
  if (!hint.path && !hint.method) {
5405
5470
  return 0;
@@ -5420,15 +5485,12 @@ var routeSimilarityScore = (hint, evt) => {
5420
5485
  }
5421
5486
  return methodOk * 10;
5422
5487
  };
5423
- var envNumber = (name, fallback) => {
5424
- const parsed = Number(process.env[name]);
5425
- return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
5488
+ var isTransportError = (msg) => {
5489
+ const lowercaseMessage = (msg ?? "").toLowerCase();
5490
+ return /\bsocket hang up\b|\beconnreset\b|\betimedout\b|\beconnrefused\b|\bwrite epipe\b/.test(
5491
+ lowercaseMessage
5492
+ );
5426
5493
  };
5427
- var HEADLAMP_HTTP_WINDOW_MS = () => envNumber("HEADLAMP_HTTP_WINDOW_MS", 3e3);
5428
- var HEADLAMP_HTTP_STRICT_WINDOW_MS = () => envNumber("HEADLAMP_HTTP_STRICT_WINDOW_MS", 600);
5429
- var HEADLAMP_HTTP_MIN_SCORE = () => envNumber("HEADLAMP_HTTP_MIN_SCORE", 1200);
5430
- var HEADLAMP_HTTP_DIFF_LIMIT = () => envNumber("HEADLAMP_HTTP_DIFF_LIMIT", 6);
5431
- var HEADLAMP_HTTP_SHOW_MISS = () => process.env.HEADLAMP_HTTP_MISS === "1";
5432
5494
  var scoreHttpForAssertion = (assertion, titleHint) => (candidateEvent) => {
5433
5495
  const tsA = assertion.timestampMs;
5434
5496
  const tsH = candidateEvent.timestampMs;
@@ -5474,7 +5536,386 @@ var pickRelevantHttp = (assertion, http, ctx) => {
5474
5536
  };
5475
5537
 
5476
5538
  // src/lib/formatter/bridge/utils.ts
5477
- var import_json52 = __toESM(require_lib(), 1);
5539
+ var colorTokens2 = {
5540
+ pass: Colors.Success,
5541
+ fail: Colors.Failure,
5542
+ skip: Colors.Skip,
5543
+ todo: Colors.Todo,
5544
+ passPill: (text) => BackgroundColors.Success(ansi.white(` ${text} `)),
5545
+ failPill: (text) => BackgroundColors.Failure(ansi.white(` ${text} `))
5546
+ };
5547
+ var joinLines = (chunks) => chunks.join("\n");
5548
+ var empty = [];
5549
+ var concat = (...xs) => xs.flat();
5550
+ var by = (keySelector) => (left, right) => keySelector(left) - keySelector(right);
5551
+ var isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
5552
+ var stripBridgeEventsFromConsole = (maybeConsole) => {
5553
+ if (!Array.isArray(maybeConsole)) {
5554
+ return maybeConsole;
5555
+ }
5556
+ return maybeConsole.filter((entry) => {
5557
+ try {
5558
+ const raw = Array.isArray(entry.message) ? entry.message.map(String).join(" ") : String(entry.message ?? "");
5559
+ return !raw.includes("[JEST-BRIDGE-EVENT]");
5560
+ } catch {
5561
+ return true;
5562
+ }
5563
+ });
5564
+ };
5565
+ var parseBridgeConsole = (consoleEntries) => {
5566
+ const http = [];
5567
+ const assertions = [];
5568
+ if (!Array.isArray(consoleEntries)) {
5569
+ return { http, assertions };
5570
+ }
5571
+ for (const entry of consoleEntries) {
5572
+ const rec = entry;
5573
+ const rawMsgVal = rec && typeof rec.message !== "undefined" ? rec.message : "";
5574
+ const raw = Array.isArray(rawMsgVal) ? rawMsgVal.map(String).join(" ") : String(rawMsgVal ?? "");
5575
+ if (!raw.includes("[JEST-BRIDGE-EVENT]")) {
5576
+ continue;
5577
+ }
5578
+ const jsonText = raw.split("[JEST-BRIDGE-EVENT]").pop()?.trim() ?? "";
5579
+ try {
5580
+ const evt = import_json52.default.parse(jsonText);
5581
+ const type = evt?.type;
5582
+ if (type === "httpResponse") {
5583
+ const timestampMs = Number(evt.timestampMs ?? Date.now());
5584
+ http.push({
5585
+ kind: "response",
5586
+ timestampMs,
5587
+ method: evt.method,
5588
+ url: evt.url,
5589
+ route: evt.route,
5590
+ statusCode: evt.statusCode,
5591
+ durationMs: evt.durationMs,
5592
+ contentType: evt.contentType,
5593
+ requestId: evt.requestId,
5594
+ json: evt.json,
5595
+ bodyPreview: evt.bodyPreview,
5596
+ testPath: evt.testPath,
5597
+ currentTestName: evt.currentTestName
5598
+ });
5599
+ } else if (type === "httpAbort") {
5600
+ http.push({
5601
+ kind: "abort",
5602
+ timestampMs: Number(evt.timestampMs ?? Date.now()),
5603
+ method: evt.method,
5604
+ url: evt.url,
5605
+ route: evt.route,
5606
+ durationMs: evt.durationMs,
5607
+ testPath: evt.testPath,
5608
+ currentTestName: evt.currentTestName
5609
+ });
5610
+ } else if (type === "httpResponseBatch") {
5611
+ const list = asHttpList(evt?.events);
5612
+ for (const item of list) {
5613
+ const anyItem = item;
5614
+ http.push({
5615
+ timestampMs: Number(anyItem.timestampMs ?? Date.now()),
5616
+ method: anyItem.method,
5617
+ url: anyItem.url,
5618
+ route: anyItem.route,
5619
+ statusCode: anyItem.statusCode,
5620
+ durationMs: anyItem.durationMs,
5621
+ contentType: anyItem.contentType,
5622
+ requestId: anyItem.requestId,
5623
+ json: anyItem.json,
5624
+ bodyPreview: anyItem.bodyPreview,
5625
+ testPath: evt.testPath,
5626
+ currentTestName: evt.currentTestName
5627
+ });
5628
+ }
5629
+ } else if (type === "assertionFailure") {
5630
+ assertions.push({
5631
+ timestampMs: typeof evt.timestampMs === "number" ? evt.timestampMs : void 0,
5632
+ matcher: evt.matcher,
5633
+ expectedNumber: typeof evt.expectedNumber === "number" ? evt.expectedNumber : void 0,
5634
+ receivedNumber: typeof evt.receivedNumber === "number" ? evt.receivedNumber : void 0,
5635
+ message: typeof evt.message === "string" ? evt.message : void 0,
5636
+ stack: typeof evt.stack === "string" ? evt.stack : void 0,
5637
+ testPath: evt.testPath,
5638
+ currentTestName: evt.currentTestName,
5639
+ expectedPreview: typeof evt.expectedPreview === "string" ? evt.expectedPreview : void 0,
5640
+ actualPreview: typeof evt.actualPreview === "string" ? evt.actualPreview : void 0
5641
+ });
5642
+ }
5643
+ } catch {
5644
+ }
5645
+ }
5646
+ return { http, assertions };
5647
+ };
5648
+ var renderRunHeader = ({ ctx, onlyFailures }) => onlyFailures ? empty : [`${BackgroundColors.Run(ansi.white(" RUN "))} ${ansi.dim(ctx.cwd)}`, ""];
5649
+ var renderPerFileOverviewBlock = (rel, testResults, onlyFailures) => onlyFailures ? empty : buildPerFileOverview(rel, testResults);
5650
+ var renderFileBadge = (rel, failedCount, onlyFailures) => onlyFailures && failedCount === 0 ? empty : [buildFileBadgeLine(rel, failedCount)];
5651
+ var condenseBlankRuns = (lines) => {
5652
+ const out = [];
5653
+ let lastBlank = false;
5654
+ for (const ln of lines) {
5655
+ const isBlank = !stripAnsiSimple(String(ln ?? "")).trim();
5656
+ if (isBlank) {
5657
+ if (!lastBlank) {
5658
+ out.push("");
5659
+ }
5660
+ lastBlank = true;
5661
+ } else {
5662
+ out.push(String(ln));
5663
+ lastBlank = false;
5664
+ }
5665
+ }
5666
+ return out;
5667
+ };
5668
+ var mergeMsgLines = (primaryRaw, detailMsgs) => {
5669
+ const primary = primaryRaw.trim() ? primaryRaw.split(/\r?\n/) : [];
5670
+ const key = (line) => stripAnsiSimple(line).trim();
5671
+ const seen = new Set(primary.map(key));
5672
+ const merged = [...primary];
5673
+ for (const msg of detailMsgs) {
5674
+ const msgKey = key(String(msg ?? ""));
5675
+ if (!msgKey) {
5676
+ continue;
5677
+ }
5678
+ if (!seen.has(msgKey)) {
5679
+ merged.push(msg);
5680
+ seen.add(msgKey);
5681
+ }
5682
+ }
5683
+ return condenseBlankRuns(merged);
5684
+ };
5685
+ var renderFileLevelFailure = (file, ctx) => {
5686
+ if (!(file.failureMessage || file.testExecError)) {
5687
+ return empty;
5688
+ }
5689
+ const base = linesFromDetails(file.failureDetails);
5690
+ const exec = linesFromDetails(
5691
+ Array.isArray(file.testExecError) ? file.testExecError : [file.testExecError]
5692
+ );
5693
+ const combinedDetails = {
5694
+ stacks: [...base.stacks, ...exec.stacks],
5695
+ messages: [...base.messages, ...exec.messages]
5696
+ };
5697
+ const msgLines = mergeMsgLines(file.failureMessage || "", combinedDetails.messages);
5698
+ const mergedForStack = collapseStacks([...msgLines, ...combinedDetails.stacks]);
5699
+ const synthLoc = deepestProjectLoc(mergedForStack, ctx.projectHint);
5700
+ const stackPreview = ctx.showStacks ? mergedForStack.filter((ln) => isStackLine(stripAnsiSimple(ln))).filter((ln) => ctx.projectHint.test(stripAnsiSimple(ln))).slice(0, 2).map((ln) => ` ${colorStackLine(String(ln), ctx.projectHint)}`) : [];
5701
+ const code = buildCodeFrameSection(msgLines, ctx, synthLoc);
5702
+ const pretty = buildPrettyDiffSection(file.failureDetails, msgLines);
5703
+ const message = buildMessageSection(msgLines, combinedDetails, ctx, {
5704
+ suppressDiff: pretty.length > 0,
5705
+ stackPreview
5706
+ });
5707
+ const consoleBlock = buildConsoleSection(stripBridgeEventsFromConsole(file.console ?? null));
5708
+ const stackTail = ctx.showStacks && stackPreview.length === 0 ? (() => {
5709
+ const tail = mergedForStack.filter((ln) => isStackLine(stripAnsiSimple(ln))).slice(-4).map((ln) => ` ${colorStackLine(String(ln), ctx.projectHint)}`);
5710
+ return tail.length ? [ansi.dim(" Stack:"), ...tail, ""] : empty;
5711
+ })() : empty;
5712
+ return concat(code, pretty, message, consoleBlock, stackTail);
5713
+ };
5714
+ var renderHttpCard = (args) => {
5715
+ const { file, relPath: rel, assertion, assertionEvents, httpSorted } = args;
5716
+ const nameMatches = (left, right) => !!left && !!right && (left === right || left.includes(right) || right.includes(left));
5717
+ const inSameCtx = (testPath, testName) => httpSorted.filter(
5718
+ (event) => event.testPath === testPath && nameMatches(event.currentTestName, testName)
5719
+ );
5720
+ const perTestSlice = inSameCtx(file.testFilePath, assertion.fullName);
5721
+ const corresponding = assertionEvents.find(
5722
+ (event) => event.testPath === file.testFilePath && nameMatches(event.currentTestName, assertion.fullName)
5723
+ ) ?? assertion;
5724
+ const nearByTime = eventsNear(
5725
+ httpSorted,
5726
+ corresponding?.timestampMs,
5727
+ file.testFilePath
5728
+ );
5729
+ const hasAbort = perTestSlice.some((event) => event.kind === "abort");
5730
+ const hasTransport = isTransportError(corresponding?.message) || hasAbort;
5731
+ const httpLikely = isHttpRelevant({
5732
+ assertion: corresponding,
5733
+ title: assertion.fullName,
5734
+ relPath: rel,
5735
+ httpCountInSameTest: perTestSlice.length || nearByTime.length,
5736
+ hasTransportSignal: hasTransport
5737
+ });
5738
+ if (!httpLikely) {
5739
+ return empty;
5740
+ }
5741
+ const HEADLAMP_HTTP_DIFF_LIMIT_LOCAL = () => HEADLAMP_HTTP_DIFF_LIMIT();
5742
+ const safeParseJSON = (text) => {
5743
+ try {
5744
+ return text ? import_json52.default.parse(text) : void 0;
5745
+ } catch {
5746
+ return void 0;
5747
+ }
5748
+ };
5749
+ const expPreview = corresponding?.expectedPreview;
5750
+ const actPreview = corresponding?.actualPreview;
5751
+ const parsedExpected = safeParseJSON(expPreview);
5752
+ const parsedActual = safeParseJSON(actPreview);
5753
+ let corr = corresponding;
5754
+ if (!isHttpStatusNumber(corr.expectedNumber) && !isHttpStatusNumber(corr.receivedNumber)) {
5755
+ const inferred = inferHttpNumbersFromText(
5756
+ (assertion.failureMessages?.join("\n") || file.failureMessage || "").split("\n")
5757
+ );
5758
+ if (isHttpStatusNumber(inferred.expectedNumber) || isHttpStatusNumber(inferred.receivedNumber)) {
5759
+ corr = { ...corr, ...inferred };
5760
+ }
5761
+ }
5762
+ const relevant = pickRelevantHttp(
5763
+ {
5764
+ timestampMs: corr?.timestampMs,
5765
+ expectedNumber: corr?.expectedNumber,
5766
+ receivedNumber: corr?.receivedNumber,
5767
+ matcher: corr?.matcher,
5768
+ message: corr?.message,
5769
+ stack: corr?.stack,
5770
+ testPath: file.testFilePath,
5771
+ currentTestName: assertion.title
5772
+ },
5773
+ httpSorted,
5774
+ {
5775
+ testPath: file.testFilePath,
5776
+ currentTestName: assertion.fullName,
5777
+ title: assertion.fullName
5778
+ }
5779
+ );
5780
+ if (hasTransport) {
5781
+ const tsBase = corr?.timestampMs ?? 0;
5782
+ const [nearestAbort] = perTestSlice.filter((event) => event.kind === "abort").sort(
5783
+ (left, right) => Math.abs(tsBase - (left.timestampMs ?? 0)) - Math.abs(tsBase - (right.timestampMs ?? 0))
5784
+ );
5785
+ if (nearestAbort) {
5786
+ const ms = nearestAbort.durationMs;
5787
+ return [
5788
+ " HTTP:",
5789
+ `
5790
+ ${summarizeUrl(nearestAbort.method, nearestAbort.url, nearestAbort.route)} ${ansi.dim("->")} ${ansi.yellow("connection aborted")}`,
5791
+ ms != null ? ` ${ansi.dim(`(${ms}ms)`)} ` : "",
5792
+ "\n"
5793
+ ];
5794
+ }
5795
+ return HEADLAMP_HTTP_SHOW_MISS() ? [
5796
+ " HTTP:",
5797
+ `
5798
+ ${ansi.dim("Transport error; no matching HTTP exchange in window.")}`,
5799
+ "\n"
5800
+ ] : empty;
5801
+ }
5802
+ if (!relevant) {
5803
+ return HEADLAMP_HTTP_SHOW_MISS() ? [
5804
+ " HTTP:",
5805
+ `
5806
+ ${ansi.dim("No relevant HTTP exchange found. (HEADLAMP_HTTP_MISS=0 to hide)")}`,
5807
+ "\n"
5808
+ ] : empty;
5809
+ }
5810
+ const jsonDiff = (expected, actual, limit = HEADLAMP_HTTP_DIFF_LIMIT_LOCAL()) => {
5811
+ const out = [];
5812
+ const queue = [
5813
+ { pathSoFar: "$", expectedValue: expected, actualValue: actual }
5814
+ ];
5815
+ while (queue.length && out.length < limit) {
5816
+ const { pathSoFar, expectedValue, actualValue } = queue.shift();
5817
+ const expectedIsObject = expectedValue && typeof expectedValue === "object";
5818
+ const actualIsObject = actualValue && typeof actualValue === "object";
5819
+ if (!expectedIsObject && !actualIsObject) {
5820
+ if (JSON.stringify(expectedValue) !== JSON.stringify(actualValue)) {
5821
+ out.push({
5822
+ kind: "changed",
5823
+ path: pathSoFar,
5824
+ preview: `${String(expectedValue)} \u2192 ${String(actualValue)}`
5825
+ });
5826
+ }
5827
+ } else if (expectedIsObject && !actualIsObject) {
5828
+ out.push({ kind: "changed", path: pathSoFar, preview: "[object] \u2192 primitive" });
5829
+ } else if (!expectedIsObject && actualIsObject) {
5830
+ out.push({ kind: "changed", path: pathSoFar, preview: "primitive \u2192 [object]" });
5831
+ } else {
5832
+ const expectedKeys = new Set(Object.keys(expectedValue));
5833
+ const actualKeys = new Set(Object.keys(actualValue));
5834
+ for (const key of expectedKeys) {
5835
+ if (!actualKeys.has(key) && out.length < limit) {
5836
+ out.push({ kind: "removed", path: `${pathSoFar}.${key}` });
5837
+ }
5838
+ }
5839
+ for (const key of actualKeys) {
5840
+ if (!expectedKeys.has(key) && out.length < limit) {
5841
+ out.push({ kind: "added", path: `${pathSoFar}.${key}` });
5842
+ }
5843
+ }
5844
+ for (const key of expectedKeys) {
5845
+ if (actualKeys.has(key) && out.length < limit) {
5846
+ queue.push({
5847
+ pathSoFar: `${pathSoFar}.${key}`,
5848
+ expectedValue: expectedValue[key],
5849
+ actualValue: actualValue[key]
5850
+ });
5851
+ }
5852
+ }
5853
+ }
5854
+ }
5855
+ return out;
5856
+ };
5857
+ const importantMessages = (json) => {
5858
+ const msgs = [];
5859
+ try {
5860
+ const obj = isObject(json) ? json : {};
5861
+ const push = (msg) => {
5862
+ if (typeof msg === "string" && msg.trim()) {
5863
+ msgs.push(msg);
5864
+ }
5865
+ };
5866
+ push(obj.displayMessage);
5867
+ push(obj.message);
5868
+ if (Array.isArray(obj.errors)) {
5869
+ for (const event of obj.errors) {
5870
+ push(isObject(event) ? event.message : void 0);
5871
+ }
5872
+ }
5873
+ if (Array.isArray(obj.data)) {
5874
+ for (const event of obj.data) {
5875
+ push(isObject(event) ? event.message : void 0);
5876
+ }
5877
+ }
5878
+ } catch {
5879
+ }
5880
+ return msgs.slice(0, 2);
5881
+ };
5882
+ const where = summarizeUrl(relevant.method, relevant.url, relevant.route);
5883
+ const header = [
5884
+ " HTTP:",
5885
+ `
5886
+ ${where} ${ansi.dim("->")} ${relevant.statusCode ?? "?"}`,
5887
+ typeof relevant.durationMs === "number" ? ` ${ansi.dim(`(${relevant.durationMs}ms)`)} ` : " ",
5888
+ relevant.contentType ? ansi.dim(`(${relevant.contentType})`) : "",
5889
+ relevant.requestId ? ansi.dim(` reqId=${relevant.requestId}`) : ""
5890
+ ].join("");
5891
+ const expVsAct = typeof corr?.expectedNumber === "number" || typeof corr?.receivedNumber === "number" ? (() => {
5892
+ const exp = corr?.expectedNumber != null ? String(corr.expectedNumber) : "?";
5893
+ const got = corr?.receivedNumber != null ? String(corr.receivedNumber) : String(relevant.statusCode ?? "?");
5894
+ return `
5895
+ Expected: ${ansi.yellow(exp)} Received: ${ansi.yellow(got)}`;
5896
+ })() : "";
5897
+ const why = importantMessages(parsedActual ?? relevant.json).map((msg) => `
5898
+ Why: ${ansi.white(msg)}`).slice(0, 1).join("") || "";
5899
+ const diff = (() => {
5900
+ const rightActual = parsedActual ?? relevant.json;
5901
+ if (!parsedExpected || !rightActual) {
5902
+ return "";
5903
+ }
5904
+ const changes = jsonDiff(parsedExpected, rightActual);
5905
+ if (!changes.length) {
5906
+ return "";
5907
+ }
5908
+ const body = changes.map((change) => {
5909
+ const marker = change.kind === "added" ? "+" : change.kind === "removed" ? "-" : "~";
5910
+ const preview = change.preview ? `: ${ansi.dim(change.preview)}` : "";
5911
+ return `
5912
+ ${marker} ${change.path}${preview}`;
5913
+ }).join("");
5914
+ return `
5915
+ Diff:${body}`;
5916
+ })();
5917
+ return [header, expVsAct, why, diff, "\n"].filter(Boolean);
5918
+ };
5478
5919
  var coerceJestJsonToBridge = (raw) => {
5479
5920
  if (raw && typeof raw === "object" && "aggregated" in raw) {
5480
5921
  return raw;
@@ -5517,71 +5958,99 @@ var coerceJestJsonToBridge = (raw) => {
5517
5958
  }
5518
5959
  };
5519
5960
  };
5520
- var colorTokens2 = {
5521
- pass: Colors.Success,
5522
- fail: Colors.Failure,
5523
- skip: Colors.Skip,
5524
- todo: Colors.Todo,
5525
- passPill: (text) => BackgroundColors.Success(ansi.white(` ${text} `)),
5526
- failPill: (text) => BackgroundColors.Failure(ansi.white(` ${text} `))
5527
- };
5528
- var by = (keySelector) => (left, right) => keySelector(left) - keySelector(right);
5529
- var isObject = (candidateValue) => !!candidateValue && typeof candidateValue === "object";
5530
- var asHttpList = (candidateValue) => Array.isArray(candidateValue) ? candidateValue : [];
5531
- var summarizeUrl = (method, url, route) => {
5532
- const base = route || url || "";
5533
- const qs = url && url.includes("?") ? ` ? ${url.split("?")[1]}` : "";
5534
- return [method || "", base, qs].filter(Boolean).join(" ").trim();
5535
- };
5536
- var stripBridgeEventsFromConsole = (maybeConsole) => {
5537
- if (!Array.isArray(maybeConsole)) {
5538
- return maybeConsole;
5539
- }
5540
- return maybeConsole.filter((entry) => {
5961
+ var renderFailedAssertion = (args) => {
5962
+ const { file, relPath: rel, assertion, ctx, assertionEvents, httpSorted } = args;
5963
+ const header = `${rel} > ${assertion.fullName}`;
5964
+ const bullet = (text) => `${Colors.Failure("\xD7")} ${ansi.white(text)}`;
5965
+ const failureMessage = file.failureMessage || "";
5966
+ const detailMsgs = linesFromDetails(assertion.failureDetails || file.failureDetails).messages;
5967
+ const primaryBlock = assertion.failureMessages?.length ? assertion.failureMessages.join("\n") : failureMessage;
5968
+ const messagesArray = mergeMsgLines(primaryBlock, detailMsgs);
5969
+ const details = linesFromDetails(assertion.failureDetails || file.failureDetails);
5970
+ const mergedForStack = collapseStacks([...messagesArray, ...details.stacks]);
5971
+ const deepestLoc = deepestProjectLoc(mergedForStack, ctx.projectHint);
5972
+ const locLink = deepestLoc ? (() => {
5973
+ const href = preferredEditorHref(deepestLoc.file, deepestLoc.line, ctx.editorCmd);
5974
+ const base = `${deepestLoc.file.split("/").pop()}:${deepestLoc.line}`;
5975
+ return osc8(base, href);
5976
+ })() : void 0;
5977
+ const headerLine = `${ansi.white(header)}${locLink ? ` ${ansi.dim(`(${locLink})`)}` : ""}`;
5978
+ const msgLines = messagesArray.join("\n").split("\n");
5979
+ const assertFallback = deepestLoc || assertion.location && { file: file.testFilePath, line: assertion.location.line };
5980
+ const matcherMsg = (() => {
5541
5981
  try {
5542
- const raw = Array.isArray(entry.message) ? entry.message.map(String).join(" ") : String(entry.message ?? "");
5543
- return !raw.includes("[JEST-BRIDGE-EVENT]");
5982
+ const arr = assertion.failureDetails || file.failureDetails;
5983
+ if (!arr) {
5984
+ return empty;
5985
+ }
5986
+ for (const detailEntry of arr) {
5987
+ const obj = detailEntry && typeof detailEntry === "object" ? detailEntry : null;
5988
+ const mr = obj && obj.matcherResult && typeof obj.matcherResult === "object" ? obj.matcherResult : null;
5989
+ if (mr && typeof mr.message === "string" && mr.message.trim()) {
5990
+ const name = typeof mr.matcherName === "string" ? mr.matcherName : "";
5991
+ const matcherHeader = name ? ` ${ansi.bold("Matcher:")} ${ansi.yellow(name)}` : "";
5992
+ const bodyHeader = ` ${ansi.bold("Message:")}`;
5993
+ const body = String(mr.message).split(/\r?\n/).slice(0, 6).map((ln) => ` ${ansi.yellow(ln)}`);
5994
+ return [matcherHeader, bodyHeader, ...body, ""].filter(Boolean);
5995
+ }
5996
+ }
5544
5997
  } catch {
5545
- return true;
5546
5998
  }
5999
+ return empty;
6000
+ })();
6001
+ const code = concat(
6002
+ ["", drawFailLine(), bullet(headerLine), ""],
6003
+ buildCodeFrameSection(msgLines, ctx, assertFallback || void 0),
6004
+ [""]
6005
+ );
6006
+ const pretty = buildPrettyDiffSection(assertion.failureDetails || file.failureDetails, msgLines);
6007
+ const hasPretty = pretty.length > 0;
6008
+ const stackPreview = ctx.showStacks ? mergedForStack.filter((ln) => isStackLine(stripAnsiSimple(ln))).filter((ln) => ctx.projectHint.test(stripAnsiSimple(ln))).slice(0, 2).map((ln) => ` ${colorStackLine(String(ln), ctx.projectHint)}`) : [];
6009
+ const message = buildMessageSection(msgLines, details, ctx, {
6010
+ suppressDiff: hasPretty,
6011
+ stackPreview
5547
6012
  });
6013
+ const httpCard = renderHttpCard({ file, relPath: rel, assertion, assertionEvents, httpSorted });
6014
+ const minimalInfo = msgLines.every((ln) => !ln.trim());
6015
+ const thrown = minimalInfo ? (() => {
6016
+ try {
6017
+ return buildThrownSection(assertion.failureDetails || []);
6018
+ } catch {
6019
+ return empty;
6020
+ }
6021
+ })() : empty;
6022
+ const consoleBlock = buildConsoleSection(stripBridgeEventsFromConsole(file.console ?? null));
6023
+ const stackTail = ctx.showStacks && stackPreview.length === 0 ? (() => {
6024
+ const merged = collapseStacks([...msgLines, ...details.stacks]);
6025
+ const tail = collapseStacks(merged).filter((ln) => isStackLine(stripAnsiSimple(ln))).slice(-4).map((ln) => ` ${colorStackLine(String(ln), ctx.projectHint)}`);
6026
+ return tail.length ? [ansi.dim(" Stack:"), ...tail, ""] : empty;
6027
+ })() : empty;
6028
+ return concat(code, pretty, matcherMsg, message, httpCard, thrown, consoleBlock, stackTail, [
6029
+ drawFailLine(),
6030
+ ""
6031
+ ]);
5548
6032
  };
5549
- var eventsNear = (http, ts, testPath, windowMs = HEADLAMP_HTTP_WINDOW_MS()) => {
5550
- if (typeof ts !== "number" || !Number.isFinite(ts)) {
5551
- return [];
5552
- }
5553
- return http.filter((event) => {
5554
- const timeOk = typeof event.timestampMs === "number" && Math.abs(event.timestampMs - ts) <= windowMs;
5555
- const pathOk = !testPath || event.testPath === testPath;
5556
- return timeOk && pathOk;
5557
- });
5558
- };
5559
- var isHttpStatusNumber = (statusNumber) => typeof statusNumber === "number" && statusNumber >= 100 && statusNumber <= 599;
5560
- var inferHttpNumbersFromText = (lines) => {
5561
- const text = lines.join("\n");
5562
- const match = text.match(/Expected:\s*(\d{3})[\s\S]*?Received:\s*(\d{3})/i);
5563
- if (match) {
5564
- return { expectedNumber: Number(match[1]), receivedNumber: Number(match[2]) };
5565
- }
5566
- return {};
5567
- };
5568
- var titleSuggestsHttp = (title) => {
5569
- const { method, path: parsedPath } = parseMethodPathFromTitle(title);
5570
- return Boolean(method || parsedPath && parsedPath.startsWith("/"));
5571
- };
5572
- var hasStatusSemantics = (assertionLike) => {
5573
- if (!assertionLike) {
5574
- return false;
5575
- }
5576
- if (isHttpStatusNumber(assertionLike.expectedNumber) || isHttpStatusNumber(assertionLike.receivedNumber)) {
5577
- return true;
5578
- }
5579
- const combinedRaw = `${assertionLike.matcher ?? ""} ${assertionLike.message ?? ""}`;
5580
- const combinedMessage = combinedRaw.toLowerCase();
5581
- return /\bstatus(code)?\b|\btohaves(tatus|tatuscode)\b/.test(combinedMessage);
6033
+ var renderFileBlock = (file, env) => {
6034
+ const rel = file.testFilePath.replace(/\\/g, "/").replace(`${env.ctx.cwd}/`, "");
6035
+ const failed = file.testResults.filter((assertion) => assertion.status === "failed");
6036
+ const { http, assertions } = parseBridgeConsole(file.console);
6037
+ const httpSorted = [...http].sort(by((event) => event.timestampMs));
6038
+ return concat(
6039
+ renderPerFileOverviewBlock(rel, file.testResults, env.onlyFailures),
6040
+ renderFileBadge(rel, failed.length, env.onlyFailures),
6041
+ renderFileLevelFailure(file, env.ctx),
6042
+ ...failed.map(
6043
+ (assertion) => renderFailedAssertion({
6044
+ file,
6045
+ relPath: rel,
6046
+ assertion,
6047
+ ctx: env.ctx,
6048
+ assertionEvents: assertions,
6049
+ httpSorted
6050
+ })
6051
+ )
6052
+ );
5582
6053
  };
5583
- var fileSuggestsHttp = (relPath2) => /(?:^|\/)(routes?|api|controllers?|e2e|integration)(?:\/|\.test\.)/i.test(relPath2);
5584
- var isHttpRelevant = (ctx) => ctx.hasTransportSignal || ctx.httpCountInSameTest > 0 || titleSuggestsHttp(ctx.title) || hasStatusSemantics(ctx.assertion) || fileSuggestsHttp(ctx.relPath);
5585
6054
  var vitestFooter = (agg, durationMs) => {
5586
6055
  const files = [
5587
6056
  agg.numFailedTestSuites ? colorTokens2.fail(`${agg.numFailedTestSuites} failed`) : "",
@@ -5602,462 +6071,24 @@ var vitestFooter = (agg, durationMs) => {
5602
6071
  `${ansi.bold("Time")} ${time} ${thread}`
5603
6072
  ].join("\n");
5604
6073
  };
5605
- var renderVitestFromJestJSON = (data, ctx, opts) => {
5606
- const out = [];
5607
- const onlyFailures = Boolean(opts?.onlyFailures);
5608
- if (!onlyFailures) {
5609
- out.push(`${BackgroundColors.Run(ansi.white(" RUN "))} ${ansi.dim(ctx.cwd)}`, "");
5610
- }
5611
- for (const file of data.testResults) {
5612
- const rel = file.testFilePath.replace(/\\/g, "/").replace(`${ctx.cwd}/`, "");
5613
- const failed = file.testResults.filter((assertion) => assertion.status === "failed");
5614
- if (!onlyFailures) {
5615
- out.push(...buildPerFileOverview(rel, file.testResults));
5616
- }
5617
- if (!(onlyFailures && failed.length === 0)) {
5618
- out.push(buildFileBadgeLine(rel, failed.length));
5619
- }
5620
- let httpSorted = [];
5621
- let assertionEvents = [];
5622
- {
5623
- const parseBridge = (consoleEntries) => {
5624
- const http = [];
5625
- const assertions = [];
5626
- if (!Array.isArray(consoleEntries)) {
5627
- return { http, assertions };
5628
- }
5629
- for (const entry of consoleEntries) {
5630
- const rec = entry;
5631
- const rawMsgVal = rec && typeof rec.message !== "undefined" ? rec.message : "";
5632
- const raw = Array.isArray(rawMsgVal) ? rawMsgVal.map(String).join(" ") : String(rawMsgVal ?? "");
5633
- if (!raw.includes("[JEST-BRIDGE-EVENT]")) {
5634
- continue;
5635
- }
5636
- const jsonText = raw.split("[JEST-BRIDGE-EVENT]").pop()?.trim() ?? "";
5637
- try {
5638
- const evt = import_json52.default.parse(jsonText);
5639
- const type = evt?.type;
5640
- if (type === "httpResponse") {
5641
- const timestampMs = Number(evt.timestampMs ?? Date.now());
5642
- http.push({
5643
- kind: "response",
5644
- timestampMs,
5645
- method: evt.method,
5646
- url: evt.url,
5647
- route: evt.route,
5648
- statusCode: evt.statusCode,
5649
- durationMs: evt.durationMs,
5650
- contentType: evt.contentType,
5651
- requestId: evt.requestId,
5652
- json: evt.json,
5653
- bodyPreview: evt.bodyPreview,
5654
- testPath: evt.testPath,
5655
- currentTestName: evt.currentTestName
5656
- });
5657
- } else if (type === "httpAbort") {
5658
- http.push({
5659
- kind: "abort",
5660
- timestampMs: Number(evt.timestampMs ?? Date.now()),
5661
- method: evt.method,
5662
- url: evt.url,
5663
- route: evt.route,
5664
- durationMs: evt.durationMs,
5665
- testPath: evt.testPath,
5666
- currentTestName: evt.currentTestName
5667
- });
5668
- } else if (type === "httpResponseBatch") {
5669
- const list = asHttpList(evt?.events);
5670
- for (const item of list) {
5671
- const anyItem = item;
5672
- http.push({
5673
- timestampMs: Number(anyItem.timestampMs ?? Date.now()),
5674
- method: anyItem.method,
5675
- url: anyItem.url,
5676
- route: anyItem.route,
5677
- statusCode: anyItem.statusCode,
5678
- durationMs: anyItem.durationMs,
5679
- contentType: anyItem.contentType,
5680
- requestId: anyItem.requestId,
5681
- json: anyItem.json,
5682
- bodyPreview: anyItem.bodyPreview,
5683
- testPath: evt.testPath,
5684
- currentTestName: evt.currentTestName
5685
- });
5686
- }
5687
- } else if (type === "assertionFailure") {
5688
- assertions.push({
5689
- timestampMs: typeof evt.timestampMs === "number" ? evt.timestampMs : void 0,
5690
- matcher: evt.matcher,
5691
- expectedNumber: typeof evt.expectedNumber === "number" ? evt.expectedNumber : void 0,
5692
- receivedNumber: typeof evt.receivedNumber === "number" ? evt.receivedNumber : void 0,
5693
- message: typeof evt.message === "string" ? evt.message : void 0,
5694
- stack: typeof evt.stack === "string" ? evt.stack : void 0,
5695
- testPath: evt.testPath,
5696
- currentTestName: evt.currentTestName,
5697
- expectedPreview: typeof evt.expectedPreview === "string" ? evt.expectedPreview : void 0,
5698
- actualPreview: typeof evt.actualPreview === "string" ? evt.actualPreview : void 0
5699
- });
5700
- }
5701
- } catch {
5702
- }
5703
- }
5704
- return { http, assertions };
5705
- };
5706
- const parsed = parseBridge(file.console);
5707
- httpSorted = [...parsed.http].sort(by((event) => event.timestampMs));
5708
- assertionEvents = parsed.assertions;
5709
- }
5710
- const inSameCtx = (testPath, testName) => httpSorted.filter(
5711
- (event) => event.testPath === testPath && event.currentTestName === testName
5712
- );
5713
- if (file.failureMessage || file.testExecError) {
5714
- const lines = file.failureMessage.split(/\r?\n/);
5715
- const combinedDetails = (() => {
5716
- const base = linesFromDetails(file.failureDetails);
5717
- const exec = linesFromDetails(
5718
- Array.isArray(file.testExecError) ? file.testExecError : [file.testExecError]
5719
- );
5720
- return {
5721
- stacks: [...base.stacks, ...exec.stacks],
5722
- messages: [...base.messages, ...exec.messages]
5723
- };
5724
- })();
5725
- const mergedForStack = collapseStacks([...lines, ...combinedDetails.stacks]);
5726
- const synthLoc = deepestProjectLoc(mergedForStack, ctx.projectHint);
5727
- out.push(...buildCodeFrameSection(lines, ctx, synthLoc));
5728
- const payloadPretty = buildPrettyDiffSection(file.failureDetails, lines);
5729
- out.push(...payloadPretty);
5730
- const hasPretty = payloadPretty.length > 0;
5731
- const stackPreview = ctx.showStacks ? mergedForStack.filter((ln) => isStackLine(stripAnsiSimple(ln))).filter((ln) => ctx.projectHint.test(stripAnsiSimple(ln))).slice(0, 2).map((ln) => ` ${colorStackLine(String(ln), ctx.projectHint)}`) : [];
5732
- out.push(
5733
- ...buildMessageSection(lines, combinedDetails, ctx, {
5734
- suppressDiff: hasPretty,
5735
- stackPreview
5736
- })
5737
- );
5738
- out.push(...buildConsoleSection(stripBridgeEventsFromConsole(file.console ?? null)));
5739
- if (ctx.showStacks && stackPreview.length === 0) {
5740
- const tail = mergedForStack.filter((ln) => isStackLine(stripAnsiSimple(ln))).slice(-4).map((ln) => ` ${colorStackLine(String(ln), ctx.projectHint)}`);
5741
- if (tail.length) {
5742
- out.push(ansi.dim(" Stack:"), ...tail, "");
5743
- }
5744
- }
5745
- }
5746
- for (const assertion of failed) {
5747
- out.push(drawFailLine());
5748
- const header = `${rel} > ${assertion.fullName}`;
5749
- const messagesArray = (() => {
5750
- if (assertion.failureMessages && assertion.failureMessages.length > 0) {
5751
- return assertion.failureMessages;
5752
- }
5753
- if (file.failureMessage && file.failureMessage.trim().length > 0) {
5754
- return file.failureMessage.split(/\r?\n/);
5755
- }
5756
- const linesFromMatcher = linesFromDetails(
5757
- assertion.failureDetails || file.failureDetails
5758
- ).messages;
5759
- if (Array.isArray(linesFromMatcher) && linesFromMatcher.length > 0) {
5760
- return linesFromMatcher;
5761
- }
5762
- return [""];
5763
- })();
5764
- const details = linesFromDetails(assertion.failureDetails || file.failureDetails);
5765
- const matcherMsg = (() => {
5766
- try {
5767
- const arr = assertion.failureDetails || file.failureDetails;
5768
- if (!arr) {
5769
- return [];
5770
- }
5771
- for (const detailEntry of arr) {
5772
- const obj = detailEntry && typeof detailEntry === "object" ? detailEntry : null;
5773
- const mr = obj && obj.matcherResult && typeof obj.matcherResult === "object" ? obj.matcherResult : null;
5774
- if (mr && typeof mr.message === "string" && mr.message.trim()) {
5775
- const name = typeof mr.matcherName === "string" ? mr.matcherName : "";
5776
- const matcherHeader = name ? ` ${ansi.bold("Matcher:")} ${ansi.yellow(name)}` : "";
5777
- const bodyHeader = ` ${ansi.bold("Message:")}`;
5778
- const body = String(mr.message).split(/\r?\n/).slice(0, 6).map((ln) => ` ${ansi.yellow(ln)}`);
5779
- return [matcherHeader, bodyHeader, ...body, ""].filter(Boolean);
5780
- }
5781
- }
5782
- } catch {
5783
- }
5784
- return [];
5785
- })();
5786
- const mergedForStack = collapseStacks([...messagesArray, ...details.stacks]);
5787
- const deepestLoc = deepestProjectLoc(mergedForStack, ctx.projectHint);
5788
- const locLink = deepestLoc ? (() => {
5789
- const href = preferredEditorHref(deepestLoc.file, deepestLoc.line, ctx.editorCmd);
5790
- const base = `${deepestLoc.file.split("/").pop()}:${deepestLoc.line}`;
5791
- return osc8(base, href);
5792
- })() : void 0;
5793
- const bullet = (text) => `${Colors.Failure("\xD7")} ${ansi.white(text)}`;
5794
- const headerLine = `${ansi.white(header)}${locLink ? ` ${ansi.dim(`(${locLink})`)}` : ""}`;
5795
- out.push(bullet(headerLine));
5796
- const msgLines = messagesArray.join("\n").split("\n");
5797
- const assertFallback = deepestLoc || assertion.location && { file: file.testFilePath, line: assertion.location.line };
5798
- out.push("", ...buildCodeFrameSection(msgLines, ctx, assertFallback || void 0), "");
5799
- const pretty = buildPrettyDiffSection(
5800
- assertion.failureDetails || file.failureDetails,
5801
- msgLines
5802
- );
5803
- out.push(...pretty);
5804
- const hasPretty = pretty.length > 0;
5805
- const stackPreview = ctx.showStacks ? mergedForStack.filter((ln) => isStackLine(stripAnsiSimple(ln))).filter((ln) => ctx.projectHint.test(stripAnsiSimple(ln))).slice(0, 2).map((ln) => ` ${colorStackLine(String(ln), ctx.projectHint)}`) : [];
5806
- if (matcherMsg.length) {
5807
- out.push(...matcherMsg);
5808
- }
5809
- out.push(
5810
- ...buildMessageSection(msgLines, details, ctx, { suppressDiff: hasPretty, stackPreview })
5811
- );
5812
- {
5813
- const HEADLAMP_HTTP_DIFF_LIMIT_LOCAL = () => HEADLAMP_HTTP_DIFF_LIMIT();
5814
- const safeParseJSON = (text) => {
5815
- try {
5816
- return text ? import_json52.default.parse(text) : void 0;
5817
- } catch {
5818
- return void 0;
5819
- }
5820
- };
5821
- const jsonDiff = (expected, actual, limit = HEADLAMP_HTTP_DIFF_LIMIT_LOCAL()) => {
5822
- const outChanges = [];
5823
- const queue = [];
5824
- queue.push({
5825
- pathSoFar: "$",
5826
- expectedValue: expected,
5827
- actualValue: actual
5828
- });
5829
- while (queue.length && outChanges.length < limit) {
5830
- const { pathSoFar, expectedValue, actualValue } = queue.shift();
5831
- const expectedIsObj = expectedValue && typeof expectedValue === "object";
5832
- const actualIsObj = actualValue && typeof actualValue === "object";
5833
- if (!expectedIsObj && !actualIsObj) {
5834
- if (JSON.stringify(expectedValue) !== JSON.stringify(actualValue)) {
5835
- outChanges.push({
5836
- kind: "changed",
5837
- path: pathSoFar,
5838
- preview: `${String(expectedValue)} \u2192 ${String(actualValue)}`
5839
- });
5840
- }
5841
- } else if (expectedIsObj && !actualIsObj) {
5842
- outChanges.push({
5843
- kind: "changed",
5844
- path: pathSoFar,
5845
- preview: "[object] \u2192 primitive"
5846
- });
5847
- } else if (!expectedIsObj && actualIsObj) {
5848
- outChanges.push({
5849
- kind: "changed",
5850
- path: pathSoFar,
5851
- preview: "primitive \u2192 [object]"
5852
- });
5853
- } else {
5854
- const expectedKeys = new Set(Object.keys(expectedValue));
5855
- const actualKeys = new Set(Object.keys(actualValue));
5856
- for (const key of expectedKeys) {
5857
- if (!actualKeys.has(key) && outChanges.length < limit) {
5858
- outChanges.push({ kind: "removed", path: `${pathSoFar}.${key}` });
5859
- }
5860
- }
5861
- for (const key of actualKeys) {
5862
- if (!expectedKeys.has(key) && outChanges.length < limit) {
5863
- outChanges.push({ kind: "added", path: `${pathSoFar}.${key}` });
5864
- }
5865
- }
5866
- for (const key of expectedKeys) {
5867
- if (actualKeys.has(key) && outChanges.length < limit) {
5868
- queue.push({
5869
- pathSoFar: `${pathSoFar}.${key}`,
5870
- expectedValue: expectedValue[key],
5871
- actualValue: actualValue[key]
5872
- });
5873
- }
5874
- }
5875
- }
5876
- }
5877
- return outChanges;
5878
- };
5879
- const importantMessages = (json) => {
5880
- const msgs = [];
5881
- try {
5882
- const obj = isObject(json) ? json : {};
5883
- const pushMaybe = (candidate) => {
5884
- if (typeof candidate === "string" && candidate.trim()) {
5885
- msgs.push(candidate);
5886
- }
5887
- };
5888
- pushMaybe(obj.displayMessage);
5889
- pushMaybe(obj.message);
5890
- if (Array.isArray(obj.errors)) {
5891
- for (const element of obj.errors) {
5892
- pushMaybe(isObject(element) ? element.message : void 0);
5893
- }
5894
- }
5895
- if (Array.isArray(obj.data)) {
5896
- for (const element of obj.data) {
5897
- pushMaybe(isObject(element) ? element.message : void 0);
5898
- }
5899
- }
5900
- } catch {
5901
- }
5902
- return msgs.slice(0, 2);
5903
- };
5904
- const nameMatches = (leftName, rightName) => !!leftName && !!rightName && (leftName === rightName || leftName.includes(rightName) || rightName.includes(leftName));
5905
- const corresponding = assertionEvents.find(
5906
- (aevt) => aevt.testPath === file.testFilePath && nameMatches(aevt.currentTestName, assertion.title)
5907
- ) ?? assertion;
5908
- const perTestSlice = inSameCtx(file.testFilePath, assertion.title);
5909
- const nearByTime = eventsNear(
5910
- httpSorted,
5911
- corresponding?.timestampMs,
5912
- file.testFilePath
5913
- );
5914
- const hasAbort = perTestSlice.some((event) => event.kind === "abort");
5915
- const hasTransport = isTransportError(corresponding?.message) || hasAbort;
5916
- const httpLikely = isHttpRelevant({
5917
- assertion: corresponding,
5918
- title: assertion.fullName,
5919
- relPath: rel,
5920
- httpCountInSameTest: perTestSlice.length || nearByTime.length,
5921
- hasTransportSignal: hasTransport
5922
- });
5923
- if (!httpLikely) {
5924
- } else {
5925
- const expPreview = corresponding?.expectedPreview;
5926
- const actPreview = corresponding?.actualPreview;
5927
- const parsedExpected = safeParseJSON(expPreview);
5928
- const parsedActual = safeParseJSON(actPreview);
5929
- let corr = corresponding;
5930
- if (!isHttpStatusNumber(corr.expectedNumber) && !isHttpStatusNumber(corr.receivedNumber)) {
5931
- const inferred = inferHttpNumbersFromText(msgLines);
5932
- if (isHttpStatusNumber(inferred.expectedNumber) || isHttpStatusNumber(inferred.receivedNumber)) {
5933
- corr = { ...corr, ...inferred };
5934
- }
5935
- }
5936
- const relevant = pickRelevantHttp(
5937
- {
5938
- timestampMs: corr?.timestampMs,
5939
- expectedNumber: corr?.expectedNumber,
5940
- receivedNumber: corr?.receivedNumber,
5941
- matcher: corr?.matcher,
5942
- message: corr?.message,
5943
- stack: corr?.stack,
5944
- testPath: file.testFilePath,
5945
- currentTestName: assertion.title
5946
- },
5947
- httpSorted,
5948
- {
5949
- testPath: file.testFilePath,
5950
- currentTestName: assertion.title,
5951
- title: assertion.fullName
5952
- }
5953
- );
5954
- if (hasTransport) {
5955
- const tsBase = corresponding?.timestampMs ?? 0;
5956
- const abortCandidates = perTestSlice.filter((event) => event.kind === "abort").sort((leftEvent, rightEvent) => {
5957
- const deltaLeft = Math.abs(tsBase - (leftEvent.timestampMs ?? 0));
5958
- const deltaRight = Math.abs(tsBase - (rightEvent.timestampMs ?? 0));
5959
- return deltaLeft - deltaRight;
5960
- });
5961
- const [nearestAbort] = abortCandidates;
5962
- if (nearestAbort) {
5963
- out.push(
5964
- " HTTP:",
5965
- `
5966
- ${summarizeUrl(nearestAbort.method, nearestAbort.url, nearestAbort.route)} ${ansi.dim("->")} ${ansi.yellow("connection aborted")}`,
5967
- (() => {
5968
- const ms = nearestAbort.durationMs;
5969
- return ms != null ? ` ${ansi.dim(`(${ms}ms)`)} ` : "";
5970
- })(),
5971
- "\n"
5972
- );
5973
- } else if (relevant) {
5974
- } else if (HEADLAMP_HTTP_SHOW_MISS()) {
5975
- out.push(
5976
- " HTTP:",
5977
- `
5978
- ${ansi.dim("Transport error; no matching HTTP exchange in window.")}`,
5979
- "\n"
5980
- );
5981
- }
5982
- }
5983
- if (!hasTransport && relevant) {
5984
- const parts = [];
5985
- const where = summarizeUrl(relevant.method, relevant.url, relevant.route);
5986
- const line1 = [
5987
- " HTTP:",
5988
- `
5989
- ${where} ${ansi.dim("->")} ${relevant.statusCode ?? "?"}`,
5990
- (() => {
5991
- const ms = relevant.durationMs;
5992
- return typeof ms === "number" ? ` ${ansi.dim(`(${ms}ms)`)} ` : " ";
5993
- })(),
5994
- relevant.contentType ? ansi.dim(`(${relevant.contentType})`) : "",
5995
- relevant.requestId ? ansi.dim(` reqId=${relevant.requestId}`) : ""
5996
- ].join("");
5997
- const expVsAct = (() => {
5998
- if (typeof corresponding?.expectedNumber === "number" || typeof corresponding?.receivedNumber === "number") {
5999
- const exp = corresponding?.expectedNumber != null ? String(corresponding.expectedNumber) : "?";
6000
- const got = corresponding?.receivedNumber != null ? String(corresponding.receivedNumber) : String(relevant.statusCode ?? "?");
6001
- return `
6002
- Expected: ${ansi.yellow(exp)} Received: ${ansi.yellow(got)}`;
6003
- }
6004
- return "";
6005
- })();
6006
- const whyLines = importantMessages(relevant.json).map((msg) => `
6007
- Why: ${ansi.white(msg)}`).slice(0, 1).join("");
6008
- const diffLines = (() => {
6009
- const rightActual = parsedActual ?? relevant.json;
6010
- if (!parsedExpected || !rightActual) {
6011
- return "";
6012
- }
6013
- const changes = jsonDiff(parsedExpected, rightActual);
6014
- if (!changes.length) {
6015
- return "";
6016
- }
6017
- const head = "\n Diff:";
6018
- const body = changes.map((change) => {
6019
- const marker = change.kind === "added" ? "+" : change.kind === "removed" ? "-" : "~";
6020
- const previewText = change.preview ? `: ${ansi.dim(change.preview)}` : "";
6021
- return `
6022
- ${marker} ${change.path}${previewText}`;
6023
- }).join("");
6024
- return head + body;
6025
- })();
6026
- parts.push(line1, expVsAct, whyLines, diffLines, "\n");
6027
- out.push(...parts.filter(Boolean));
6028
- } else if (!hasTransport && !relevant && HEADLAMP_HTTP_SHOW_MISS()) {
6029
- out.push(
6030
- " HTTP:",
6031
- `
6032
- ${ansi.dim("No relevant HTTP exchange found. (HEADLAMP_HTTP_MISS=0 to hide)")}`,
6033
- "\n"
6034
- );
6035
- }
6036
- }
6037
- }
6038
- const minimalInfo = msgLines.every((ln) => !ln.trim());
6039
- if (minimalInfo) {
6040
- try {
6041
- out.push(...buildThrownSection(assertion.failureDetails || []));
6042
- } catch {
6043
- }
6044
- }
6045
- out.push(...buildConsoleSection(stripBridgeEventsFromConsole(file.console ?? null)));
6046
- if (ctx.showStacks && stackPreview.length === 0) {
6047
- const tail = mergedForStack.filter((ln) => isStackLine(stripAnsiSimple(ln))).slice(-4).map((ln) => ` ${colorStackLine(String(ln), ctx.projectHint)}`);
6048
- if (tail.length) {
6049
- out.push(ansi.dim(" Stack:"), ...tail, "");
6050
- }
6051
- }
6052
- out.push(drawFailLine(), "");
6053
- }
6054
- }
6074
+ var renderFooter = (data) => {
6055
6075
  const failedCount = data.aggregated.numFailedTests;
6056
- out.push(drawRule(BackgroundColors.Failure(ansi.white(` Failed Tests ${failedCount} `))));
6057
- out.push("");
6058
- out.push(vitestFooter(data.aggregated));
6059
- return out.join("\n");
6076
+ return [
6077
+ drawRule(BackgroundColors.Failure(ansi.white(` Failed Tests ${failedCount} `))),
6078
+ "",
6079
+ vitestFooter(data.aggregated)
6080
+ ];
6060
6081
  };
6082
+ var renderVitestFromJestJSON = (data, ctx, opts) => pipe(
6083
+ concat(
6084
+ renderRunHeader({ ctx, onlyFailures: Boolean(opts?.onlyFailures) }),
6085
+ ...data.testResults.map(
6086
+ (file) => renderFileBlock(file, { ctx, onlyFailures: Boolean(opts?.onlyFailures) })
6087
+ ),
6088
+ renderFooter(data)
6089
+ ),
6090
+ joinLines
6091
+ );
6061
6092
 
6062
6093
  // src/lib/formatter/bridge/tryBridgeFallback.ts
6063
6094
  var tryBridgeFallback = (raw, ctx, opts) => {
@@ -6452,7 +6483,8 @@ var program = async () => {
6452
6483
  coverageMaxFiles: coverageMaxFilesArg,
6453
6484
  coverageMaxHotspots: coverageMaxHotspotsArg,
6454
6485
  coveragePageFit,
6455
- changed
6486
+ changed,
6487
+ changedDepth
6456
6488
  } = deriveArgs(argv);
6457
6489
  const getChangedFiles = async (mode, cwd) => {
6458
6490
  const collect = async (cmd, args) => {
@@ -6924,7 +6956,7 @@ var program = async () => {
6924
6956
  resolutionCache.set(key, resolved);
6925
6957
  return resolved;
6926
6958
  };
6927
- const MAX_DEPTH = 5;
6959
+ const MAX_DEPTH = Number.isFinite(Number(changedDepth)) && Number(changedDepth) > 0 ? Number(changedDepth) : 5;
6928
6960
  const seen = /* @__PURE__ */ new Set();
6929
6961
  const matchesTransitively = async (absTestPath, depth) => {
6930
6962
  if (depth > MAX_DEPTH) {