@ait-co/devtools 0.1.122 → 0.1.123

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 (58) hide show
  1. package/dist/{attach-orchestrator-EnZEr4Uu.js → attach-orchestrator-CpE0dMew.js} +2 -2
  2. package/dist/{attach-orchestrator-EnZEr4Uu.js.map → attach-orchestrator-CpE0dMew.js.map} +1 -1
  3. package/dist/{cell-3bIG9538.js → cell-Ba9lCI3B.js} +2 -2
  4. package/dist/{cell-3bIG9538.js.map → cell-Ba9lCI3B.js.map} +1 -1
  5. package/dist/{cell-Cad-vi7X.js → cell-CX92lDI0.js} +3 -3
  6. package/dist/{cell-CgKUkDxY.js.map → cell-CX92lDI0.js.map} +1 -1
  7. package/dist/{cell-CgKUkDxY.js → cell-D0dJSGHi.js} +2 -2
  8. package/dist/{cell-Cad-vi7X.js.map → cell-D0dJSGHi.js.map} +1 -1
  9. package/dist/{debug-server-CF-1s3ks.js → debug-server-Bup2AJ5H.js} +5 -943
  10. package/dist/debug-server-Bup2AJ5H.js.map +1 -0
  11. package/dist/{debug-server-D79E3hTa.js → debug-server-C0A_Kujq.js} +5 -225
  12. package/dist/debug-server-C0A_Kujq.js.map +1 -0
  13. package/dist/{debug-server-D_28CxC1.js → debug-server-DywH5GU-.js} +6 -6
  14. package/dist/{debug-server-D_28CxC1.js.map → debug-server-DywH5GU-.js.map} +1 -1
  15. package/dist/mcp/cli.js +8204 -4
  16. package/dist/mcp/cli.js.map +1 -1
  17. package/dist/mcp/server.js +1 -1
  18. package/dist/panel/index.js +1 -1
  19. package/dist/{qr-http-server-BJjw0hDr.js → qr-http-server-DDmFS7fX.js} +1 -1
  20. package/dist/{qr-http-server-gHBd-z7T.js.map → qr-http-server-DDmFS7fX.js.map} +1 -1
  21. package/dist/{relay-factory-BBnZM-an.js → relay-factory-DnHMDxMA.js} +3 -3
  22. package/dist/{relay-factory-BBnZM-an.js.map → relay-factory-DnHMDxMA.js.map} +1 -1
  23. package/dist/{relay-secret-store-BJjoxes1.js → relay-secret-store-CFc9n0OA.js} +1 -1
  24. package/dist/{relay-secret-store-BJjoxes1.js.map → relay-secret-store-CFc9n0OA.js.map} +1 -1
  25. package/dist/{relay-secret-store-CtK1eqoV.js → relay-secret-store-DhzAnnj-.js} +3 -3
  26. package/dist/{relay-secret-store-CtK1eqoV.js.map → relay-secret-store-DhzAnnj-.js.map} +1 -1
  27. package/dist/{relay-url-store-D3H067u0.js → relay-url-store-CwKT7i04.js} +2 -2
  28. package/dist/{relay-url-store-D3H067u0.js.map → relay-url-store-CwKT7i04.js.map} +1 -1
  29. package/dist/{relay-url-store-uyVNe-Az.js → relay-url-store-DGQ-HPQC.js} +2 -2
  30. package/dist/{relay-url-store-uyVNe-Az.js.map → relay-url-store-DGQ-HPQC.js.map} +1 -1
  31. package/dist/test-runner/bin.d.ts +2 -0
  32. package/dist/{cli-CaJ-vnBj.js → test-runner/bin.js} +31 -8
  33. package/dist/test-runner/bin.js.map +1 -0
  34. package/dist/test-runner/config.js +1 -1
  35. package/dist/test-runner/relay-factory.js +2 -2
  36. package/dist/totp-D1pulXLa.js +3 -0
  37. package/dist/{totp-0bvlo9Yb.js → totp-WY6l0ysP.js} +1 -1
  38. package/dist/{totp-0bvlo9Yb.js.map → totp-WY6l0ysP.js.map} +1 -1
  39. package/package.json +2 -2
  40. package/dist/attach-orchestrator-BYMCSPjf.js +0 -2797
  41. package/dist/attach-orchestrator-BYMCSPjf.js.map +0 -1
  42. package/dist/attach-orchestrator-LOjvsE9R.js +0 -3
  43. package/dist/cell-Bfbke0cT.js +0 -3
  44. package/dist/cell-SKNUQTr8.js +0 -85
  45. package/dist/cell-SKNUQTr8.js.map +0 -1
  46. package/dist/cli-CaJ-vnBj.js.map +0 -1
  47. package/dist/debug-server-CF-1s3ks.js.map +0 -1
  48. package/dist/debug-server-D79E3hTa.js.map +0 -1
  49. package/dist/debug-server-IVaJ9vHH.js +0 -4278
  50. package/dist/debug-server-IVaJ9vHH.js.map +0 -1
  51. package/dist/qr-http-server-BJjw0hDr.js.map +0 -1
  52. package/dist/qr-http-server-BpcDt-cp.js +0 -3
  53. package/dist/qr-http-server-gHBd-z7T.js +0 -1506
  54. package/dist/relay-secret-store-BIM_0y18.js +0 -3
  55. package/dist/test-runner/cli.d.ts +0 -547
  56. package/dist/test-runner/cli.d.ts.map +0 -1
  57. package/dist/test-runner/cli.js +0 -3
  58. package/dist/totp-zGIlDsZS.js +0 -3
@@ -1,784 +1,18 @@
1
1
  import { n as buildRelayVerifyAuth, r as generateTotp, t as assertRelayAuthConfigured } from "./totp-DfekTBk3.js";
2
- import { createRelayConnectionFactory } from "./test-runner/relay-factory.js";
2
+ import "./test-runner/relay-factory.js";
3
3
  import { a as startTunnelHealthProbe, i as startQuickTunnel, n as makeTunnelStatus, o as logError, r as printAttachBanner, s as logInfo, t as generateAttachToken } from "./attach-orchestrator-RhOdO3DS.js";
4
- import "./cell-3bIG9538.js";
4
+ import "./cell-Ba9lCI3B.js";
5
5
  import "./qr-http-server-BsWlOA85.js";
6
6
  import "./relay-secret-store-BcVrWwTq.js";
7
7
  import { createRequire } from "node:module";
