@ait-co/devtools 0.1.144 → 0.2.1

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 (182) hide show
  1. package/README.en.md +47 -218
  2. package/README.md +36 -246
  3. package/dist/in-app/auto.d.ts +1 -138
  4. package/dist/in-app/auto.js +28 -1102
  5. package/dist/in-app/auto.js.map +1 -1
  6. package/dist/in-app/index.d.ts +38 -547
  7. package/dist/in-app/index.d.ts.map +1 -1
  8. package/dist/in-app/index.js +62 -939
  9. package/dist/in-app/index.js.map +1 -1
  10. package/dist/mcp/cli.d.ts +1 -54
  11. package/dist/mcp/cli.js +33 -9720
  12. package/dist/mcp/cli.js.map +1 -1
  13. package/dist/mcp/server.d.ts +1 -88
  14. package/dist/mcp/server.js +36 -1076
  15. package/dist/mcp/server.js.map +1 -1
  16. package/dist/mock/index.d.ts +35 -21
  17. package/dist/mock/index.d.ts.map +1 -1
  18. package/dist/mock/index.js +81 -2
  19. package/dist/mock/index.js.map +1 -1
  20. package/dist/panel/index.js +80 -104
  21. package/dist/panel/index.js.map +1 -1
  22. package/dist/relay-url-store-CkVSQZMq.cjs +110 -0
  23. package/dist/relay-url-store-CkVSQZMq.cjs.map +1 -0
  24. package/dist/relay-url-store-dkII-DHD.js +109 -0
  25. package/dist/relay-url-store-dkII-DHD.js.map +1 -0
  26. package/dist/stubs/bin-devtools-mcp.js +58 -0
  27. package/dist/stubs/bin-devtools-mcp.js.map +1 -0
  28. package/dist/stubs/bin-devtools-test.d.ts +2 -0
  29. package/dist/stubs/bin-devtools-test.js +55 -0
  30. package/dist/stubs/bin-devtools-test.js.map +1 -0
  31. package/dist/test-runner/config.d.ts +1 -231
  32. package/dist/test-runner/config.js +41 -45
  33. package/dist/test-runner/config.js.map +1 -1
  34. package/dist/{tunnel-BGT9Curk.cjs → tunnel-BKZkOyQp.cjs} +1 -1
  35. package/dist/{tunnel-BGT9Curk.cjs.map → tunnel-BKZkOyQp.cjs.map} +1 -1
  36. package/dist/{tunnel-BOKmLzBO.js → tunnel-CqSCIrdU.js} +1 -1
  37. package/dist/{tunnel-BOKmLzBO.js.map → tunnel-CqSCIrdU.js.map} +1 -1
  38. package/dist/unplugin/index.cjs +9 -18
  39. package/dist/unplugin/index.cjs.map +1 -1
  40. package/dist/unplugin/index.d.cts +26 -5
  41. package/dist/unplugin/index.d.cts.map +1 -1
  42. package/dist/unplugin/index.d.ts +27 -6
  43. package/dist/unplugin/index.d.ts.map +1 -1
  44. package/dist/unplugin/index.js +10 -19
  45. package/dist/unplugin/index.js.map +1 -1
  46. package/package.json +10 -25
  47. package/dist/attach-orchestrator-0F0m_UqQ.js +0 -1845
  48. package/dist/attach-orchestrator-0F0m_UqQ.js.map +0 -1
  49. package/dist/attach-orchestrator-D65KxFy_.js +0 -1831
  50. package/dist/attach-orchestrator-D65KxFy_.js.map +0 -1
  51. package/dist/attach-orchestrator-DL3NQ9ca.js +0 -1846
  52. package/dist/attach-orchestrator-DL3NQ9ca.js.map +0 -1
  53. package/dist/bundle-C796JIwG.d.ts +0 -159
  54. package/dist/bundle-C796JIwG.d.ts.map +0 -1
  55. package/dist/capture-DsP525OZ.d.ts +0 -58
  56. package/dist/capture-DsP525OZ.d.ts.map +0 -1
  57. package/dist/cdp-connection-rP1WdnH5.d.ts +0 -287
  58. package/dist/cdp-connection-rP1WdnH5.d.ts.map +0 -1
  59. package/dist/cell-BaLvusOl.js +0 -68
  60. package/dist/cell-BaLvusOl.js.map +0 -1
  61. package/dist/cell-CBUS3-nT.js +0 -274
  62. package/dist/cell-CBUS3-nT.js.map +0 -1
  63. package/dist/cell-EBKKpAAT.js +0 -307
  64. package/dist/cell-EBKKpAAT.js.map +0 -1
  65. package/dist/chii-relay-B3ZhjGMi.js +0 -304
  66. package/dist/chii-relay-B3ZhjGMi.js.map +0 -1
  67. package/dist/chii-relay-CGMlePMd.cjs +0 -304
  68. package/dist/chii-relay-CGMlePMd.cjs.map +0 -1
  69. package/dist/debug-server-B3ABDrRI.js +0 -456
  70. package/dist/debug-server-B3ABDrRI.js.map +0 -1
  71. package/dist/debug-server-BWhwrVXa.js +0 -1158
  72. package/dist/debug-server-BWhwrVXa.js.map +0 -1
  73. package/dist/debug-server-CfQNxxGW.js +0 -600
  74. package/dist/debug-server-CfQNxxGW.js.map +0 -1
  75. package/dist/devtools-opener-3Drge_RJ.js +0 -75
  76. package/dist/devtools-opener-3Drge_RJ.js.map +0 -1
  77. package/dist/devtools-opener-CJpEsXXQ.js +0 -76
  78. package/dist/devtools-opener-CJpEsXXQ.js.map +0 -1
  79. package/dist/devtools-opener-CxtryS8c.js +0 -75
  80. package/dist/devtools-opener-CxtryS8c.js.map +0 -1
  81. package/dist/in-app/auto.d.ts.map +0 -1
  82. package/dist/mcp/cli.d.ts.map +0 -1
  83. package/dist/mcp/server.d.ts.map +0 -1
  84. package/dist/pool-DcaaOwUq.d.ts +0 -14761
  85. package/dist/pool-DcaaOwUq.d.ts.map +0 -1
  86. package/dist/qr-http-server-C_lqOrgc.js +0 -1644
  87. package/dist/qr-http-server-C_lqOrgc.js.map +0 -1
  88. package/dist/qr-http-server-CopuMbub.js +0 -1644
  89. package/dist/qr-http-server-CopuMbub.js.map +0 -1
  90. package/dist/qr-http-server-DrbIVDjO.js +0 -1645
  91. package/dist/qr-http-server-DrbIVDjO.js.map +0 -1
  92. package/dist/relay-factory-N9QobQxG.js +0 -206
  93. package/dist/relay-factory-N9QobQxG.js.map +0 -1
  94. package/dist/relay-secret-store-BR0YIkNv.cjs +0 -241
  95. package/dist/relay-secret-store-BR0YIkNv.cjs.map +0 -1
  96. package/dist/relay-secret-store-Bmyleu0A.js +0 -154
  97. package/dist/relay-secret-store-Bmyleu0A.js.map +0 -1
  98. package/dist/relay-secret-store-CQenfcSL.js +0 -154
  99. package/dist/relay-secret-store-CQenfcSL.js.map +0 -1
  100. package/dist/relay-secret-store-CYM8CBIF.js +0 -240
  101. package/dist/relay-secret-store-CYM8CBIF.js.map +0 -1
  102. package/dist/relay-secret-store-DKxs7zwq.js +0 -153
  103. package/dist/relay-secret-store-DKxs7zwq.js.map +0 -1
  104. package/dist/relay-secret-store-WJ8EGkIl.js +0 -153
  105. package/dist/relay-secret-store-WJ8EGkIl.js.map +0 -1
  106. package/dist/relay-url-store-BR2XodiO.js +0 -123
  107. package/dist/relay-url-store-BR2XodiO.js.map +0 -1
  108. package/dist/relay-url-store-C1as_m5G.cjs +0 -115
  109. package/dist/relay-url-store-C1as_m5G.cjs.map +0 -1
  110. package/dist/relay-url-store-CH63fVCm.js +0 -122
  111. package/dist/relay-url-store-CH63fVCm.js.map +0 -1
  112. package/dist/relay-url-store-CzFo_84F.js +0 -114
  113. package/dist/relay-url-store-CzFo_84F.js.map +0 -1
  114. package/dist/relay-url-store-DaY1QPes.js +0 -123
  115. package/dist/relay-url-store-DaY1QPes.js.map +0 -1
  116. package/dist/relay-url-store-xmUuTjXA.js +0 -122
  117. package/dist/relay-url-store-xmUuTjXA.js.map +0 -1
  118. package/dist/relay-worker-B5HKkGUY.js +0 -832
  119. package/dist/relay-worker-B5HKkGUY.js.map +0 -1
  120. package/dist/relay-worker-YdlpZQl9.d.ts +0 -214
  121. package/dist/relay-worker-YdlpZQl9.d.ts.map +0 -1
  122. package/dist/rolldown-runtime-DGkTqVfb.js +0 -15
  123. package/dist/rolldown-runtime-DUslC3ob.js +0 -14
  124. package/dist/runtime-kn9DxOeg.d.ts +0 -249
  125. package/dist/runtime-kn9DxOeg.d.ts.map +0 -1
  126. package/dist/test-runner/bin.js +0 -2584
  127. package/dist/test-runner/bin.js.map +0 -1
  128. package/dist/test-runner/bridge-stub.d.ts +0 -125
  129. package/dist/test-runner/bridge-stub.d.ts.map +0 -1
  130. package/dist/test-runner/bridge-stub.js +0 -92
  131. package/dist/test-runner/bridge-stub.js.map +0 -1
  132. package/dist/test-runner/bundle.d.ts +0 -2
  133. package/dist/test-runner/bundle.js +0 -439
  134. package/dist/test-runner/bundle.js.map +0 -1
  135. package/dist/test-runner/capture.d.ts +0 -2
  136. package/dist/test-runner/capture.js +0 -44
  137. package/dist/test-runner/capture.js.map +0 -1
  138. package/dist/test-runner/config.d.ts.map +0 -1
  139. package/dist/test-runner/method-pace.d.ts +0 -82
  140. package/dist/test-runner/method-pace.d.ts.map +0 -1
  141. package/dist/test-runner/method-pace.js +0 -120
  142. package/dist/test-runner/method-pace.js.map +0 -1
  143. package/dist/test-runner/pool.d.ts +0 -2
  144. package/dist/test-runner/pool.js +0 -136
  145. package/dist/test-runner/pool.js.map +0 -1
  146. package/dist/test-runner/relay-factory.d.ts +0 -11245
  147. package/dist/test-runner/relay-factory.d.ts.map +0 -1
  148. package/dist/test-runner/relay-factory.js +0 -206
  149. package/dist/test-runner/relay-factory.js.map +0 -1
  150. package/dist/test-runner/relay-worker.d.ts +0 -2
  151. package/dist/test-runner/relay-worker.js +0 -2
  152. package/dist/test-runner/report.d.ts +0 -163
  153. package/dist/test-runner/report.d.ts.map +0 -1
  154. package/dist/test-runner/report.js +0 -198
  155. package/dist/test-runner/report.js.map +0 -1
  156. package/dist/test-runner/rpc.d.ts +0 -56
  157. package/dist/test-runner/rpc.d.ts.map +0 -1
  158. package/dist/test-runner/rpc.js +0 -98
  159. package/dist/test-runner/rpc.js.map +0 -1
  160. package/dist/test-runner/runtime.d.ts +0 -2
  161. package/dist/test-runner/runtime.js +0 -659
  162. package/dist/test-runner/runtime.js.map +0 -1
  163. package/dist/test-runner/task-graph.d.ts +0 -38
  164. package/dist/test-runner/task-graph.d.ts.map +0 -1
  165. package/dist/test-runner/task-graph.js +0 -182
  166. package/dist/test-runner/task-graph.js.map +0 -1
  167. package/dist/throttle-DKKzX1qC.js +0 -59
  168. package/dist/throttle-DKKzX1qC.js.map +0 -1
  169. package/dist/totp-BqmCLSNA.js +0 -189
  170. package/dist/totp-BqmCLSNA.js.map +0 -1
  171. package/dist/totp-CMHR5lsW.cjs +0 -191
  172. package/dist/totp-CMHR5lsW.cjs.map +0 -1
  173. package/dist/totp-CZLLKfOC.js +0 -200
  174. package/dist/totp-CZLLKfOC.js.map +0 -1
  175. package/dist/totp-DAxys-r0.js +0 -199
  176. package/dist/totp-DAxys-r0.js.map +0 -1
  177. package/dist/totp-DfekTBk3.js +0 -211
  178. package/dist/totp-DfekTBk3.js.map +0 -1
  179. package/dist/totp-Dwft0Kz7.js +0 -3
  180. package/dist/totp-WY6l0ysP.js +0 -190
  181. package/dist/totp-WY6l0ysP.js.map +0 -1
  182. /package/dist/{test-runner/bin.d.ts → stubs/bin-devtools-mcp.d.ts} +0 -0
