@ait-co/devtools 0.1.126 → 0.1.128

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.
@@ -64,6 +64,14 @@ interface RelayRunOptions {
64
64
  */
65
65
  collectCaptures?: boolean;
66
66
  }
67
+ /**
68
+ * Sentinel string embedded in the error message by `injectAndRunBundle` when
69
+ * the per-file evaluate race hits the timeout. Used by the retry guard so only
70
+ * genuine timeouts get a second attempt — not bundle errors or parse failures.
71
+ *
72
+ * Exported for unit tests that assert the retry path is taken.
73
+ */
74
+ declare const EVALUATE_TIMEOUT_MARKER = "rpc: evaluate timed out after";
67
75
  /**
68
76
  * Runs all `files` sequentially over the given CDP `connection`.
69
77
  *
@@ -94,5 +102,5 @@ declare function flattenResults(report: RelayRunReport): Array<TestResult & {
94
102
  file: string;
95
103
  }>;
96
104
  //#endregion
97
- export { runTestFilesOverRelay as a, flattenResults as i, RelayRunOptions as n, RelayRunReport as r, FileResult as t };
98
- //# sourceMappingURL=relay-worker-7biDlEjo.d.ts.map
105
+ export { flattenResults as a, RelayRunReport as i, FileResult as n, runTestFilesOverRelay as o, RelayRunOptions as r, EVALUATE_TIMEOUT_MARKER as t };
106
+ //# sourceMappingURL=relay-worker-TReZtzGl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relay-worker-TReZtzGl.d.ts","names":[],"sources":["../src/test-runner/relay-worker.ts"],"mappings":";;;;;;;UAwBiB,UAAA;EAcf;EAZA,IAAA;EAcA;EAZA,MAAA,EAAQ,SAAA;IAAc,KAAA;EAAA;AAAA;;UAIP,cAAA;EAwBS;EAtBxB,SAAA;EA0Be;EAxBf,QAAA;;EAEA,KAAA,EAAO,UAAA;EA0BP;EAxBA,MAAA;IACE,MAAA;IACA,MAAA;IACA,OAAA;IACA,KAAA;EAAA;EA+CgC;;;;AAuBpC;;;;;;EA1DE,QAAA,EAAU,cAAA;AAAA;;UAIK,eAAA;EAuDf;;;EAnDA,aAAA,GAAgB,aAAA;EAsDf;;;;EAjDD,SAAA;EAwP4B;;;;;;;;;;;EA5O5B,eAAA;AAAA;;;;;;;;cAUW,uBAAA;;;;;;;;;;;;;;;;;;;;;;iBAuBS,qBAAA,CACpB,UAAA,EAAY,aAAA,EACZ,KAAA,YACA,IAAA,GAAO,eAAA,GACN,OAAA,CAAQ,cAAA;;;;;iBAuMK,cAAA,CAAe,MAAA,EAAQ,cAAA,GAAiB,KAAA,CAAM,UAAA;EAAe,IAAA;AAAA"}
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
+ import * as path$1 from "node:path";
3
+ import path, { basename, isAbsolute, resolve } from "node:path";
2
4
  import { parseArgs } from "node:util";
3
5
  import * as fs from "node:fs/promises";
4
6
  import { glob, mkdir, writeFile } from "node:fs/promises";
5
- import * as path$1 from "node:path";
6
- import path, { isAbsolute, resolve } from "node:path";
7
7
  import { accessSync } from "node:fs";
8
8
  import { fileURLToPath } from "node:url";
9
9
  //#region src/test-runner/discover.ts
@@ -549,7 +549,7 @@ function parseCaptureLines(raw) {
549
549
  //#endregion
550
550
  //#region src/test-runner/rpc.ts
551
551
  /** Maximum milliseconds to wait for a single evaluate round-trip. */
552
- const DEFAULT_TIMEOUT_MS = 3e4;
552
+ const DEFAULT_TIMEOUT_MS = 6e4;
553
553
  /**
554
554
  * Wraps bundle code in a self-executing IIFE that:
555
555
  * 1. Evaluates the bundle (registering describe/it/test).
@@ -608,13 +608,22 @@ function parseRunTestsResult(rawValue) {
608
608
  */