8
- import { accessSync } from "node:fs";
9
- import * as path$1 from "node:path";
10
- import path, { isAbsolute, resolve } from "node:path";
11
8
  import "@modelcontextprotocol/sdk/server/index.js";
12
9
  import "@modelcontextprotocol/sdk/server/stdio.js";
13
10
  import "@modelcontextprotocol/sdk/types.js";
14
- import { parseArgs } from "node:util";
15
- import * as fs from "node:fs/promises";
16
- import { glob, mkdir, writeFile } from "node:fs/promises";
17
- import { fileURLToPath } from "node:url";
18
11
  import { EventEmitter } from "node:events";
19
12
  import { WebSocket, WebSocketServer } from "ws";
20
13
  import { createServer } from "node:http";
21
- //#region src/test-runner/discover.ts
22
- /**
23
- * Test-file discovery shared by the `devtools-test` CLI and the `run_tests`
24
- * MCP tool, so both expand glob patterns with identical semantics.
25
- *
26
- * Uses Node's built-in `fs/promises` `glob` (Node 22+) — no extra dependency,
27
- * which keeps the MCP daemon install graph lean (a plain glob lib would land in
28
- * the `npx … devtools-mcp` path for no benefit).
29
- *
30
- * Pure Node IO only (`node:fs/promises` + `node:path`) — react-free, so it is
31
- * safe to import from the MCP daemon graph.
32
- */
33
- /**
34
- * Expands `patterns` (globs or plain paths) into a sorted, de-duplicated list of
35
- * ABSOLUTE test file paths, resolved relative to `cwd`.
36
- *
37
- * A plain (non-glob) path passes through when it matches a real file; a glob
38
- * expands against `cwd`. Absolute matches are kept as-is; relative matches are
39
- * resolved against `cwd`. `bundleTestFile` requires an absolute path, so the
40
- * absolute output feeds it directly.
41
- *
42
- * @param patterns Glob patterns or file paths (e.g. `['src/**\/*.ait.test.ts']`).
43
- * @param cwd Base directory for relative patterns/results.
44
- * @returns Sorted, de-duplicated absolute file paths. Empty when nothing matches.
45
- */
46
- async function discoverTestFiles(patterns, cwd) {
47
- const out = /* @__PURE__ */ new Set();
48
- for await (const match of glob(patterns, { cwd })) out.add(isAbsolute(match) ? match : resolve(cwd, match));
49
- return [...out].sort();
50
- }
51
- //#endregion
52
- //#region src/test-runner/bundle.ts
53
- /**
54
- * esbuild-based bundler for user test files.
55
- *
56
- * Bundles a single test file into a self-contained IIFE string that can be
57
- * injected into a WebView via `Runtime.evaluate`. The bundle includes the
58
- * test runtime (`runtime.ts`), which provides `describe/it/test/expect` and
59
- * the `runTestModule(factory)` entry point.
60
- *
61
- * ## How the wiring works
62
- *
63
- * The bundle exposes two exports on `globalThis.__testBundle`:
64
- * - `runTestModule` — the runtime's entry function.
65
- * - `__userFactory` — an async function whose body is the user's top-level
66
- * test registration code (describe/it/test calls).
67
- *
68
- * The Node-side RPC (`rpc.ts`) calls:
69
- * `globalThis.__testBundle.runTestModule(globalThis.__testBundle.__userFactory)`
70
- *
71
- * `runTestModule` then installs `describe/it/test/expect` as globals, invokes
72
- * the factory (which registers all tests), runs them, and returns a `RunReport`.
73
- *
74
- * ## Why a factory wrapper is needed
75
- *
76
- * Naively adding the runtime to `entryPoints` and bundling the user file would
77
- * fail for two reasons:
78
- * 1. `describe/it/test/expect` from the runtime are module-local in the IIFE
79
- * scope. The user's top-level `describe(...)` calls expect them as globals —
80
- * they are not globals until `runTestModule` installs them.
81
- * 2. Even with globals pre-installed, the user file runs at IIFE-evaluation
82
- * time, before the RPC layer calls `runTestModule` to reset state and start
83
- * the test clock.
84
- *
85
- * The factory approach solves both: the user's registration code is deferred
86
- * into a function that `runTestModule` calls AFTER installing the globals.
87
- *
88
- * ## Factory extraction algorithm
89
- *
90
- * The `userFactoryPlugin` reads the user file and splits lines into:
91
- * - **top-level**: `import …` and re-export lines — kept at module scope
92
- * (the only valid position for static `import` in ESM).
93
- * - **body**: all other statements — moved into the body of the exported
94
- * `__userFactory` async function.
95
- *
96
- * esbuild processes the re-generated module, following each static import
97
- * through the normal dependency graph (including the SDK-redirect plugin).
98
- *
99
- * ## SDK redirect
100
- *
101
- * Imports of `@apps-in-toss/web-framework` (and sub-paths) are intercepted via
102
- * the `sdkRedirectPlugin` and replaced with a virtual `window.__sdk` proxy that
103
- * `src/in-app/auto.ts` installs at runtime. This works for both 2.x and 3.x SDK.
104
- *
105
- * SECRET-HANDLING: the returned bundle code is caller-managed; never log it.
106
- */
107
- /** The SDK package name that mini-app test code imports from. */
108
- const SDK_PACKAGE = "@apps-in-toss/web-framework";
109
- /**
110
- * Names the runtime installs as globals before invoking the user factory.
111
- * The `vitest` virtual module re-exports each as a lazy getter that reads from
112
- * `globalThis` at access time. Keep in sync with the globals installed in
113
- * `runtime.ts#runTestModule`.
114
- */
115
- const VITEST_GLOBAL_NAMES = [
116
- "describe",
117
- "it",
118
- "test",
119
- "expect",
120
- "beforeAll",
121
- "afterAll",
122
- "beforeEach",
123
- "afterEach",
124
- "vi"
125
- ];
126
- /**
127
- * Matches the bare SDK package and any sub-path import
128
- * (`@apps-in-toss/web-framework`, `@apps-in-toss/web-framework/foo`).
129
- * Built from {@link SDK_PACKAGE} so the package name has a single source.
130
- */
131
- const SDK_IMPORT_FILTER = new RegExp(`^${SDK_PACKAGE.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`);
132
- /**
133
- * esbuild plugin that intercepts SDK imports and redirects them to the
134
- * `window.__sdk` proxy that `src/in-app/auto.ts` installs at runtime.
135
- *
136
- * Strategy: for every import of `@apps-in-toss/web-framework` (or sub-paths),
137
- * esbuild resolves it to a virtual module that re-exports all named exports
138
- * via `window.__sdk[name]`. This avoids bundling the real SDK (which may not
139
- * be available in the test environment) while still making named imports work.
140
- *
141
- * If `window.__sdk` is absent (non-dog-food build), every access throws a
142
- * descriptive error rather than returning `undefined` silently.
143
- */
144
- function sdkRedirectPlugin() {
145
- return {
146
- name: "sdk-redirect",
147
- setup(build) {
148
- build.onResolve({ filter: SDK_IMPORT_FILTER }, (args) => ({
149
- path: args.path,
150
- namespace: "sdk-redirect"
151
- }));
152
- build.onLoad({
153
- filter: /.*/,
154
- namespace: "sdk-redirect"
155
- }, () => ({
156
- contents: `
157
- var __proxy = (typeof window !== 'undefined' && window.__sdk)
158
- ? window.__sdk
159
- : new Proxy({}, {
160
- get: function(_t, p) {
161
- throw new Error('window.__sdk is not installed — run in a dog-food build. Missing: ' + String(p));
162
- }
163
- });
164
- module.exports = __proxy;
165
- `,
166
- loader: "js"
167
- }));
168
- }
169
- };
170
- }
171
- /**
172
- * esbuild plugin that intercepts `import … from 'vitest'` and replaces it with
173
- * a virtual module that delegates every named import to `globalThis` at ACCESS
174
- * time (not at bundle-evaluation time).
175
- *
176
- * The runtime installs `describe/it/test/expect/beforeAll/afterAll/beforeEach/
177
- * afterEach/vi` as globals inside `runTestModule`, which runs AFTER the bundle
178
- * IIFE is evaluated. A value-copy redirect (`export var describe =
179
- * globalThis.describe`) would therefore capture `undefined` at evaluation time
180
- * and the user's `describe(...)` calls would be no-ops — registering zero tests.
181
- *
182
- * The fix defers the lookup to call time using per-name **getter** exports.
183
- * We emit a CommonJS module that:
184
- * 1. sets `__esModule = true` so esbuild's `__toESM` interop maps each named
185
- * import directly to a property access on the module (NOT wrapped under a
186
- * `default` shim — which is what happens for a bare Proxy whose own-keys
187
- * are empty, leaving every named import `undefined`);
188
- * 2. defines each global name as a getter that reads `globalThis[name]` on
189
- * every access. So `import { describe } from 'vitest'` compiles to
190
- * `import_vitest.describe`, whose getter returns the real `describe` only
191
- * when the factory calls it — after `runTestModule` installs the globals.
192
- *
193
- * A plain `module.exports = new Proxy(...)` does NOT work here: esbuild routes
194
- * the virtual module through `__toESM`, which enumerates own-keys (none on an
195
- * empty Proxy target) and therefore exposes zero named exports. Explicit getter
196
- * properties give `__toESM` real keys to map while keeping access lazy.
197
- */
198
- function vitestRedirectPlugin() {
199
- return {
200
- name: "vitest-redirect",
201
- setup(build) {
202
- build.onResolve({ filter: /^vitest$/ }, () => ({
203
- path: "vitest",
204
- namespace: "vitest-redirect"
205
- }));
206
- build.onLoad({
207
- filter: /^vitest$/,
208
- namespace: "vitest-redirect"
209
- }, () => {
210
- return {
211
- contents: `Object.defineProperty(exports, '__esModule', { value: true });\n${VITEST_GLOBAL_NAMES.map((name) => `Object.defineProperty(exports, ${JSON.stringify(name)}, { enumerable: true, get: function() { return globalThis[${JSON.stringify(name)}]; } });`).join("\n")}\n`,
212
- loader: "js"
213
- };
214
- });
215
- }
216
- };
217
- }
218
- /**
219
- * esbuild plugin that transforms the user test file into a module that exports
220
- * an async `__userFactory` function. The factory defers the user's top-level
221
- * test registration code (describe/it/test calls) so it only runs when
222
- * `runTestModule(__userFactory)` explicitly invokes it — AFTER the runtime has
223
- * installed describe/it/test/expect as globals.
224
- *
225
- * Algorithm:
226
- * - Import declarations and re-export statements are kept at module top-level
227
- * (the only valid ESM position for static `import`). A statement that spans
228
- * multiple lines — e.g. a named import with one member per line:
229
- * import {
230
- * appLogin,
231
- * getAnonymousKey,
232
- * } from '@apps-in-toss/web-framework';
233
- * is tracked as a single block: every line from the opening `import {` /
234
- * `export {` through the closing `from '…'` (or side-effect `'…'`) line is
235
- * kept together at top-level. This prevents the member lines and the
236
- * closing `} from '…'` line from leaking into the factory body, which would
237
- * leave an unterminated `import {` at module scope (the #678 env3 failure:
238
- * esbuild threw `Expected "as" but found "{"` on multi-line SDK imports).
239
- * - All other lines (describe/it/test calls, local declarations, etc.) are
240
- * moved into the body of the exported async factory function.
241
- *
242
- * This preserves SDK import resolution (the sdk-redirect plugin processes
243
- * top-level imports normally) while deferring test registration to the factory.
244
- */
245
- function userFactoryPlugin(absPath) {
246
- const NAMESPACE = "user-test-factory";
247
- return {
248
- name: "user-test-factory",
249
- setup(build) {
250
- build.onResolve({ filter: /^user-test-factory$/ }, () => ({
251
- path: absPath,
252
- namespace: NAMESPACE
253
- }));
254
- build.onLoad({
255
- filter: /.*/,
256
- namespace: NAMESPACE
257
- }, async (args) => {
258
- const lines = (await fs.readFile(args.path, "utf8")).split("\n");
259
- const topLevelLines = [];
260
- const bodyLines = [];
261
- const EXPORT_DECLARATION_RE = /^(export\s+)(default\s+|async\s+function\s+|function\s+|class\s+|const\s+|let\s+|var\s+)/;
262
- const isImportStart = (trimmed) => trimmed.startsWith("import ") || trimmed.startsWith("import{") || trimmed.startsWith("import'") || trimmed.startsWith("import\"");
263
- const endsStatement = (trimmed) => /['"]\s*;?\s*$/.test(trimmed.replace(/\/\/.*$/, "").trimEnd());
264
- let inImportBlock = false;
265
- for (const line of lines) {
266
- const trimmed = line.trimStart();
267
- const indent = line.slice(0, line.length - trimmed.length);
268
- if (inImportBlock) {
269
- topLevelLines.push(line);
270
- if (endsStatement(trimmed)) inImportBlock = false;
271
- continue;
272
- }
273
- if (isImportStart(trimmed)) {
274
- topLevelLines.push(line);
275
- if (!endsStatement(trimmed)) inImportBlock = true;
276
- } else if (trimmed.startsWith("export ")) if (trimmed.match(EXPORT_DECLARATION_RE)) bodyLines.push(indent + trimmed.slice(7));
277
- else {
278
- topLevelLines.push(line);
279
- if (/\bfrom\b/.test(trimmed) ? !endsStatement(trimmed) : trimmed.endsWith("{")) inImportBlock = true;
280
- }
281
- else bodyLines.push(line);
282
- }
283
- return {
284
- contents: [
285
- ...topLevelLines,
286
- "",
287
- "// biome-ignore lint: generated factory wrapper",
288
- "export default async function __userFactory(): Promise<void> {",
289
- ...bodyLines.map((l) => ` ${l}`),
290
- "}"
291
- ].join("\n"),
292
- loader: "ts",
293
- resolveDir: path$1.dirname(absPath)
294
- };
295
- });
296
- }
297
- };
298
- }
299
- /**
300
- * Returns the absolute filesystem path to the test-runner runtime module
301
- * (dist/test-runner/runtime.js — a fully self-contained page-side bundle).
302
- *
303
- * Rolldown code-splitting duplicates this bundling logic into shared chunks
304
- * emitted at ARBITRARY dist depths: the `devtools-test` CLI pulls it from
305
- * dist/test-runner/bundle.js (dir = dist/test-runner/), while the `devtools-mcp`
306
- * daemon (dist/mcp/cli.js) pulls it through a ROOT chunk
307
- * (dist/debug-server-<hash>.js, dir = dist/). A fixed `..`-hop candidate list
308
- * is therefore wrong from at least one chunk — the live #697 regression.
309
- *
310
- * This resolves WITHOUT assuming chunk depth: from `import.meta.url`'s dir it
311
- * probes the co-located `runtime.js` and the nested `test-runner/runtime.js`,
312
- * then ascends one directory at a time (bounded) repeating both probes. The
313
- * nested probe catches dist/test-runner/runtime.js from the dist/ root level no
314
- * matter which depth the chunk was hoisted to (root, dist/mcp/, or a future
315
- * relocation). The build always emits dist/test-runner/runtime.js (tsdown entry
316
- * `'test-runner/runtime'`; guarded by scripts/check-test-runner-dist.sh).
317
- *
318
- * An ABSOLUTE path is returned deliberately: esbuild loads it as a literal file
319
- * read, bypassing Node module resolution entirely, so this works identically in
320
- * the npx-daemon context (its own dist tree) and the consumer-CLI context
321
- * (the mini-app's installed @ait-co/devtools dist) — neither needs the package
322
- * to be node-resolvable from the caller.
323
- */
324
- function getRuntimePath() {
325
- const startDir = path$1.dirname(fileURLToPath(import.meta.url));
326
- const RELATIVE_PROBES = [
327
- ["runtime.js"],
328
- ["test-runner", "runtime.js"],
329
- ["runtime.ts"],
330
- ["test-runner", "runtime.ts"]
331
- ];
332
- let dir = startDir;
333
- for (let i = 0; i < 12; i++) {
334
- for (const segs of RELATIVE_PROBES) {
335
- const candidate = path$1.join(dir, ...segs);
336
- try {
337
- accessSync(candidate);
338
- return candidate;
339
- } catch {}
340
- }
341
- const parent = path$1.dirname(dir);
342
- if (parent === dir) break;
343
- dir = parent;
344
- }
345
- return path$1.join(startDir, "runtime.js");
346
- }
347
- /**
348
- * Bundles `absPath` into a single IIFE string suitable for `Runtime.evaluate`.
349
- *
350
- * The IIFE installs `window.__testBundle` (or the custom `globalName`) with:
351
- * - `runTestModule` — the runtime entry (from `runtime.ts`).
352
- * - `__userFactory` — an async function wrapping the user's test registration
353
- * code so it runs AFTER `runTestModule` installs the globals.
354
- *
355
- * Callers (rpc.ts) invoke:
356
- * `globalThis.__testBundle.runTestModule(globalThis.__testBundle.__userFactory)`
357
- *
358
- * @param absPath - Absolute path to the user test file.
359
- * @param opts - Optional bundling overrides.
360
- */
361
- async function bundleTestFile(absPath, opts) {
362
- const globalName = opts?.globalName ?? "__testBundle";
363
- const extraExternals = opts?.extraExternals ?? [];
364
- const esbuild = await import("esbuild");
365
- const runtimePath = getRuntimePath();
366
- const wrapperContent = [
367
- `import { runTestModule } from ${JSON.stringify(runtimePath)};`,
368
- `import __userFactory from "user-test-factory";`,
369
- `export { runTestModule, __userFactory };`
370
- ].join("\n");
371
- const result = await esbuild.build({
372
- stdin: {
373
- contents: wrapperContent,
374
- loader: "ts",
375
- resolveDir: path$1.dirname(absPath)
376
- },
377
- bundle: true,
378
- format: "iife",
379
- globalName,
380
- platform: "browser",
381
- target: "es2022",
382
- write: false,
383
- plugins: [
384
- userFactoryPlugin(absPath),
385
- vitestRedirectPlugin(),
386
- sdkRedirectPlugin()
387
- ],
388
- external: extraExternals,
389
- treeShaking: true,
390
- footer: { js: `globalThis[${JSON.stringify(globalName)}] = ${globalName};` }
391
- });
392
- const warnings = result.warnings.map((w) => `${path$1.relative(process.cwd(), w.location?.file ?? "")}:${w.location?.line ?? "?"}: ${w.text}`);
393
- const outputFile = result.outputFiles?.[0];
394
- if (!outputFile) throw new Error("bundleTestFile: esbuild produced no output — check entryPoints");
395
- return {
396
- code: outputFile.text,
397
- warnings
398
- };
399
- }
400
- //#endregion
401
- //#region src/test-runner/capture.ts
402
- /** The exact console-line prefix sdk-example's `flushCapture` emits. */
403
- const CAPTURE_PREFIX = "__AIT_CAPTURE__ ";
404
- /**
405
- * Parses raw console line texts into {@link AitCaptureLine}s.
406
- *
407
- * Filtering rules (each independently drops a line — never throws):
408
- * - the line text must `startsWith(CAPTURE_PREFIX)` exactly (allowlist);
409
- * - there must be a non-empty category token (up to the next space);
410
- * - the remaining payload must be valid JSON (`JSON.parse` succeeds).
411
- *
412
- * Lines that fail any rule (wss/scheme noise, truncated, broken JSON) are
413
- * silently discarded — capture harvesting is best-effort and must never fail a
414
- * run or leak a malformed/secret-bearing line.
415
- *
416
- * @param raw - Console line objects (only `.text` is read).
417
- * @returns The captured lines, in input order.
418
- */
419
- function parseCaptureLines(raw) {
420
- const out = [];
421
- for (const { text } of raw) {
422
- if (!text.startsWith(CAPTURE_PREFIX)) continue;
423
- const body = text.slice(16);
424
- const spaceIdx = body.indexOf(" ");
425
- if (spaceIdx === -1) continue;
426
- const category = body.slice(0, spaceIdx);
427
- const json = body.slice(spaceIdx + 1);
428
- if (category === "" || json === "") continue;
429
- try {
430
- JSON.parse(json);
431
- } catch {
432
- continue;
433
- }
434
- out.push({
435
- category,
436
- json
437
- });
438
- }
439
- return out;
440
- }
441
- //#endregion
442
- //#region src/test-runner/rpc.ts
443
- /** Maximum milliseconds to wait for a single evaluate round-trip. */
444
- const DEFAULT_TIMEOUT_MS = 3e4;
445
- /**
446
- * Wraps bundle code in a self-executing IIFE that:
447
- * 1. Evaluates the bundle (registering describe/it/test).
448
- * 2. Calls `__testBundle.runTestModule(...)` — the entry the runtime exports.
449
- * 3. Returns a JSON-serialised `RunReport` string.
450
- *
451
- * The double-serialisation (RunReport → JSON string → returnByValue string)
452
- * is intentional: CDP `returnByValue` reliably transports strings; deeply
453
- * nested objects can lose fidelity across the Chii relay.
454
- *
455
- * SECRET-HANDLING: `bundleCode` MUST NOT be logged by callers.
456
- */
457
- function buildRunTestsExpression(bundleCode) {
458
- 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)}); }})()`;
459
- }
460
- /**
461
- * Parses the raw CDP `returnByValue` result from a `buildRunTestsExpression`
462
- * evaluate call into a typed `RpcRunResult`.
463
- *
464
- * Throws only on parse failure — an `ok:false` envelope is a normal result.
465
- *
466
- * SECRET-HANDLING: `rawValue` is not included in error messages.
467
- */
468
- function parseRunTestsResult(rawValue) {
469
- if (typeof rawValue !== "string") throw new Error(`rpc.parseRunTestsResult: unexpected return type "${typeof rawValue}" — expected JSON string`);
470
- let parsed;
471
- try {
472
- parsed = JSON.parse(rawValue);
473
- } catch {
474
- throw new Error("rpc.parseRunTestsResult: bridge returned non-JSON string");
475
- }
476
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("rpc.parseRunTestsResult: parsed result is not an object");
477
- const obj = parsed;
478
- if (obj.ok === true) return {
479
- ok: true,
480
- report: obj.value
481
- };
482
- if (obj.ok === false) return {
483
- ok: false,
484
- error: typeof obj.error === "string" ? obj.error : String(obj.error)
485
- };
486
- throw new Error("rpc.parseRunTestsResult: result missing \"ok\" field");
487
- }
488
- /**
489
- * Injects `bundleCode` into the attached page and awaits test execution.
490
- *
491
- * Uses `Runtime.evaluate` with `awaitPromise: true` to wait for the
492
- * async IIFE to settle. The 30-second CDP command timeout covers even
493
- * long-running test suites; split into smaller files if you hit it.
494
- *
495
- * @param connection - Active CDP connection (relay or local).
496
- * @param bundleCode - IIFE bundle string from `bundleTestFile`.
497
- * @param timeoutMs - Override the default 30 s timeout.
498
- *
499
- * SECRET-HANDLING: `bundleCode` and the raw CDP result value are never logged.
500
- */
501
- async function injectAndRunBundle(connection, bundleCode, timeoutMs = DEFAULT_TIMEOUT_MS) {
502
- const expression = buildRunTestsExpression(bundleCode);
503
- const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error(`rpc: evaluate timed out after ${timeoutMs}ms`)), timeoutMs));
504
- const evalPromise = connection.send("Runtime.evaluate", {
505
- expression,
506
- returnByValue: true,
507
- awaitPromise: true
508
- });
509
- const cdpResult = await Promise.race([evalPromise, timeoutPromise]);
510
- if (cdpResult.exceptionDetails) {
511
- const msg = cdpResult.exceptionDetails.exception?.description ?? cdpResult.exceptionDetails.text ?? "Runtime.evaluate threw an exception";
512
- throw new Error(`rpc.injectAndRunBundle: ${msg}`);
513
- }
514
- return parseRunTestsResult(cdpResult.result.value);
515
- }
516
- //#endregion
517
- //#region src/test-runner/relay-worker.ts
518
- /**
519
- * Runs all `files` sequentially over the given CDP `connection`.
520
- *
521
- * For each file:
522
- * 1. Bundle with esbuild (includes SDK shim + runtime).
523
- * 2. Inject into the attached page via `Runtime.evaluate`.
524
- * 3. Await the `RunReport` JSON response.
525
- * 4. Accumulate results.
526
- *
527
- * Returns a `RelayRunReport` with per-file results and flattened totals.
528
- *
529
- * This function does NOT open or manage the relay connection — the caller
530
- * is responsible for attaching and closing it.
531
- *
532
- * TODO (#645): implement the Vitest `PoolRunnerInitializer` interface here
533
- * so that `runTestFilesOverRelay` can be used as a Vitest pool entry.
534
- *
535
- * @param connection - Active CDP connection (relay or local kind).
536
- * @param files - Absolute paths to test files, run in order.
537
- * @param opts - Optional per-run overrides.
538
- */
539
- async function runTestFilesOverRelay(connection, files, opts) {
540
- const wallStart = Date.now();
541
- const startedAt = new Date(wallStart).toISOString();
542
- const fileResults = [];
543
- let domainsEnabled = false;
544
- try {
545
- await connection.enableDomains();
546
- domainsEnabled = true;
547
- } catch (e) {
548
- process.stderr.write(`relay-worker: enableDomains() failed before run — console capture may be empty (${e instanceof Error ? e.message : String(e)})\n`);
549
- }
550
- const collectCaptures = opts?.collectCaptures === true;
551
- const liveConsole = [];
552
- let unsubscribeConsole;
553
- if (collectCaptures && domainsEnabled) unsubscribeConsole = connection.on("Runtime.consoleAPICalled", (event) => {
554
- liveConsole.push(event);
555
- });
556
- try {
557
- for (const file of files) {
558
- let fileEntry;
559
- try {
560
- const { code } = await bundleTestFile(file, opts?.bundleOptions);
561
- const rpcResult = await injectAndRunBundle(connection, code, opts?.timeoutMs);
562
- if (rpcResult.ok) fileEntry = {
563
- file,
564
- result: rpcResult.report
565
- };
566
- else fileEntry = {
567
- file,
568
- result: { error: rpcResult.error }
569
- };
570
- } catch (e) {
571
- fileEntry = {
572
- file,
573
- result: { error: e instanceof Error ? e.message : String(e) }
574
- };
575
- }
576
- fileResults.push(fileEntry);
577
- }
578
- } finally {
579
- unsubscribeConsole?.();
580
- }
581
- const captures = collectCaptures ? parseCaptureLines(liveConsole.map((e) => ({ text: renderConsoleLineText(e) }))) : [];
582
- const totals = fileResults.reduce((acc, { result }) => {
583
- if ("error" in result) {
584
- acc.failed += 1;
585
- acc.total += 1;
586
- } else {
587
- acc.passed += result.passed;
588
- acc.failed += result.failed;
589
- acc.skipped += result.skipped;
590
- acc.total += result.passed + result.failed + result.skipped;
591
- }
592
- return acc;
593
- }, {
594
- passed: 0,
595
- failed: 0,
596
- skipped: 0,
597
- total: 0
598
- });
599
- return {
600
- startedAt,
601
- duration: Date.now() - wallStart,
602
- files: fileResults,
603
- totals,
604
- captures
605
- };
606
- }
607
- /**
608
- * Renders one `Runtime.consoleAPICalled` event to a single line of text, the
609
- * same way `tools.ts#normalizeConsoleMessage` does (args rendered + space-
610
- * joined). Inlined here (≈8 lines) so this module avoids importing `tools.ts`,
611
- * which would drag the heavy MCP/Node graph (server-lock, parent-watcher, …)
612
- * onto the test-runner entry.
613
- *
614
- * SECRET-HANDLING: this only stringifies console args; the caller's
615
- * allowlist-prefix parser then discards everything that is not a genuine
616
- * `__AIT_CAPTURE__` line.
617
- */
618
- function renderConsoleLineText(event) {
619
- return event.args.map((arg) => {
620
- if (arg.value !== void 0) {
621
- if (typeof arg.value === "string") return arg.value;
622
- try {
623
- return JSON.stringify(arg.value);
624
- } catch {
625
- return String(arg.value);
626
- }
627
- }
628
- if (arg.description !== void 0) return arg.description;
629
- if (arg.className !== void 0) return arg.className;
630
- return arg.subtype ?? arg.type;
631
- }).join(" ");
632
- }
633
- //#endregion
634
- //#region src/test-runner/report.ts
635
- /**
636
- * Runner-agnostic report serialisation for env3 test runs (devtools#696).
637
- *
638
- * Both env3 execution paths — the Vitest custom pool (`pool.ts`) and the
639
- * standalone `devtools-test` CLI (`cli.ts`) — call the same core
640
- * `runTestFilesOverRelay` and so produce the same {@link RelayRunReport}. This
641
- * module is the single, runner-neutral place that turns that in-memory report
642
- * into a stable on-disk artifact so a 2.x run and a 3.0 run can be diffed
643
- * cell-by-cell after the fact.
644
- *
645
- * The serialised schema is deliberately MINIMAL and secret-free:
646
- *
647
- * - file paths are stored RELATIVE to `projectRoot` (no absolute `/Users/...`
648
- * leakage — see {@link RunnerAgnosticReport.files});
649
- * - the cell metadata (sdkLine/platform) is baked INTO the body, not only the
650
- * filename, so a moved artifact never loses its provenance;
651
- * - NO relay wss / scheme / TOTP / relayUrl fields exist in the schema at all
652
- * (enforced by the type + this comment) — error strings are the matcher
653
- * message only, inherited from rpc.ts which already strips expression/value.
654
- *
655
- * react-free — depends only on the type-level `RelayRunReport` and `node:fs` /
656
- * `node:path`. Safe to bundle without pulling the chii/cloudflared graph.
657
- */
658
- /**
659
- * Converts an absolute (or already-relative) file path to a projectRoot-relative
660
- * one. `path.relative` returns `''` when the paths are equal — guard that to the
661
- * basename so the field is never empty.
662
- *
663
- * SECRET-HANDLING: this is the single choke point that strips absolute project
664
- * paths from the artifact.
665
- */
666
- function relativise(projectRoot, file) {
667
- const rel = path.relative(projectRoot, file);
668
- if (rel === "" || rel.startsWith("..") || path.isAbsolute(rel)) return path.basename(file);
669
- return rel;
670
- }
671
- /**
672
- * Serialises a {@link RelayRunReport} into the runner-agnostic, secret-free
673
- * on-disk shape. Pure — no IO; testable with a plain report + meta.
674
- *
675
- * @param report - The core relay run report.
676
- * @param meta - Cell axes + projectRoot (projectRoot is consumed, not stored).
677
- */
678
- function serializeRelayReport(report, meta) {
679
- return {
680
- cell: {
681
- sdkLine: meta.sdkLine,
682
- platform: meta.platform
683
- },
684
- startedAt: report.startedAt,
685
- duration: report.duration,
686
- totals: report.totals,
687
- files: report.files.map((f) => {
688
- const file = relativise(meta.projectRoot, f.file);
689
- if ("error" in f.result) return {
690
- file,
691
- error: f.result.error
692
- };
693
- return {
694
- file,
695
- duration: f.result.duration,
696
- passed: f.result.passed,
697
- failed: f.result.failed,
698
- skipped: f.result.skipped,
699
- tests: f.result.tests
700
- };
701
- })
702
- };
703
- }
704
- /**
705
- * Writes the serialised report to `<dir>/<sdkLine>.<platform>.json`, creating
706
- * `dir` if needed. Returns the absolute path written.
707
- *
708
- * The cell-suffixed filename keeps 2.x and 3.0 (and per-platform) runs as
709
- * distinct artifacts in the same directory; the same cell metadata is also baked
710
- * into the body so a renamed/moved file still carries its provenance.
711
- *
712
- * SECRET-HANDLING: the written body contains no relay/secret fields (the schema
713
- * has none). `dir`/`projectRoot` are local filesystem paths, never logged here.
714
- *
715
- * @param report - The core relay run report.
716
- * @param dir - Output directory (created recursively if missing).
717
- * @param meta - Cell axes + projectRoot.
718
- * @returns The absolute path of the written file.
719
- */
720
- async function writeReportArtifact(report, dir, meta) {
721
- const serialised = serializeRelayReport(report, meta);
722
- await mkdir(dir, { recursive: true });
723
- const outFile = path.join(dir, `${meta.sdkLine}.${meta.platform}.json`);
724
- await writeFile(outFile, `${JSON.stringify(serialised, null, 2)}\n`, "utf8");
725
- return outFile;
726
- }
727
- /**
728
- * Writes harvested `__AIT_CAPTURE__` lines to per-category files under `dir`,
729
- * named `<category>.<sdkLine>.<platform>.json` — the SAME convention
730
- * sdk-example's env1 `flushCapture` uses on the filesystem, so env1 and env3
731
- * capture artifacts line up for diffing.
732
- *
733
- * Each line's `json` payload is an opaque JSON array of capture records. Lines
734
- * sharing a category are concatenated into one array, in harvest order.
735
- *
736
- * SECRET-HANDLING: only allowlist-prefixed capture lines reach here (the parser
737
- * dropped wss/scheme noise); the `json` payload is written verbatim but is a
738
- * capture record array, not a relay/secret.
739
- *
740
- * @param captures - Parsed capture lines (from `RelayRunReport.captures`).
741
- * @param dir - Output directory (created recursively if missing).
742
- * @param cell - Cell axes for the filename suffix.
743
- * @returns The absolute paths written (one per category), in category order.
744
- */
745
- async function writeCaptureArtifacts(captures, dir, cell) {
746
- if (captures.length === 0) return [];
747
- const byCategory = /* @__PURE__ */ new Map();
748
- for (const { category, json } of captures) {
749
- let merged = byCategory.get(category);
750
- if (!merged) {
751
- merged = [];
752
- byCategory.set(category, merged);
753
- }
754
- const parsed = JSON.parse(json);
755
- if (Array.isArray(parsed)) merged.push(...parsed);
756
- else merged.push(parsed);
757
- }
758
- await mkdir(dir, { recursive: true });
759
- const written = [];
760
- for (const [category, records] of byCategory) {
761
- const outFile = path.join(dir, `${category}.${cell.sdkLine}.${cell.platform}.json`);
762
- await writeFile(outFile, `${JSON.stringify(records, null, 2)}\n`, "utf8");
763
- written.push(outFile);
764
- }
765
- return written;
766
- }
767
- //#endregion
768
- //#region src/test-runner/cli.ts
769
- /**
770
- * `devtools-test` CLI.
771
- *
772
- * Shares test-file discovery with the `run_tests` MCP tool (`discoverTestFiles`)
773
- * and exposes `runWithConnection` — the pure run core that bundles, injects, and
774
- * collects each file over a CDP connection. The CLI's `main()` performs a
775
- * standalone relay attach (boot relay → QR → phone scan → cell inject → run).
776
- *
777
- * NOTE: no shebang in this source file — the tsdown entry's `banner` option
778
- * injects `#!/usr/bin/env node` into the compiled output (same pattern as
779
- * `src/mcp/cli.ts`).
780
- */
781
- const USAGE = `
14
+ new RegExp(`^${"@apps-in-toss/web-framework".replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`);
15
+ `
782
16
  devtools-test — run mini-app tests on a real device WebView over the CDP relay
783
17
 
784
18
  USAGE
@@ -825,178 +59,6 @@ EXAMPLE
825
59
  --timeout 60000
826
60
 
827
61
  `.trimStart();
828
- /**
829
- * Runs `files` over `connection` and returns the aggregate report.
830
- * This pure function is the testable core of the CLI (and is what the
831
- * `run_tests` MCP tool calls against the daemon's attached connection); it is
832
- * separate from `main()` so tests can call it without spawning a subprocess.
833
- */
834
- async function runWithConnection(connection, files, opts) {
835
- const report = await runTestFilesOverRelay(connection, files, opts);
836
- if (opts?.printSummary) {
837
- const { totals } = report;
838
- process.stdout.write(`\ndevtools-test: ${totals.passed} passed, ${totals.failed} failed, ${totals.skipped} skipped (${report.duration}ms)\n`);
839
- }
840
- return report;
841
- }
842
- /**
843
- * Decides whether to suppress the QR/attach block on stdout.
844
- *
845
- * Suppress when EITHER the user passed `--no-qr-stdout`, OR stdout is not a TTY
846
- * / `CI` is set / `AIT_NO_QR_STDOUT` is set (non-interactive — a captured stdout
847
- * must not leak the relay wss + TOTP `at=` code that the QR block encodes). The
848
- * suppression is whole-chunk: `attachUrl` AND `relayUrl` ride in the same block.
849
- *
850
- * Exported for unit testing.
851
- */
852
- function shouldSuppressQr(noQrFlag) {
853
- return noQrFlag || !process.stdout.isTTY || process.env.CI !== void 0 || process.env.AIT_NO_QR_STDOUT !== void 0;
854
- }
855
- /**
856
- * CLI entry point.
857
- *
858
- * Performs a standalone relay attach → run lifecycle, sharing the attach
859
- * assembly with the Vitest pool via `createRelayConnectionFactory` (single
860
- * source — no drift):
861
- *
862
- * 1. Parse args: globs, --timeout, --cell-sdk-line, --cell-platform,
863
- * --scheme-url (required for env3), --report-dir, --no-qr-stdout,
864
- * --headless, --project-root.
865
- * 2. Discover test files; exit 1 if none.
866
- * 3. factory.open() — boot relay → render QR (suppressed on non-interactive
867
- * stdout) → wait for phone → inject cell → enableDomains. Returns the conn.
868
- * 4. runWithConnection(conn, files, { timeoutMs, collectCaptures, printSummary }).
869
- * 5. With --report-dir: write the runner-agnostic report + capture files.
870
- * 6. factory.close(); process.exitCode = failed > 0 ? 1 : 0.
871
- *
872
- * The CLI is not a daemon — no lock, router, SSE, or tools_list is needed.
873
- * Attach timeout exits with code 1; test failures exit with code 1.
874
- *
875
- * SECRET-HANDLING: scheme_url / relay wssUrl / TOTP codes are never written to
876
- * stdout/stderr directly. The QR block (which encodes the TOTP `at=` code) is
877
- * printed only when stdout is interactive AND not suppressed.
878
- */
879
- async function main(argv = process.argv.slice(2)) {
880
- let parsed;
881
- try {
882
- parsed = parseArgs({
883
- args: argv,
884
- options: {
885
- help: {
886
- type: "boolean",
887
- short: "h"
888
- },
889
- timeout: { type: "string" },
890
- "scheme-url": { type: "string" },
891
- "cell-sdk-line": { type: "string" },
892
- "cell-platform": { type: "string" },
893
- "report-dir": { type: "string" },
894
- "no-qr-stdout": { type: "boolean" },
895
- headless: { type: "boolean" },
896
- "project-root": { type: "string" }
897
- },
898
- allowPositionals: true
899
- });
900
- } catch (e) {
901
- process.stderr.write(`devtools-test: ${e instanceof Error ? e.message : String(e)}\n`);
902
- process.exitCode = 1;
903
- return;
904
- }
905
- if (parsed.values.help || argv.length === 0) {
906
- process.stdout.write(USAGE);
907
- return;
908
- }
909
- const vals = parsed.values;
910
- const rawTimeout = typeof vals.timeout === "string" ? vals.timeout : void 0;
911
- const timeoutMs = rawTimeout !== void 0 ? parseInt(rawTimeout, 10) : 3e4;
912
- if (Number.isNaN(timeoutMs) || timeoutMs <= 0) {
913
- process.stderr.write(`devtools-test: --timeout must be a positive integer\n`);
914
- process.exitCode = 1;
915
- return;
916
- }
917
- const schemeUrl = typeof vals["scheme-url"] === "string" ? vals["scheme-url"] : "";
918
- if (schemeUrl === "") {
919
- process.stderr.write("devtools-test: --scheme-url is required for standalone relay attach.\n Pass the intoss-private:// URL from `ait deploy --scheme-only`.\n");
920
- process.exitCode = 1;
921
- return;
922
- }
923
- const headless = vals.headless === true;
924
- const projectRoot = typeof vals["project-root"] === "string" ? vals["project-root"] : process.cwd();
925
- const reportDir = typeof vals["report-dir"] === "string" ? vals["report-dir"] : void 0;
926
- const suppressQr = shouldSuppressQr(vals["no-qr-stdout"] === true);
927
- const cellSdkLine = typeof vals["cell-sdk-line"] === "string" ? vals["cell-sdk-line"] : void 0;
928
- const cellPlatform = typeof vals["cell-platform"] === "string" ? vals["cell-platform"] : process.env.AIT_CELL_PLATFORM;
929
- const hasCell = cellSdkLine !== void 0 || cellPlatform !== void 0;
930
- const cell = {
931
- sdkLine: cellSdkLine ?? "2.x",
932
- platform: cellPlatform ?? "mock"
933
- };
934
- const globs = parsed.positionals;
935
- if (globs.length === 0) {
936
- process.stderr.write(`devtools-test: at least one glob pattern is required\n`);
937
- process.stdout.write(USAGE);
938
- process.exitCode = 1;
939
- return;
940
- }
941
- const files = await discoverTestFiles(globs, process.cwd());
942
- if (files.length === 0) {
943
- process.stderr.write(`devtools-test: no test files matched ${globs.join(", ")}\n`);
944
- process.exitCode = 1;
945
- return;
946
- }
947
- process.stderr.write(`devtools-test: found ${files.length} test file(s)\n`);
948
- if (hasCell) process.stderr.write(`devtools-test: injecting __AIT_CELL__ = ${JSON.stringify(cell)}\n`);
949
- const factory = createRelayConnectionFactory({
950
- schemeUrl,
951
- projectRoot,
952
- timeoutMs,
953
- headless,
954
- cell: hasCell ? cell : void 0,
955
- onQrContent: (chunks) => {
956
- if (suppressQr) {
957
- process.stdout.write("QR suppressed (non-interactive)\n");
958
- return;
959
- }
960
- for (const chunk of chunks) process.stdout.write(`${chunk}\n`);
961
- }
962
- });
963
- let connection;
964
- try {
965
- connection = await factory.open();
966
- } catch (e) {
967
- process.stderr.write(`devtools-test: ${e instanceof Error ? e.message : String(e)}\n`);
968
- process.exitCode = 1;
969
- return;
970
- }
971
- let exitCode = 0;
972
- try {
973
- const report = await runWithConnection(connection, files, {
974
- timeoutMs,
975
- printSummary: true,
976
- collectCaptures: reportDir !== void 0
977
- });
978
- if (reportDir !== void 0) try {
979
- const reportPath = await writeReportArtifact(report, reportDir, {
980
- sdkLine: cell.sdkLine,
981
- platform: cell.platform,
982
- projectRoot
983
- });
984
- process.stderr.write(`devtools-test: wrote report ${reportPath}\n`);
985
- const capturePaths = await writeCaptureArtifacts(report.captures, `${reportDir}/.ait-capture`, cell);
986
- if (capturePaths.length > 0) process.stderr.write(`devtools-test: wrote ${capturePaths.length} capture file(s)\n`);
987
- } catch (e) {
988
- process.stderr.write(`devtools-test: failed to write report artifacts: ${e instanceof Error ? e.message : String(e)}\n`);
989
- }
990
- exitCode = report.totals.failed > 0 ? 1 : 0;
991
- } finally {
992
- await factory.close(connection);
993
- process.exitCode = exitCode;
994
- }
995
- }
996
- if (import.meta.url === new URL(process.argv[1], "file://").href) main().catch((e) => {
997
- process.stderr.write(`devtools-test: unexpected error: ${e instanceof Error ? e.message : String(e)}\n`);
998
- process.exitCode = 1;
999
- });
1000
62
  //#endregion
1001
63
  //#region src/shared/relay-auth-close.ts
1002
64
  /**
@@ -1907,4 +969,4 @@ async function bootRelayFamily(options = {}) {
1907
969
  //#endregion
1908
970
  export { bootRelayFamily, buildRelayVerifyAuth };
1909
971
 
1910
- //# sourceMappingURL=debug-server-CF-1s3ks.js.map
972
+ //# sourceMappingURL=debug-server-Bup2AJ5H.js.map