@@ -1,198 +0,0 @@
1
- import { mkdir, writeFile } from "node:fs/promises";
2
- import path from "node:path";
3
- //#region src/test-runner/report.ts
4
- /**
5
- * Runner-agnostic report serialisation for env3 test runs (devtools#696).
6
- *
7
- * Both env3 execution paths — the Vitest custom pool (`pool.ts`) and the
8
- * standalone `devtools-test` CLI (`cli.ts`) — call the same core
9
- * `runTestFilesOverRelay` and so produce the same {@link RelayRunReport}. This
10
- * module is the single, runner-neutral place that turns that in-memory report
11
- * into a stable on-disk artifact so a 2.x run and a 3.0 run can be diffed
12
- * cell-by-cell after the fact.
13
- *
14
- * The serialised schema is deliberately MINIMAL and secret-free:
15
- *
16
- * - file paths are stored RELATIVE to `projectRoot` (no absolute `/Users/...`
17
- * leakage — see {@link RunnerAgnosticReport.files});
18
- * - the cell metadata (sdkLine/platform) is baked INTO the body, not only the
19
- * filename, so a moved artifact never loses its provenance;
20
- * - NO relay wss / scheme / TOTP / relayUrl fields exist in the schema at all
21
- * (enforced by the type + this comment) — error strings are the matcher
22
- * message only, inherited from rpc.ts which already strips expression/value.
23
- *
24
- * react-free — depends only on the type-level `RelayRunReport` and `node:fs` /
25
- * `node:path`. Safe to bundle without pulling the chii/cloudflared graph.
26
- */
27
- /**
28
- * Converts an absolute (or already-relative) file path to a projectRoot-relative
29
- * one. `path.relative` returns `''` when the paths are equal — guard that to the
30
- * basename so the field is never empty.
31
- *
32
- * SECRET-HANDLING: this is the single choke point that strips absolute project
33
- * paths from the artifact.
34
- */
35
- function relativise(projectRoot, file) {
36
- const rel = path.relative(projectRoot, file);
37
- if (rel === "" || rel.startsWith("..") || path.isAbsolute(rel)) return path.basename(file);
38
- return rel;
39
- }
40
- /**
41
- * Serialises a {@link RelayRunReport} into the runner-agnostic, secret-free
42
- * on-disk shape. Pure — no IO; testable with a plain report + meta.
43
- *
44
- * @param report - The core relay run report.
45
- * @param meta - Cell axes + projectRoot (projectRoot is consumed, not stored).
46
- */
47
- function serializeRelayReport(report, meta) {
48
- return {
49
- cell: {
50
- sdkLine: meta.sdkLine,
51
- platform: meta.platform
52
- },
53
- startedAt: report.startedAt,
54
- duration: report.duration,
55
- totals: report.totals,
56
- ...report.preflight ? { preflight: report.preflight } : {},
57
- files: report.files.map((f) => {
58
- const file = relativise(meta.projectRoot, f.file);
59
- const modeField = f.mode === "manual" || f.mode === "stubbed" ? { mode: f.mode } : {};
60
- if ("error" in f.result) return {
61
- file,
62
- error: f.result.error,
63
- ...modeField
64
- };
65
- return {
66
- file,
67
- duration: f.result.duration,
68
- passed: f.result.passed,
69
- failed: f.result.failed,
70
- skipped: f.result.skipped,
71
- tests: f.result.tests,
72
- ...modeField
73
- };
74
- })
75
- };
76
- }
77
- /**
78
- * Writes the serialised report to `<dir>/<sdkLine>.<platform>.json`, creating
79
- * `dir` if needed. Returns the absolute path(s) written.
80
- *
81
- * The cell-suffixed filename keeps 2.x and 3.0 (and per-platform) runs as
82
- * distinct artifacts in the same directory; the same cell metadata is also baked
83
- * into the body so a renamed/moved file still carries its provenance.
84
- *
85
- * Manual-run provenance (devtools#741): when `report.files` contains ANY
86
- * `mode: 'manual'` entry (i.e. this run included `--manual-blocking` files),
87
- * the manual-tagged files are written to a SEPARATE
88
- * `<dir>/<sdkLine>.<platform>.manual.json` artifact instead of the standard
89
- * one — the standard `<sdkLine>.<platform>.json` filename is reserved for the
90
- * regular (unattended) files only, so a manual run's presence never mutates
91
- * what the unattended-baseline filename means.
92
- *
93
- * Stub-blocking provenance (devtools#740, DT-2): `mode: 'stubbed'` files are
94
- * written to a THIRD, separate `<dir>/<sdkLine>.<platform>.stubbed.json`
95
- * artifact — never merged into the standard file (it is unattended, like the
96
- * standard file, so a naive filename split could conflate the two) and never
97
- * merged into `.manual.json` (it did NOT have a human present). Its body is
98
- * ALSO stamped `cell.bridgeStub: true` — the mandatory HYBRID-cell provenance
99
- * from the issue: a stubbed result must never be silently mixed into the
100
- * real-device baseline report.
101
- *
102
- * If ALL files in the run are regular, only the standard artifact is written
103
- * (today's behavior, unchanged). Any combination of the three modes present
104
- * in a single run writes ONLY the corresponding artifacts — each is additive,
105
- * never replacing another.
106
- *
107
- * SECRET-HANDLING: the written body contains no relay/secret fields (the schema
108
- * has none). `dir`/`projectRoot` are local filesystem paths, never logged here.
109
- *
110
- * @param report - The core relay run report.
111
- * @param dir - Output directory (created recursively if missing).
112
- * @param meta - Cell axes + projectRoot.
113
- * @returns The absolute path(s) written, in order: standard first (if any
114
- * regular files ran), then manual (if any manual files ran), then stubbed
115
- * (if any stubbed files ran). At least one path is always returned when
116
- * `report.files` is non-empty.
117
- */
118
- async function writeReportArtifact(report, dir, meta) {
119
- const serialised = serializeRelayReport(report, meta);
120
- await mkdir(dir, { recursive: true });
121
- const regularFiles = serialised.files.filter((f) => f.mode === void 0);
122
- const manualFiles = serialised.files.filter((f) => f.mode === "manual");
123
- const stubbedFiles = serialised.files.filter((f) => f.mode === "stubbed");
124
- const written = [];
125
- if (regularFiles.length > 0 || serialised.files.length === 0) {
126
- const outFile = path.join(dir, `${meta.sdkLine}.${meta.platform}.json`);
127
- await writeFile(outFile, `${JSON.stringify({
128
- ...serialised,
129
- files: regularFiles
130
- }, null, 2)}\n`, "utf8");
131
- written.push(outFile);
132
- }
133
- if (manualFiles.length > 0) {
134
- const manualOutFile = path.join(dir, `${meta.sdkLine}.${meta.platform}.manual.json`);
135
- await writeFile(manualOutFile, `${JSON.stringify({
136
- ...serialised,
137
- files: manualFiles
138
- }, null, 2)}\n`, "utf8");
139
- written.push(manualOutFile);
140
- }
141
- if (stubbedFiles.length > 0) {
142
- const stubbedOutFile = path.join(dir, `${meta.sdkLine}.${meta.platform}.stubbed.json`);
143
- await writeFile(stubbedOutFile, `${JSON.stringify({
144
- ...serialised,
145
- cell: {
146
- ...serialised.cell,
147
- bridgeStub: true
148
- },
149
- files: stubbedFiles
150
- }, null, 2)}\n`, "utf8");
151
- written.push(stubbedOutFile);
152
- }
153
- return written;
154
- }
155
- /**
156
- * Writes harvested `__AIT_CAPTURE__` lines to per-category files under `dir`,
157
- * named `<category>.<sdkLine>.<platform>.json` — the SAME convention
158
- * sdk-example's env1 `flushCapture` uses on the filesystem, so env1 and env3
159
- * capture artifacts line up for diffing.
160
- *
161
- * Each line's `json` payload is an opaque JSON array of capture records. Lines
162
- * sharing a category are concatenated into one array, in harvest order.
163
- *
164
- * SECRET-HANDLING: only allowlist-prefixed capture lines reach here (the parser
165
- * dropped wss/scheme noise); the `json` payload is written verbatim but is a
166
- * capture record array, not a relay/secret.
167
- *
168
- * @param captures - Parsed capture lines (from `RelayRunReport.captures`).
169
- * @param dir - Output directory (created recursively if missing).
170
- * @param cell - Cell axes for the filename suffix.
171
- * @returns The absolute paths written (one per category), in category order.
172
- */
173
- async function writeCaptureArtifacts(captures, dir, cell) {
174
- if (captures.length === 0) return [];
175
- const byCategory = /* @__PURE__ */ new Map();
176
- for (const { category, json } of captures) {
177
- let merged = byCategory.get(category);
178
- if (!merged) {
179
- merged = [];
180
- byCategory.set(category, merged);
181
- }
182
- const parsed = JSON.parse(json);
183
- if (Array.isArray(parsed)) merged.push(...parsed);
184
- else merged.push(parsed);
185
- }
186
- await mkdir(dir, { recursive: true });
187
- const written = [];
188
- for (const [category, records] of byCategory) {
189
- const outFile = path.join(dir, `${category}.${cell.sdkLine}.${cell.platform}.json`);
190
- await writeFile(outFile, `${JSON.stringify(records, null, 2)}\n`, "utf8");
191
- written.push(outFile);
192
- }
193
- return written;
194
- }
195
- //#endregion
196
- export { serializeRelayReport, writeCaptureArtifacts, writeReportArtifact };
197
-
198
- //# sourceMappingURL=report.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"report.js","names":[],"sources":["../../src/test-runner/report.ts"],"sourcesContent":["/**\n * Runner-agnostic report serialisation for env3 test runs (devtools#696).\n *\n * Both env3 execution paths — the Vitest custom pool (`pool.ts`) and the\n * standalone `devtools-test` CLI (`cli.ts`) — call the same core\n * `runTestFilesOverRelay` and so produce the same {@link RelayRunReport}. This\n * module is the single, runner-neutral place that turns that in-memory report\n * into a stable on-disk artifact so a 2.x run and a 3.0 run can be diffed\n * cell-by-cell after the fact.\n *\n * The serialised schema is deliberately MINIMAL and secret-free:\n *\n * - file paths are stored RELATIVE to `projectRoot` (no absolute `/Users/...`\n * leakage — see {@link RunnerAgnosticReport.files});\n * - the cell metadata (sdkLine/platform) is baked INTO the body, not only the\n * filename, so a moved artifact never loses its provenance;\n * - NO relay wss / scheme / TOTP / relayUrl fields exist in the schema at all\n * (enforced by the type + this comment) — error strings are the matcher\n * message only, inherited from rpc.ts which already strips expression/value.\n *\n * react-free — depends only on the type-level `RelayRunReport` and `node:fs` /\n * `node:path`. Safe to bundle without pulling the chii/cloudflared graph.\n */\n\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport type { AitCaptureLine } from './capture.js';\nimport type { RelayRunReport } from './relay-worker.js';\nimport type { TestResult } from './runtime.js';\n\n/** The cell axes a report is stamped with — the test-matrix coordinates. */\nexport interface ReportCellMeta {\n /** SDK line under test (e.g. `'2.x'` / `'3.x'`). */\n sdkLine: string;\n /** Platform under test (e.g. `'ios'` / `'android'` / `'mock'`). */\n platform: string;\n /**\n * Project root the run was launched from. Used ONLY to relativise file paths\n * out of the serialised report — never stored in the output. SECRET-HANDLING:\n * absolute project paths must not leak into artifacts.\n */\n projectRoot: string;\n}\n\n/** Per-file slice of a {@link RunnerAgnosticReport}. */\nexport interface RunnerAgnosticFileReport {\n /**\n * Test file path, RELATIVE to `projectRoot`. Never absolute — `path.relative`\n * strips the machine-specific prefix so the artifact is portable and leaks no\n * local filesystem layout.\n */\n file: string;\n /** Whole-file bundle/inject error (matcher message only), when the file failed. */\n error?: string;\n /** In-page run duration (ms) for this file, when it ran. */\n duration?: number;\n passed?: number;\n failed?: number;\n skipped?: number;\n /** Per-test results, when the file ran. Error strings are matcher messages only. */\n tests?: TestResult[];\n /**\n * Present only when this file ran under `--manual-blocking` or\n * `--stub-blocking`. Absent (never `false`) for unattended files:\n * absence-means-unattended is the contract, mirroring `FileResult.mode` in\n * relay-worker.ts.\n *\n * - `'manual'` — devtools#741, human-attended, real native envelopes.\n * - `'stubbed'` — devtools#740 (DT-2), unattended, blocking-UI calls\n * answered from fixtures. A manual/stubbed-stamped record must never be\n * diffed against an unattended (or each other's) baseline as if\n * equivalent.\n */\n mode?: 'manual' | 'stubbed';\n}\n\n/**\n * The runner-neutral, secret-free report written to disk. There are\n * intentionally NO wss/scheme/TOTP/relayUrl fields on this type — the absence is\n * load-bearing (SECRET-HANDLING) and must not be \"completed\" by a future edit.\n */\nexport interface RunnerAgnosticReport {\n /**\n * Cell axes this run belongs to — baked into the body for portability.\n * `bridgeStub` is present + `true` ONLY on the artifact written for\n * stub-blocking files (devtools#740, DT-2) — see `writeReportArtifact`'s\n * doc for why this is a SEPARATE `.stubbed.json` artifact rather than a\n * flag inside the standard/`.manual.json` report. Absent (never `false`)\n * on every other artifact — this is the mandatory HYBRID-cell provenance\n * stamp the issue calls for: a stubbed run must never be silently mixed\n * into the real-device baseline report.\n */\n cell: { sdkLine: string; platform: string; bridgeStub?: true };\n /** ISO timestamp of when the run started (from the core report). */\n startedAt: string;\n /** Total wall-clock ms (bundling + sequential injection). */\n duration: number;\n /** Flattened totals across all files. */\n totals: RelayRunReport['totals'];\n /** Per-file results with projectRoot-relative paths. */\n files: RunnerAgnosticFileReport[];\n /**\n * Permission-state preflight result (devtools#739), mirrored verbatim from\n * `RelayRunReport.preflight` — see that field's docblock. Absent when the\n * preflight did not complete (non-fatal; the run still succeeds). No\n * secrets — permission-state strings only.\n */\n preflight?: { permissions: Record<string, string> };\n}\n\n/**\n * Converts an absolute (or already-relative) file path to a projectRoot-relative\n * one. `path.relative` returns `''` when the paths are equal — guard that to the\n * basename so the field is never empty.\n *\n * SECRET-HANDLING: this is the single choke point that strips absolute project\n * paths from the artifact.\n */\nfunction relativise(projectRoot: string, file: string): string {\n const rel = path.relative(projectRoot, file);\n if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel)) {\n // Outside the project root (or equal) — fall back to the basename rather\n // than emitting an absolute or `../../..` traversal path.\n return path.basename(file);\n }\n return rel;\n}\n\n/**\n * Serialises a {@link RelayRunReport} into the runner-agnostic, secret-free\n * on-disk shape. Pure — no IO; testable with a plain report + meta.\n *\n * @param report - The core relay run report.\n * @param meta - Cell axes + projectRoot (projectRoot is consumed, not stored).\n */\nexport function serializeRelayReport(\n report: RelayRunReport,\n meta: ReportCellMeta,\n): RunnerAgnosticReport {\n return {\n cell: { sdkLine: meta.sdkLine, platform: meta.platform },\n startedAt: report.startedAt,\n duration: report.duration,\n totals: report.totals,\n ...(report.preflight ? { preflight: report.preflight } : {}),\n files: report.files.map((f): RunnerAgnosticFileReport => {\n const file = relativise(meta.projectRoot, f.file);\n const modeField =\n f.mode === 'manual' || f.mode === 'stubbed' ? ({ mode: f.mode } as const) : {};\n if ('error' in f.result) {\n // Matcher/inject error message only — rpc.ts already stripped the\n // expression/value upstream.\n return { file, error: f.result.error, ...modeField };\n }\n return {\n file,\n duration: f.result.duration,\n passed: f.result.passed,\n failed: f.result.failed,\n skipped: f.result.skipped,\n tests: f.result.tests,\n ...modeField,\n };\n }),\n };\n}\n\n/**\n * Writes the serialised report to `<dir>/<sdkLine>.<platform>.json`, creating\n * `dir` if needed. Returns the absolute path(s) written.\n *\n * The cell-suffixed filename keeps 2.x and 3.0 (and per-platform) runs as\n * distinct artifacts in the same directory; the same cell metadata is also baked\n * into the body so a renamed/moved file still carries its provenance.\n *\n * Manual-run provenance (devtools#741): when `report.files` contains ANY\n * `mode: 'manual'` entry (i.e. this run included `--manual-blocking` files),\n * the manual-tagged files are written to a SEPARATE\n * `<dir>/<sdkLine>.<platform>.manual.json` artifact instead of the standard\n * one — the standard `<sdkLine>.<platform>.json` filename is reserved for the\n * regular (unattended) files only, so a manual run's presence never mutates\n * what the unattended-baseline filename means.\n *\n * Stub-blocking provenance (devtools#740, DT-2): `mode: 'stubbed'` files are\n * written to a THIRD, separate `<dir>/<sdkLine>.<platform>.stubbed.json`\n * artifact — never merged into the standard file (it is unattended, like the\n * standard file, so a naive filename split could conflate the two) and never\n * merged into `.manual.json` (it did NOT have a human present). Its body is\n * ALSO stamped `cell.bridgeStub: true` — the mandatory HYBRID-cell provenance\n * from the issue: a stubbed result must never be silently mixed into the\n * real-device baseline report.\n *\n * If ALL files in the run are regular, only the standard artifact is written\n * (today's behavior, unchanged). Any combination of the three modes present\n * in a single run writes ONLY the corresponding artifacts — each is additive,\n * never replacing another.\n *\n * SECRET-HANDLING: the written body contains no relay/secret fields (the schema\n * has none). `dir`/`projectRoot` are local filesystem paths, never logged here.\n *\n * @param report - The core relay run report.\n * @param dir - Output directory (created recursively if missing).\n * @param meta - Cell axes + projectRoot.\n * @returns The absolute path(s) written, in order: standard first (if any\n * regular files ran), then manual (if any manual files ran), then stubbed\n * (if any stubbed files ran). At least one path is always returned when\n * `report.files` is non-empty.\n */\nexport async function writeReportArtifact(\n report: RelayRunReport,\n dir: string,\n meta: ReportCellMeta,\n): Promise<string[]> {\n const serialised = serializeRelayReport(report, meta);\n await mkdir(dir, { recursive: true });\n\n const regularFiles = serialised.files.filter((f) => f.mode === undefined);\n const manualFiles = serialised.files.filter((f) => f.mode === 'manual');\n const stubbedFiles = serialised.files.filter((f) => f.mode === 'stubbed');\n\n const written: string[] = [];\n\n // Standard artifact: written whenever there are regular files, OR when the\n // whole run is empty (preserves the pre-#741 \"always write one file\"\n // behavior for a run with zero files, e.g. an empty glob match).\n if (regularFiles.length > 0 || serialised.files.length === 0) {\n const outFile = path.join(dir, `${meta.sdkLine}.${meta.platform}.json`);\n await writeFile(\n outFile,\n `${JSON.stringify({ ...serialised, files: regularFiles }, null, 2)}\\n`,\n 'utf8',\n );\n written.push(outFile);\n }\n\n if (manualFiles.length > 0) {\n const manualOutFile = path.join(dir, `${meta.sdkLine}.${meta.platform}.manual.json`);\n await writeFile(\n manualOutFile,\n `${JSON.stringify({ ...serialised, files: manualFiles }, null, 2)}\\n`,\n 'utf8',\n );\n written.push(manualOutFile);\n }\n\n if (stubbedFiles.length > 0) {\n const stubbedOutFile = path.join(dir, `${meta.sdkLine}.${meta.platform}.stubbed.json`);\n await writeFile(\n stubbedOutFile,\n `${JSON.stringify(\n {\n ...serialised,\n cell: { ...serialised.cell, bridgeStub: true as const },\n files: stubbedFiles,\n },\n null,\n 2,\n )}\\n`,\n 'utf8',\n );\n written.push(stubbedOutFile);\n }\n\n return written;\n}\n\n/**\n * Writes harvested `__AIT_CAPTURE__` lines to per-category files under `dir`,\n * named `<category>.<sdkLine>.<platform>.json` — the SAME convention\n * sdk-example's env1 `flushCapture` uses on the filesystem, so env1 and env3\n * capture artifacts line up for diffing.\n *\n * Each line's `json` payload is an opaque JSON array of capture records. Lines\n * sharing a category are concatenated into one array, in harvest order.\n *\n * SECRET-HANDLING: only allowlist-prefixed capture lines reach here (the parser\n * dropped wss/scheme noise); the `json` payload is written verbatim but is a\n * capture record array, not a relay/secret.\n *\n * @param captures - Parsed capture lines (from `RelayRunReport.captures`).\n * @param dir - Output directory (created recursively if missing).\n * @param cell - Cell axes for the filename suffix.\n * @returns The absolute paths written (one per category), in category order.\n */\nexport async function writeCaptureArtifacts(\n captures: ReadonlyArray<AitCaptureLine>,\n dir: string,\n cell: { sdkLine: string; platform: string },\n): Promise<string[]> {\n if (captures.length === 0) return [];\n\n // Group payloads by category, preserving harvest order. Each payload is an\n // opaque JSON array string; concatenate parsed arrays under the same category.\n const byCategory = new Map<string, unknown[]>();\n for (const { category, json } of captures) {\n let merged = byCategory.get(category);\n if (!merged) {\n merged = [];\n byCategory.set(category, merged);\n }\n // The parser already validated `json` parses; an array payload is expected.\n const parsed = JSON.parse(json) as unknown;\n if (Array.isArray(parsed)) {\n merged.push(...parsed);\n } else {\n merged.push(parsed);\n }\n }\n\n await mkdir(dir, { recursive: true });\n const written: string[] = [];\n for (const [category, records] of byCategory) {\n const outFile = path.join(dir, `${category}.${cell.sdkLine}.${cell.platform}.json`);\n await writeFile(outFile, `${JSON.stringify(records, null, 2)}\\n`, 'utf8');\n written.push(outFile);\n }\n return written;\n}\n"],"mappings":"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"}
@@ -1,56 +0,0 @@
1
- import { t as CdpConnection } from "../cdp-connection-rP1WdnH5.js";
2
- import { t as RunReport } from "../runtime-kn9DxOeg.js";
3
-
4
- //#region src/test-runner/rpc.d.ts
5
- /**
6
- * Wraps bundle code in a self-executing IIFE that:
7
- * 1. Evaluates the bundle (registering describe/it/test).
8
- * 2. Calls `__testBundle.runTestModule(...)` — the entry the runtime exports.
9
- * 3. Returns a JSON-serialised `RunReport` string.
10
- *
11
- * The double-serialisation (RunReport → JSON string → returnByValue string)
12
- * is intentional: CDP `returnByValue` reliably transports strings; deeply
13
- * nested objects can lose fidelity across the Chii relay.
14
- *
15
- * SECRET-HANDLING: `bundleCode` MUST NOT be logged by callers.
16
- */
17
- declare function buildRunTestsExpression(bundleCode: string): string;
18
- /**
19
- * Result of `injectAndRunBundle`.
20
- */
21
- type RpcRunResult = {
22
- ok: true;
23
- report: RunReport;
24
- } | {
25
- ok: false;
26
- error: string;
27
- };
28
- /**
29
- * Parses the raw CDP `returnByValue` result from a `buildRunTestsExpression`
30
- * evaluate call into a typed `RpcRunResult`.
31
- *
32
- * Throws only on parse failure — an `ok:false` envelope is a normal result.
33
- *
34
- * SECRET-HANDLING: `rawValue` is not included in error messages.
35
- */
36
- declare function parseRunTestsResult(rawValue: unknown): RpcRunResult;
37
- /**
38
- * Injects `bundleCode` into the attached page and awaits test execution.
39
- *
40
- * Uses `Runtime.evaluate` with `awaitPromise: true` to wait for the
41
- * async IIFE to settle. `timeoutMs` (default 60s) is the authoritative file
42
- * timeout — this function races it against the evaluate call AND (devtools#747)
43
- * threads it down to the underlying `CdpConnection`'s own per-command watchdog
44
- * (plus a small margin) so that watchdog can never undercut this race. Split
45
- * into smaller files if you hit the timeout.
46
- *
47
- * @param connection - Active CDP connection (relay or local).
48
- * @param bundleCode - IIFE bundle string from `bundleTestFile`.
49
- * @param timeoutMs - Override the default 60 s timeout.
50
- *
51
- * SECRET-HANDLING: `bundleCode` and the raw CDP result value are never logged.
52
- */
53
- declare function injectAndRunBundle(connection: CdpConnection, bundleCode: string, timeoutMs?: number): Promise<RpcRunResult>;
54
- //#endregion
55
- export { RpcRunResult, buildRunTestsExpression, injectAndRunBundle, parseRunTestsResult };
56
- //# sourceMappingURL=rpc.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"rpc.d.ts","names":[],"sources":["../../src/test-runner/rpc.ts"],"mappings":";;;;;;;;;;;;;AA4EA;;;iBAvCgB,uBAAA,CAAwB,UAAA;;AAoFxC;;KAvDY,YAAA;EAAiB,EAAA;EAAU,MAAA,EAAQ,SAAA;AAAA;EAAgB,EAAA;EAAW,KAAA;AAAA;;;;;;;;;iBAU1D,mBAAA,CAAoB,QAAA,YAAoB,YAAA;;;;;;;;;;;;;;;;;iBA6ClC,kBAAA,CACpB,UAAA,EAAY,aAAA,EACZ,UAAA,UACA,SAAA,YACC,OAAA,CAAQ,YAAA"}
@@ -1,98 +0,0 @@
1
- //#region src/test-runner/rpc.ts
2
- /** Maximum milliseconds to wait for a single evaluate round-trip. */
3
- const DEFAULT_TIMEOUT_MS = 6e4;
4
- /**
5
- * Extra headroom (ms) added on top of the rpc-level `timeoutMs` when telling
6
- * the underlying `CdpConnection` its per-command watchdog budget (devtools#747).
7
- * The rpc-level race above is the authoritative file timeout — this margin
8
- * only ensures the connection's own (shorter-by-default) watchdog never fires
9
- * first and masks the rpc-level timeout error with a connection-level one.
10
- */
11
- const CONNECTION_TIMEOUT_MARGIN_MS = 5e3;
12
- /**
13
- * Wraps bundle code in a self-executing IIFE that:
14
- * 1. Evaluates the bundle (registering describe/it/test).
15
- * 2. Calls `__testBundle.runTestModule(...)` — the entry the runtime exports.
16
- * 3. Returns a JSON-serialised `RunReport` string.
17
- *
18
- * The double-serialisation (RunReport → JSON string → returnByValue string)
19
- * is intentional: CDP `returnByValue` reliably transports strings; deeply
20
- * nested objects can lose fidelity across the Chii relay.
21
- *
22
- * SECRET-HANDLING: `bundleCode` MUST NOT be logged by callers.
23
- */
24
- function buildRunTestsExpression(bundleCode) {
25
- return `(async () => { try { ${bundleCode} } catch(e) { return JSON.stringify({ok:false,error:'bundle-eval: ' + String(e && e.message || e)}); } if (typeof globalThis.__testBundle !== 'object' || typeof globalThis.__testBundle.runTestModule !== 'function' || typeof globalThis.__testBundle.__userFactory !== 'function') { return JSON.stringify({ok:false,error:'bundle-missing-export: __testBundle.runTestModule or __userFactory is not a function'}); } try { const report = await globalThis.__testBundle.runTestModule(globalThis.__testBundle.__userFactory); return JSON.stringify({ok:true,value:report}); } catch(e) { return JSON.stringify({ok:false,error:'test-run: ' + String(e && e.message || e)}); }})()`;
26
- }
27
- /**
28
- * Parses the raw CDP `returnByValue` result from a `buildRunTestsExpression`
29
- * evaluate call into a typed `RpcRunResult`.
30
- *
31
- * Throws only on parse failure — an `ok:false` envelope is a normal result.
32
- *
33
- * SECRET-HANDLING: `rawValue` is not included in error messages.
34
- */
35
- function parseRunTestsResult(rawValue) {
36
- if (typeof rawValue !== "string") throw new Error(`rpc.parseRunTestsResult: unexpected return type "${typeof rawValue}" — expected JSON string`);
37
- let parsed;
38
- try {
39
- parsed = JSON.parse(rawValue);
40
- } catch {
41
- throw new Error("rpc.parseRunTestsResult: bridge returned non-JSON string");
42
- }
43
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("rpc.parseRunTestsResult: parsed result is not an object");
44
- const obj = parsed;
45
- if (obj.ok === true) return {
46
- ok: true,
47
- report: obj.value
48
- };
49
- if (obj.ok === false) return {
50
- ok: false,
51
- error: typeof obj.error === "string" ? obj.error : String(obj.error)
52
- };
53
- throw new Error("rpc.parseRunTestsResult: result missing \"ok\" field");
54
- }
55
- /**
56
- * Injects `bundleCode` into the attached page and awaits test execution.
57
- *
58
- * Uses `Runtime.evaluate` with `awaitPromise: true` to wait for the
59
- * async IIFE to settle. `timeoutMs` (default 60s) is the authoritative file
60
- * timeout — this function races it against the evaluate call AND (devtools#747)
61
- * threads it down to the underlying `CdpConnection`'s own per-command watchdog
62
- * (plus a small margin) so that watchdog can never undercut this race. Split
63
- * into smaller files if you hit the timeout.
64
- *
65
- * @param connection - Active CDP connection (relay or local).
66
- * @param bundleCode - IIFE bundle string from `bundleTestFile`.
67
- * @param timeoutMs - Override the default 60 s timeout.
68
- *
69
- * SECRET-HANDLING: `bundleCode` and the raw CDP result value are never logged.
70
- */
71
- async function injectAndRunBundle(connection, bundleCode, timeoutMs = DEFAULT_TIMEOUT_MS) {
72
- const expression = buildRunTestsExpression(bundleCode);
73
- const TIMEOUT_SENTINEL = Symbol("timeout");
74
- const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve(TIMEOUT_SENTINEL), timeoutMs));
75
- const evalPromise = connection.send("Runtime.evaluate", {
76
- expression,
77
- returnByValue: true,
78
- awaitPromise: true
79
- }, { timeoutMs: timeoutMs + CONNECTION_TIMEOUT_MARGIN_MS });
80
- const raceResult = await Promise.race([evalPromise.then((v) => ({
81
- tag: "eval",
82
- v
83
- })), timeoutPromise.then(() => ({ tag: "timeout" }))]);
84
- if (raceResult.tag === "timeout") return {
85
- ok: false,
86
- error: `rpc: evaluate timed out after ${timeoutMs}ms`
87
- };
88
- const cdpResult = raceResult.v;
89
- if (cdpResult.exceptionDetails) {
90
- const msg = cdpResult.exceptionDetails.exception?.description ?? cdpResult.exceptionDetails.text ?? "Runtime.evaluate threw an exception";
91
- throw new Error(`rpc.injectAndRunBundle: ${msg}`);
92
- }
93
- return parseRunTestsResult(cdpResult.result.value);
94
- }
95
- //#endregion
96
- export { buildRunTestsExpression, injectAndRunBundle, parseRunTestsResult };
97
-
98
- //# sourceMappingURL=rpc.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"rpc.js","names":[],"sources":["../../src/test-runner/rpc.ts"],"sourcesContent":["/**\n * Node-side RPC helpers for injecting and collecting test execution over CDP.\n *\n * Uses the same IIFE + JSON.stringify envelope pattern as `buildCallSdkExpression`\n * in `src/mcp/tools.ts` to reliably shuttle structured results through\n * `Runtime.evaluate`'s `returnByValue: true` boundary.\n *\n * SECRET-HANDLING: bundle code, relay URLs, and result values are NOT logged.\n */\n\nimport type { CdpConnection } from '../mcp/cdp-connection.js';\nimport type { RunReport } from './runtime.js';\n\n/** Maximum milliseconds to wait for a single evaluate round-trip. */\nconst DEFAULT_TIMEOUT_MS = 60_000;\n\n/**\n * Extra headroom (ms) added on top of the rpc-level `timeoutMs` when telling\n * the underlying `CdpConnection` its per-command watchdog budget (devtools#747).\n * The rpc-level race above is the authoritative file timeout — this margin\n * only ensures the connection's own (shorter-by-default) watchdog never fires\n * first and masks the rpc-level timeout error with a connection-level one.\n */\nconst CONNECTION_TIMEOUT_MARGIN_MS = 5_000;\n\n/**\n * Wraps bundle code in a self-executing IIFE that:\n * 1. Evaluates the bundle (registering describe/it/test).\n * 2. Calls `__testBundle.runTestModule(...)` — the entry the runtime exports.\n * 3. Returns a JSON-serialised `RunReport` string.\n *\n * The double-serialisation (RunReport → JSON string → returnByValue string)\n * is intentional: CDP `returnByValue` reliably transports strings; deeply\n * nested objects can lose fidelity across the Chii relay.\n *\n * SECRET-HANDLING: `bundleCode` MUST NOT be logged by callers.\n */\nexport function buildRunTestsExpression(bundleCode: string): string {\n // We trust bundleCode is already a self-contained IIFE that installs\n // `window.__testBundle` (or `globalThis.__testBundle`).\n // We then call `__testBundle.runTestModule()` and return a JSON string.\n return (\n `(async () => {` +\n // Step 1: evaluate the bundle to register tests\n ` try { ${bundleCode} } catch(e) {` +\n ` return JSON.stringify({ok:false,error:'bundle-eval: ' + String(e && e.message || e)});` +\n ` }` +\n // Step 2: check that the expected exports are present\n ` if (typeof globalThis.__testBundle !== 'object' || typeof globalThis.__testBundle.runTestModule !== 'function' || typeof globalThis.__testBundle.__userFactory !== 'function') {` +\n ` return JSON.stringify({ok:false,error:'bundle-missing-export: __testBundle.runTestModule or __userFactory is not a function'});` +\n ` }` +\n // Step 3: run tests — pass the factory so runTestModule installs globals\n // first, then invokes the factory to register describe/it/test blocks.\n ` try {` +\n ` const report = await globalThis.__testBundle.runTestModule(globalThis.__testBundle.__userFactory);` +\n ` return JSON.stringify({ok:true,value:report});` +\n ` } catch(e) {` +\n ` return JSON.stringify({ok:false,error:'test-run: ' + String(e && e.message || e)});` +\n ` }` +\n `})()`\n );\n}\n\n/**\n * Result of `injectAndRunBundle`.\n */\nexport type RpcRunResult = { ok: true; report: RunReport } | { ok: false; error: string };\n\n/**\n * Parses the raw CDP `returnByValue` result from a `buildRunTestsExpression`\n * evaluate call into a typed `RpcRunResult`.\n *\n * Throws only on parse failure — an `ok:false` envelope is a normal result.\n *\n * SECRET-HANDLING: `rawValue` is not included in error messages.\n */\nexport function parseRunTestsResult(rawValue: unknown): RpcRunResult {\n if (typeof rawValue !== 'string') {\n throw new Error(\n `rpc.parseRunTestsResult: unexpected return type \"${typeof rawValue}\" — expected JSON string`,\n );\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(rawValue);\n } catch {\n // Do NOT include rawValue — could contain secrets.\n throw new Error('rpc.parseRunTestsResult: bridge returned non-JSON string');\n }\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new Error('rpc.parseRunTestsResult: parsed result is not an object');\n }\n const obj = parsed as Record<string, unknown>;\n if (obj.ok === true) {\n return { ok: true, report: obj.value as RunReport };\n }\n if (obj.ok === false) {\n return {\n ok: false,\n error: typeof obj.error === 'string' ? obj.error : String(obj.error),\n };\n }\n throw new Error('rpc.parseRunTestsResult: result missing \"ok\" field');\n}\n\n/**\n * Injects `bundleCode` into the attached page and awaits test execution.\n *\n * Uses `Runtime.evaluate` with `awaitPromise: true` to wait for the\n * async IIFE to settle. `timeoutMs` (default 60s) is the authoritative file\n * timeout — this function races it against the evaluate call AND (devtools#747)\n * threads it down to the underlying `CdpConnection`'s own per-command watchdog\n * (plus a small margin) so that watchdog can never undercut this race. Split\n * into smaller files if you hit the timeout.\n *\n * @param connection - Active CDP connection (relay or local).\n * @param bundleCode - IIFE bundle string from `bundleTestFile`.\n * @param timeoutMs - Override the default 60 s timeout.\n *\n * SECRET-HANDLING: `bundleCode` and the raw CDP result value are never logged.\n */\nexport async function injectAndRunBundle(\n connection: CdpConnection,\n bundleCode: string,\n timeoutMs = DEFAULT_TIMEOUT_MS,\n): Promise<RpcRunResult> {\n const expression = buildRunTestsExpression(bundleCode);\n\n // Use a tagged race so we can distinguish \"timeout won\" from \"evaluate won\"\n // without relying on exception identity. The timeout arm RETURNS a typed\n // RpcRunResult (ok:false + EVALUATE_TIMEOUT_MARKER) so the caller (relay-\n // worker's attempt()) can detect it via `.error.includes(EVALUATE_TIMEOUT_MARKER)`\n // and trigger the one-retry path.\n //\n // Genuine CDP `exceptionDetails` (engine exceptions thrown inside the page)\n // still THROW — those are deterministic failures that a retry cannot fix and\n // relay-worker's catch block finalises them correctly.\n //\n // DO NOT change the literal string below — relay-worker.ts imports\n // EVALUATE_TIMEOUT_MARKER = 'rpc: evaluate timed out after' and uses\n // `.includes()` to recognise this exact prefix.\n const TIMEOUT_SENTINEL = Symbol('timeout');\n const timeoutPromise = new Promise<typeof TIMEOUT_SENTINEL>((resolve) =>\n setTimeout(() => resolve(TIMEOUT_SENTINEL), timeoutMs),\n );\n\n // devtools#747: thread our own timeout budget down to the connection's\n // per-command watchdog (ChiiCdpConnection's default is 30s and used to fire\n // before this rpc-level race no matter how large `timeoutMs` was). A small\n // margin keeps the connection watchdog from firing a beat before our own\n // race settles — the rpc-level race above remains the authoritative file\n // timeout; this override only ensures the connection layer never undercuts\n // it. `LocalCdpConnection` ignores the option (no watchdog of its own).\n const evalPromise = connection.send(\n 'Runtime.evaluate',\n {\n expression,\n returnByValue: true,\n awaitPromise: true,\n },\n { timeoutMs: timeoutMs + CONNECTION_TIMEOUT_MARGIN_MS },\n );\n\n const raceResult = await Promise.race([\n evalPromise.then((v) => ({ tag: 'eval' as const, v })),\n timeoutPromise.then(() => ({ tag: 'timeout' as const })),\n ]);\n\n if (raceResult.tag === 'timeout') {\n return { ok: false, error: `rpc: evaluate timed out after ${timeoutMs}ms` };\n }\n\n const cdpResult = raceResult.v;\n\n if (cdpResult.exceptionDetails) {\n // Surface only the engine error string — not the expression or value.\n const msg =\n cdpResult.exceptionDetails.exception?.description ??\n cdpResult.exceptionDetails.text ??\n 'Runtime.evaluate threw an exception';\n throw new Error(`rpc.injectAndRunBundle: ${msg}`);\n }\n\n return parseRunTestsResult(cdpResult.result.value);\n}\n"],"mappings":";;AAcA,MAAM,qBAAqB;;;;;;;;AAS3B,MAAM,+BAA+B;;;;;;;;;;;;;AAcrC,SAAgB,wBAAwB,YAA4B;AAIlE,QACE,yBAEW,WAAW;;;;;;;;;;AAgC1B,SAAgB,oBAAoB,UAAiC;AACnE,KAAI,OAAO,aAAa,SACtB,OAAM,IAAI,MACR,oDAAoD,OAAO,SAAS,0BACrE;CAEH,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,SAAS;SACvB;AAEN,QAAM,IAAI,MAAM,2DAA2D;;AAE7E,KAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,OAAO,CACxE,OAAM,IAAI,MAAM,0DAA0D;CAE5E,MAAM,MAAM;AACZ,KAAI,IAAI,OAAO,KACb,QAAO;EAAE,IAAI;EAAM,QAAQ,IAAI;EAAoB;AAErD,KAAI,IAAI,OAAO,MACb,QAAO;EACL,IAAI;EACJ,OAAO,OAAO,IAAI,UAAU,WAAW,IAAI,QAAQ,OAAO,IAAI,MAAM;EACrE;AAEH,OAAM,IAAI,MAAM,uDAAqD;;;;;;;;;;;;;;;;;;AAmBvE,eAAsB,mBACpB,YACA,YACA,YAAY,oBACW;CACvB,MAAM,aAAa,wBAAwB,WAAW;CAetD,MAAM,mBAAmB,OAAO,UAAU;CAC1C,MAAM,iBAAiB,IAAI,SAAkC,YAC3D,iBAAiB,QAAQ,iBAAiB,EAAE,UAAU,CACvD;CASD,MAAM,cAAc,WAAW,KAC7B,oBACA;EACE;EACA,eAAe;EACf,cAAc;EACf,EACD,EAAE,WAAW,YAAY,8BAA8B,CACxD;CAED,MAAM,aAAa,MAAM,QAAQ,KAAK,CACpC,YAAY,MAAM,OAAO;EAAE,KAAK;EAAiB;EAAG,EAAE,EACtD,eAAe,YAAY,EAAE,KAAK,WAAoB,EAAE,CACzD,CAAC;AAEF,KAAI,WAAW,QAAQ,UACrB,QAAO;EAAE,IAAI;EAAO,OAAO,iCAAiC,UAAU;EAAK;CAG7E,MAAM,YAAY,WAAW;AAE7B,KAAI,UAAU,kBAAkB;EAE9B,MAAM,MACJ,UAAU,iBAAiB,WAAW,eACtC,UAAU,iBAAiB,QAC3B;AACF,QAAM,IAAI,MAAM,2BAA2B,MAAM;;AAGnD,QAAO,oBAAoB,UAAU,OAAO,MAAM"}
@@ -1,2 +0,0 @@
1
- import { a as runTestModule, i as TestResult, n as RunTestModuleOptions, o as runtimeGlobals, r as TestContext, t as RunReport } from "../runtime-kn9DxOeg.js";
2
- export { RunReport, RunTestModuleOptions, TestContext, TestResult, runTestModule, runtimeGlobals };