609
609
  async function injectAndRunBundle(connection, bundleCode, timeoutMs = DEFAULT_TIMEOUT_MS) {
610
610
  const expression = buildRunTestsExpression(bundleCode);
611
- const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error(`rpc: evaluate timed out after ${timeoutMs}ms`)), timeoutMs));
611
+ const TIMEOUT_SENTINEL = Symbol("timeout");
612
+ const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve(TIMEOUT_SENTINEL), timeoutMs));
612
613
  const evalPromise = connection.send("Runtime.evaluate", {
613
614
  expression,
614
615
  returnByValue: true,
615
616
  awaitPromise: true
616
617
  });
617
- const cdpResult = await Promise.race([evalPromise, timeoutPromise]);
618
+ const raceResult = await Promise.race([evalPromise.then((v) => ({
619
+ tag: "eval",
620
+ v
621
+ })), timeoutPromise.then(() => ({ tag: "timeout" }))]);
622
+ if (raceResult.tag === "timeout") return {
623
+ ok: false,
624
+ error: `rpc: evaluate timed out after ${timeoutMs}ms`
625
+ };
626
+ const cdpResult = raceResult.v;
618
627
  if (cdpResult.exceptionDetails) {
619
628
  const msg = cdpResult.exceptionDetails.exception?.description ?? cdpResult.exceptionDetails.text ?? "Runtime.evaluate threw an exception";
620
629
  throw new Error(`rpc.injectAndRunBundle: ${msg}`);
@@ -624,6 +633,14 @@ async function injectAndRunBundle(connection, bundleCode, timeoutMs = DEFAULT_TI
624
633
  //#endregion
625
634
  //#region src/test-runner/relay-worker.ts
626
635
  /**
636
+ * Sentinel string embedded in the error message by `injectAndRunBundle` when
637
+ * the per-file evaluate race hits the timeout. Used by the retry guard so only
638
+ * genuine timeouts get a second attempt — not bundle errors or parse failures.
639
+ *
640
+ * Exported for unit tests that assert the retry path is taken.
641
+ */
642
+ const EVALUATE_TIMEOUT_MARKER = "rpc: evaluate timed out after";
643
+ /**
627
644
  * Runs all `files` sequentially over the given CDP `connection`.
628
645
  *
629
646
  * For each file:
@@ -666,15 +683,53 @@ async function runTestFilesOverRelay(connection, files, opts) {
666
683
  let fileEntry;
667
684
  try {
668
685
  const { code } = await bundleTestFile(file, opts?.bundleOptions);
669
- const rpcResult = await injectAndRunBundle(connection, code, opts?.timeoutMs);
670
- if (rpcResult.ok) fileEntry = {
671
- file,
672
- result: rpcResult.report
673
- };
674
- else fileEntry = {
675
- file,
676
- result: { error: rpcResult.error }
686
+ /**
687
+ * Runs one evaluate attempt and returns a FileResult, or `null` when the
688
+ * result is a genuine timeout and the caller should retry.
689
+ *
690
+ * We need to distinguish:
691
+ * - `rpcResult.ok = false` + timeout error → retry candidate (`return null`)
692
+ * - `rpcResult.ok = false` + other error → final error, no retry
693
+ * - `injectAndRunBundle` throws → CDP exceptionDetails (page
694
+ * engine threw); treated as a final (non-retryable) error.
695
+ *
696
+ * The Promise.race timeout in rpc.ts RETURNS `{ok:false, error: '…'}` (it
697
+ * does NOT throw/reject). Only genuine CDP `exceptionDetails` cause a throw.
698
+ * This distinction is what makes the EVALUATE_TIMEOUT_MARKER gate below
699
+ * reachable — the timeout result surfaces as `rpcResult.ok=false` with the
700
+ * marker string, not as a caught exception.
701
+ */
702
+ const attempt = async () => {
703
+ let rpcResult;
704
+ try {
705
+ rpcResult = await injectAndRunBundle(connection, code, opts?.timeoutMs);
706
+ } catch (e) {
707
+ return {
708
+ file,
709
+ result: { error: e instanceof Error ? e.message : String(e) }
710
+ };
711
+ }
712
+ if (rpcResult.ok) return {
713
+ file,
714
+ result: rpcResult.report
715
+ };
716
+ if (rpcResult.error.includes("rpc: evaluate timed out after")) return null;
717
+ return {
718
+ file,
719
+ result: { error: rpcResult.error }
720
+ };
677
721
  };
722
+ const firstResult = await attempt();
723
+ if (firstResult !== null) fileEntry = firstResult;
724
+ else {
725
+ process.stderr.write(`relay-worker: evaluate timed out for ${file} — retrying once\n`);
726
+ const retryResult = await attempt();
727
+ if (retryResult !== null) fileEntry = retryResult;
728
+ else fileEntry = {
729
+ file,
730
+ result: { error: `${EVALUATE_TIMEOUT_MARKER} ${opts?.timeoutMs ?? 3e4}ms (after retry)` }
731
+ };
732
+ }
678
733
  } catch (e) {
679
734
  fileEntry = {
680
735
  file,
@@ -961,6 +1016,42 @@ function resolveTimeouts(rawTimeout, rawAttachTimeout) {
961
1016
  };
962
1017
  }
963
1018
  /**
1019
+ * Renders per-file result lines and the aggregate totals line to a string.
1020
+ *
1021
+ * Each file gets one line:
1022
+ * - Error/timeout: `FAIL <basename>: <error-class>`
1023
+ * - Pass (0 tests): `OK <basename>: 0 passed (empty file)`
1024
+ * - Pass: `OK <basename>: N passed[, M failed][, K skipped]`
1025
+ *
1026
+ * The aggregate totals line always follows.
1027
+ *
1028
+ * SECRET-HANDLING: only `basename(file)` is used — no absolute paths, relay
1029
+ * URLs, wss URLs, scheme URLs, or TOTP codes appear in the output. The error
1030
+ * string comes from `result.error` which is already secret-free (relay-worker
1031
+ * produces only error-class messages like "rpc: evaluate timed out after
1032
+ * 30000ms").
1033
+ *
1034
+ * Exported so unit tests can assert the per-file lines without spawning a
1035
+ * subprocess or going through the full relay attach flow.
1036
+ */
1037
+ function renderSummary(report) {
1038
+ const lines = [];
1039
+ for (const { file, result } of report.files) {
1040
+ const name = basename(file);
1041
+ if ("error" in result) lines.push(`FAIL ${name}: ${result.error}`);
1042
+ else {
1043
+ const parts = [`${result.passed} passed`];
1044
+ if (result.failed > 0) parts.push(`${result.failed} failed`);
1045
+ if (result.skipped > 0) parts.push(`${result.skipped} skipped`);
1046
+ const suffix = result.passed + result.failed + result.skipped === 0 ? " (empty file)" : "";
1047
+ lines.push(`OK ${name}: ${parts.join(", ")}${suffix}`);
1048
+ }
1049
+ }
1050
+ const { totals, duration } = report;
1051
+ lines.push(`\ndevtools-test: ${totals.passed} passed, ${totals.failed} failed, ${totals.skipped} skipped (${duration}ms)`);
1052
+ return lines.join("\n");
1053
+ }
1054
+ /**
964
1055
  * Runs `files` over `connection` and returns the aggregate report.
965
1056
  * This pure function is the testable core of the CLI (and is what the
966
1057
  * `run_tests` MCP tool calls against the daemon's attached connection); it is
@@ -968,10 +1059,7 @@ function resolveTimeouts(rawTimeout, rawAttachTimeout) {
968
1059
  */
969
1060
  async function runWithConnection(connection, files, opts) {
970
1061
  const report = await runTestFilesOverRelay(connection, files, opts);
971
- if (opts?.printSummary) {
972
- const { totals } = report;
973
- process.stdout.write(`\ndevtools-test: ${totals.passed} passed, ${totals.failed} failed, ${totals.skipped} skipped (${report.duration}ms)\n`);
974
- }
1062
+ if (opts?.printSummary) process.stdout.write(`\n${renderSummary(report)}\n`);
975
1063
  return report;
976
1064
  }
977
1065
  /**