@ait-co/devtools 0.1.135 → 0.1.137

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 (72) hide show
  1. package/dist/bundle-BKqyhEK9.d.ts.map +1 -1
  2. package/dist/{chii-relay-DkTOopRj.js → chii-relay-BZ3HqWL5.js} +2 -1
  3. package/dist/{chii-relay-DkTOopRj.js.map → chii-relay-BZ3HqWL5.js.map} +1 -1
  4. package/dist/{chii-relay-P6SKmB1g.cjs → chii-relay-D7eK2acz.cjs} +2 -1
  5. package/dist/{chii-relay-P6SKmB1g.cjs.map → chii-relay-D7eK2acz.cjs.map} +1 -1
  6. package/dist/{debug-server-DR84Z243.js → debug-server-BQplCzf8.js} +23 -4
  7. package/dist/debug-server-BQplCzf8.js.map +1 -0
  8. package/dist/{debug-server-B2aCwwtV.js → debug-server-CBK7dn8O.js} +21 -2
  9. package/dist/debug-server-CBK7dn8O.js.map +1 -0
  10. package/dist/{debug-server-CBqMfcUi.js → debug-server-D2wpPK3_.js} +3 -2
  11. package/dist/debug-server-D2wpPK3_.js.map +1 -0
  12. package/dist/mcp/cli.js +72 -19
  13. package/dist/mcp/cli.js.map +1 -1
  14. package/dist/mcp/server.js +1 -1
  15. package/dist/panel/index.js +1 -1
  16. package/dist/{pool-C6TgrcyW.d.ts → pool-CmfvXnwh.d.ts} +2 -2
  17. package/dist/{pool-C6TgrcyW.d.ts.map → pool-CmfvXnwh.d.ts.map} +1 -1
  18. package/dist/{qr-http-server-yQ1EWTwf.js → qr-http-server-C7q8Gn04.js} +2 -1
  19. package/dist/{qr-http-server-iBqsAKor.js.map → qr-http-server-C7q8Gn04.js.map} +1 -1
  20. package/dist/{qr-http-server-iBqsAKor.js → qr-http-server-CTLdclg_.js} +2 -1
  21. package/dist/{qr-http-server-yQ1EWTwf.js.map → qr-http-server-CTLdclg_.js.map} +1 -1
  22. package/dist/{qr-http-server-B1en4tTL.cjs → qr-http-server-Cz1mA07b.cjs} +2 -1
  23. package/dist/{qr-http-server-hnuUw9TK.cjs.map → qr-http-server-Cz1mA07b.cjs.map} +1 -1
  24. package/dist/{qr-http-server-hnuUw9TK.cjs → qr-http-server-D16Lq2ag.cjs} +2 -1
  25. package/dist/{qr-http-server-B1en4tTL.cjs.map → qr-http-server-D16Lq2ag.cjs.map} +1 -1
  26. package/dist/{qr-http-server-DlNWJEaH.js → qr-http-server-ZkT6F7is.js} +2 -1
  27. package/dist/{qr-http-server-CLt72Dwe.js.map → qr-http-server-ZkT6F7is.js.map} +1 -1
  28. package/dist/{qr-http-server-CLt72Dwe.js → qr-http-server-wy3L7Yut.js} +2 -1
  29. package/dist/{qr-http-server-DlNWJEaH.js.map → qr-http-server-wy3L7Yut.js.map} +1 -1
  30. package/dist/{relay-factory-CjqAJ3Y8.js → relay-factory-DVzpBRxz.js} +4 -3
  31. package/dist/relay-factory-DVzpBRxz.js.map +1 -0
  32. package/dist/{relay-worker-UK0nBfDX.js → relay-worker-6fy1eaIo.js} +6 -3
  33. package/dist/{relay-worker-UK0nBfDX.js.map → relay-worker-6fy1eaIo.js.map} +1 -1
  34. package/dist/{relay-worker-Dw3aIzIK.d.ts → relay-worker-DJnZkXza.d.ts} +41 -7
  35. package/dist/relay-worker-DJnZkXza.d.ts.map +1 -0
  36. package/dist/test-runner/bin.js +198 -30
  37. package/dist/test-runner/bin.js.map +1 -1
  38. package/dist/test-runner/bridge-stub.d.ts +125 -0
  39. package/dist/test-runner/bridge-stub.d.ts.map +1 -0
  40. package/dist/test-runner/bridge-stub.js +92 -0
  41. package/dist/test-runner/bridge-stub.js.map +1 -0
  42. package/dist/test-runner/bundle.js +44 -14
  43. package/dist/test-runner/bundle.js.map +1 -1
  44. package/dist/test-runner/config.d.ts +11 -1
  45. package/dist/test-runner/config.d.ts.map +1 -1
  46. package/dist/test-runner/config.js +1 -1
  47. package/dist/test-runner/pool.d.ts +1 -1
  48. package/dist/test-runner/pool.js +1 -1
  49. package/dist/test-runner/relay-factory.d.ts +10 -0
  50. package/dist/test-runner/relay-factory.d.ts.map +1 -1
  51. package/dist/test-runner/relay-factory.js +3 -2
  52. package/dist/test-runner/relay-factory.js.map +1 -1
  53. package/dist/test-runner/relay-worker.d.ts +1 -1
  54. package/dist/test-runner/relay-worker.js +1 -1
  55. package/dist/test-runner/report.d.ts +41 -15
  56. package/dist/test-runner/report.d.ts.map +1 -1
  57. package/dist/test-runner/report.js +33 -9
  58. package/dist/test-runner/report.js.map +1 -1
  59. package/dist/{tunnel-DUCogcQi.js → tunnel-5eBncITa.js} +2 -2
  60. package/dist/{tunnel-DUCogcQi.js.map → tunnel-5eBncITa.js.map} +1 -1
  61. package/dist/{tunnel-Cr_oTkaj.cjs → tunnel-DjtSENJU.cjs} +2 -2
  62. package/dist/{tunnel-Cr_oTkaj.cjs.map → tunnel-DjtSENJU.cjs.map} +1 -1
  63. package/dist/unplugin/index.cjs +2 -2
  64. package/dist/unplugin/index.js +2 -2
  65. package/dist/unplugin/tunnel.cjs +1 -1
  66. package/dist/unplugin/tunnel.js +1 -1
  67. package/package.json +1 -1
  68. package/dist/debug-server-B2aCwwtV.js.map +0 -1
  69. package/dist/debug-server-CBqMfcUi.js.map +0 -1
  70. package/dist/debug-server-DR84Z243.js.map +0 -1
  71. package/dist/relay-factory-CjqAJ3Y8.js.map +0 -1
  72. package/dist/relay-worker-Dw3aIzIK.d.ts.map +0 -1
@@ -0,0 +1,125 @@
1
+ //#region src/test-runner/bridge-stub.d.ts
2
+ /**
3
+ * Bridge-stub interceptor for env3 blocking-UI SDK calls (devtools#740, DT-2).
4
+ *
5
+ * `--manual-blocking` (devtools#741) lets a human tap through native-UI SDK
6
+ * calls (fullscreen ads, permission dialogs, the save-file share sheet) so
7
+ * their real device behaviour can be captured. That capture (sdk-example
8
+ * run11, 2026-07-05, 2.x/iOS) is the ground truth this module replays: an
9
+ * opt-in interceptor that sits BETWEEN the test bundle and the real native
10
+ * bridge (`window.__sdk`, installed by `src/in-app/auto.ts`) and answers a
11
+ * fixed allowlist of blocking calls with fixture-shaped responses instead of
12
+ * forwarding them to native UI. This lets the SAME manual-tagged test files
13
+ * run unattended, so their assertions (denied-permission error shape,
14
+ * `NO_PERMISSION` envelope, etc.) become part of a CI-checkable contract
15
+ * instead of only ever running with a human present.
16
+ *
17
+ * HONESTY CONTRACT (devtools#740): a stubbed run is NOT env3 in the pure
18
+ * sense — it is a HYBRID cell (real device + real WebView + real bridge
19
+ * transport for every OTHER call, but a synthetic answer for the handful of
20
+ * calls in {@link STUB_REGISTRY}). Every artifact produced under this mode
21
+ * MUST be provenance-stamped (`cell.bridgeStub: true`, see `report.ts`) and
22
+ * MUST NOT be merged into the same file/mode as an unattended baseline run.
23
+ * A stubbed PASS proves "the SDK's rejection/resolution path behaves as
24
+ * fixture-shaped" — it does NOT prove the real native dialog/sheet still
25
+ * looks or behaves a given way on-device. Real behavior re-verification stays
26
+ * on `--manual-blocking` (DT-3) — this module never replaces it, only adds an
27
+ * unattended CHECK on top of what it captured.
28
+ *
29
+ * DRIFT AVOIDANCE: fixtures are frozen snapshots of a real capture. If the
30
+ * native SDK's error shape ever changes, only a fresh `--manual-blocking` run
31
+ * (DT-3) can tell you — this module has no way to detect drift on its own.
32
+ * Treat {@link STUB_REGISTRY} as "last known native truth", not as a
33
+ * specification devtools owns.
34
+ *
35
+ * Runs entirely on the PAGE side (browser-compatible, no Node APIs) — this
36
+ * file is bundled into the injected IIFE by `bundle.ts`'s `sdkRedirectPlugin`,
37
+ * mirroring how `runtime.ts` is bundled. It is imported ONLY from the
38
+ * test-runner (never from `sdk-example/src` — boilerplate cleanliness,
39
+ * umbrella §1.4: this is maintainer/runner infrastructure, not something a
40
+ * mini-app developer's shipped code should carry).
41
+ *
42
+ * SECRET-HANDLING: fixtures below are hand-derived summaries of the captured
43
+ * shapes (see per-entry doc comments citing the source scenario) — no device
44
+ * identifiers, tunnel hosts, relay URLs, or auth codes are embedded anywhere
45
+ * in this file.
46
+ */
47
+ /**
48
+ * The exact shape a rejected native bridge call takes on iOS/2.x (and,
49
+ * empirically, unchanged across the manual captures this module replays).
50
+ * Mirrors the `errorKeys` observed in the ground-truth capture:
51
+ * `['name', 'code', 'userInfo', 'moduleName', '__isError']`.
52
+ */
53
+ interface NativeBridgeErrorShape {
54
+ name: string;
55
+ code: string;
56
+ message: string;
57
+ userInfo: Record<string, unknown>;
58
+ moduleName: string;
59
+ __isError: true;
60
+ }
61
+ /**
62
+ * One blocking-call stub entry. `kind` distinguishes the two native calling
63
+ * conventions observed in the mock/real SDK:
64
+ * - `'promise'` — the call returns a Promise the stub resolves/rejects
65
+ * (`openPermissionDialog`, `requestPermission`, `saveBase64Data`).
66
+ * - `'event'` — the call takes `{ onEvent, onError }` callbacks and returns
67
+ * a cleanup function (`showFullScreenAd`) — the native fullscreen-ad flow
68
+ * never resolves a Promise; it fires events instead.
69
+ */
70
+ type StubEntry = {
71
+ kind: 'promise'; /** Pure factory so each call gets a fresh value/error (no shared mutable state). */
72
+ resolve: (args: unknown) => unknown;
73
+ } | {
74
+ kind: 'event';
75
+ /**
76
+ * Invoked synchronously with the same `args` the real call would
77
+ * receive; calls `args.onEvent`/`args.onError` per the fixture and
78
+ * returns a no-op cleanup function.
79
+ */
80
+ run: (args: {
81
+ onEvent: (e: unknown) => void;
82
+ onError: (e: unknown) => void;
83
+ }) => () => void;
84
+ };
85
+ /**
86
+ * Fixture-shaped default responses for the blocking-UI APIs named in
87
+ * devtools#740 as the smallest viable surface: ads `show*`, permissions
88
+ * `openPermissionDialog`/`requestPermission`, storage `saveBase64Data`.
89
+ *
90
+ * Each entry cites the exact `.ait-capture` record (sdk-example run11,
91
+ * 2026-07-05, 2.x/iOS) it was derived from — see the PR body for the full
92
+ * source table. Only the REPRESENTATIVE shape is kept (one success + one
93
+ * native-error per API where the capture contains both); this is a fixed v1
94
+ * default set, not a scenario-selectable registry — see `resolveStubOutcome`
95
+ * for how a test can still opt into the error branch.
96
+ */
97
+ declare const STUB_REGISTRY: Record<string, StubEntry>;
98
+ /** The allowlisted API names {@link STUB_REGISTRY} can intercept. */
99
+ type StubbableApiName = keyof typeof STUB_REGISTRY;
100
+ /**
101
+ * Wraps a `window.__sdk`-shaped bridge object so that calls to any name in
102
+ * {@link STUB_REGISTRY} are answered from the fixture instead of forwarded to
103
+ * `sdk`. Every other property (including non-stubbed functions) passes
104
+ * through UNCHANGED — this is a targeted intercept, not a blanket mock.
105
+ *
106
+ * Returns `sdk` itself, untouched, when `enabled` is false — the exact
107
+ * object identity is preserved so the zero-diff-when-off contract holds even
108
+ * under strict-equality checks in a caller.
109
+ *
110
+ * @param sdk - The real bridge namespace object (`window.__sdk`).
111
+ * @param enabled - Gate — mirrors the `--stub-blocking` CLI flag /
112
+ * `__AIT_STUB_BLOCKING__` page global. Defaults to `false`.
113
+ */
114
+ declare function wrapSdkWithStub(sdk: Record<string, unknown>, enabled: boolean): Record<string, unknown>;
115
+ /**
116
+ * Reads the page-global stub-blocking gate. Mirrors the pattern
117
+ * `runtime.ts`/`cell.ts` use for `__AIT_CELL__`/`__AIT_PERMS__` — a plain
118
+ * `globalThis` property injected via `injectGlobals` (Node side, `cell.ts`)
119
+ * BEFORE the first test bundle runs. Absent/false = disabled (default),
120
+ * matching every other opt-in flag in this runner.
121
+ */
122
+ declare function isStubBlockingEnabled(): boolean;
123
+ //#endregion
124
+ export { NativeBridgeErrorShape, STUB_REGISTRY, StubEntry, StubbableApiName, isStubBlockingEnabled, wrapSdkWithStub };
125
+ //# sourceMappingURL=bridge-stub.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge-stub.d.ts","names":[],"sources":["../../src/test-runner/bridge-stub.ts"],"mappings":";;AAoDA;;;;;;;;;;;;;AAkCA;;;;;;;;;;;;;;;;AA4BA;;;;;AAgDA;;;;;AAgBA;;;;;;;;;;AAoCA;UAlKiB,sBAAA;EACf,IAAA;EACA,IAAA;EACA,OAAA;EACA,QAAA,EAAU,MAAA;EACV,UAAA;EACA,SAAA;AAAA;;;;;;;;;;KA4BU,SAAA;EAEN,IAAA;EAEA,OAAA,GAAU,IAAA;AAAA;EAGV,IAAA;;;;;;EAMA,GAAA,GAAM,IAAA;IAAQ,OAAA,GAAU,CAAA;IAAqB,OAAA,GAAU,CAAA;EAAA;AAAA;;;;;;;;;;;;;cAehD,aAAA,EAAe,MAAA,SAAe,SAAA;;KAgD/B,gBAAA,gBAAgC,aAAA;;;;;;;;;;;;;;;iBAgB5B,eAAA,CACd,GAAA,EAAK,MAAA,mBACL,OAAA,YACC,MAAA;;;;;;;;iBAiCa,qBAAA,CAAA"}
@@ -0,0 +1,92 @@
1
+ //#region src/test-runner/bridge-stub.ts
2
+ /** Builds a {@link NativeBridgeErrorShape}-compatible `Error` instance. */
3
+ function makeNativeError(code, message, moduleName, userInfo = {}) {
4
+ const err = new Error(message);
5
+ err.name = "Error";
6
+ err.code = code;
7
+ err.userInfo = userInfo;
8
+ err.moduleName = moduleName;
9
+ err.__isError = true;
10
+ return err;
11
+ }
12
+ /**
13
+ * Fixture-shaped default responses for the blocking-UI APIs named in
14
+ * devtools#740 as the smallest viable surface: ads `show*`, permissions
15
+ * `openPermissionDialog`/`requestPermission`, storage `saveBase64Data`.
16
+ *
17
+ * Each entry cites the exact `.ait-capture` record (sdk-example run11,
18
+ * 2026-07-05, 2.x/iOS) it was derived from — see the PR body for the full
19
+ * source table. Only the REPRESENTATIVE shape is kept (one success + one
20
+ * native-error per API where the capture contains both); this is a fixed v1
21
+ * default set, not a scenario-selectable registry — see `resolveStubOutcome`
22
+ * for how a test can still opt into the error branch.
23
+ */
24
+ const STUB_REGISTRY = {
25
+ openPermissionDialog: {
26
+ kind: "promise",
27
+ resolve: () => "allowed"
28
+ },
29
+ requestPermission: {
30
+ kind: "promise",
31
+ resolve: () => {
32
+ throw makeNativeError("NO_PERMISSION", "No permission", "RNPermissions");
33
+ }
34
+ },
35
+ saveBase64Data: {
36
+ kind: "promise",
37
+ resolve: () => void 0
38
+ },
39
+ showFullScreenAd: {
40
+ kind: "event",
41
+ run: (args) => {
42
+ const err = /* @__PURE__ */ new Error("광고가 로드 중이거나 준비되지 않았습니다");
43
+ err.code = "1006";
44
+ args.onError(err);
45
+ return () => {};
46
+ }
47
+ }
48
+ };
49
+ /**
50
+ * Wraps a `window.__sdk`-shaped bridge object so that calls to any name in
51
+ * {@link STUB_REGISTRY} are answered from the fixture instead of forwarded to
52
+ * `sdk`. Every other property (including non-stubbed functions) passes
53
+ * through UNCHANGED — this is a targeted intercept, not a blanket mock.
54
+ *
55
+ * Returns `sdk` itself, untouched, when `enabled` is false — the exact
56
+ * object identity is preserved so the zero-diff-when-off contract holds even
57
+ * under strict-equality checks in a caller.
58
+ *
59
+ * @param sdk - The real bridge namespace object (`window.__sdk`).
60
+ * @param enabled - Gate — mirrors the `--stub-blocking` CLI flag /
61
+ * `__AIT_STUB_BLOCKING__` page global. Defaults to `false`.
62
+ */
63
+ function wrapSdkWithStub(sdk, enabled) {
64
+ if (!enabled) return sdk;
65
+ return new Proxy(sdk, { get(target, prop, receiver) {
66
+ if (typeof prop === "string" && Object.hasOwn(STUB_REGISTRY, prop)) return buildStubFunction(prop);
67
+ return Reflect.get(target, prop, receiver);
68
+ } });
69
+ }
70
+ /** Builds the callable replacement for a single stubbed API name. */
71
+ function buildStubFunction(name) {
72
+ const entry = STUB_REGISTRY[name];
73
+ if (entry.kind === "promise") return async (...args) => entry.resolve(args[0]);
74
+ return (...args) => {
75
+ const callArgs = args[0];
76
+ return entry.run(callArgs);
77
+ };
78
+ }
79
+ /**
80
+ * Reads the page-global stub-blocking gate. Mirrors the pattern
81
+ * `runtime.ts`/`cell.ts` use for `__AIT_CELL__`/`__AIT_PERMS__` — a plain
82
+ * `globalThis` property injected via `injectGlobals` (Node side, `cell.ts`)
83
+ * BEFORE the first test bundle runs. Absent/false = disabled (default),
84
+ * matching every other opt-in flag in this runner.
85
+ */
86
+ function isStubBlockingEnabled() {
87
+ return globalThis.__AIT_STUB_BLOCKING__ === true;
88
+ }
89
+ //#endregion
90
+ export { STUB_REGISTRY, isStubBlockingEnabled, wrapSdkWithStub };
91
+
92
+ //# sourceMappingURL=bridge-stub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge-stub.js","names":[],"sources":["../../src/test-runner/bridge-stub.ts"],"sourcesContent":["/**\n * Bridge-stub interceptor for env3 blocking-UI SDK calls (devtools#740, DT-2).\n *\n * `--manual-blocking` (devtools#741) lets a human tap through native-UI SDK\n * calls (fullscreen ads, permission dialogs, the save-file share sheet) so\n * their real device behaviour can be captured. That capture (sdk-example\n * run11, 2026-07-05, 2.x/iOS) is the ground truth this module replays: an\n * opt-in interceptor that sits BETWEEN the test bundle and the real native\n * bridge (`window.__sdk`, installed by `src/in-app/auto.ts`) and answers a\n * fixed allowlist of blocking calls with fixture-shaped responses instead of\n * forwarding them to native UI. This lets the SAME manual-tagged test files\n * run unattended, so their assertions (denied-permission error shape,\n * `NO_PERMISSION` envelope, etc.) become part of a CI-checkable contract\n * instead of only ever running with a human present.\n *\n * HONESTY CONTRACT (devtools#740): a stubbed run is NOT env3 in the pure\n * sense — it is a HYBRID cell (real device + real WebView + real bridge\n * transport for every OTHER call, but a synthetic answer for the handful of\n * calls in {@link STUB_REGISTRY}). Every artifact produced under this mode\n * MUST be provenance-stamped (`cell.bridgeStub: true`, see `report.ts`) and\n * MUST NOT be merged into the same file/mode as an unattended baseline run.\n * A stubbed PASS proves \"the SDK's rejection/resolution path behaves as\n * fixture-shaped\" — it does NOT prove the real native dialog/sheet still\n * looks or behaves a given way on-device. Real behavior re-verification stays\n * on `--manual-blocking` (DT-3) — this module never replaces it, only adds an\n * unattended CHECK on top of what it captured.\n *\n * DRIFT AVOIDANCE: fixtures are frozen snapshots of a real capture. If the\n * native SDK's error shape ever changes, only a fresh `--manual-blocking` run\n * (DT-3) can tell you — this module has no way to detect drift on its own.\n * Treat {@link STUB_REGISTRY} as \"last known native truth\", not as a\n * specification devtools owns.\n *\n * Runs entirely on the PAGE side (browser-compatible, no Node APIs) — this\n * file is bundled into the injected IIFE by `bundle.ts`'s `sdkRedirectPlugin`,\n * mirroring how `runtime.ts` is bundled. It is imported ONLY from the\n * test-runner (never from `sdk-example/src` — boilerplate cleanliness,\n * umbrella §1.4: this is maintainer/runner infrastructure, not something a\n * mini-app developer's shipped code should carry).\n *\n * SECRET-HANDLING: fixtures below are hand-derived summaries of the captured\n * shapes (see per-entry doc comments citing the source scenario) — no device\n * identifiers, tunnel hosts, relay URLs, or auth codes are embedded anywhere\n * in this file.\n */\n\n/**\n * The exact shape a rejected native bridge call takes on iOS/2.x (and,\n * empirically, unchanged across the manual captures this module replays).\n * Mirrors the `errorKeys` observed in the ground-truth capture:\n * `['name', 'code', 'userInfo', 'moduleName', '__isError']`.\n */\nexport interface NativeBridgeErrorShape {\n name: string;\n code: string;\n message: string;\n userInfo: Record<string, unknown>;\n moduleName: string;\n __isError: true;\n}\n\n/** Builds a {@link NativeBridgeErrorShape}-compatible `Error` instance. */\nfunction makeNativeError(\n code: string,\n message: string,\n moduleName: string,\n userInfo: Record<string, unknown> = {},\n): Error & NativeBridgeErrorShape {\n const err = new Error(message) as Error & NativeBridgeErrorShape;\n err.name = 'Error';\n err.code = code;\n err.userInfo = userInfo;\n err.moduleName = moduleName;\n err.__isError = true;\n return err;\n}\n\n/**\n * One blocking-call stub entry. `kind` distinguishes the two native calling\n * conventions observed in the mock/real SDK:\n * - `'promise'` — the call returns a Promise the stub resolves/rejects\n * (`openPermissionDialog`, `requestPermission`, `saveBase64Data`).\n * - `'event'` — the call takes `{ onEvent, onError }` callbacks and returns\n * a cleanup function (`showFullScreenAd`) — the native fullscreen-ad flow\n * never resolves a Promise; it fires events instead.\n */\nexport type StubEntry =\n | {\n kind: 'promise';\n /** Pure factory so each call gets a fresh value/error (no shared mutable state). */\n resolve: (args: unknown) => unknown;\n }\n | {\n kind: 'event';\n /**\n * Invoked synchronously with the same `args` the real call would\n * receive; calls `args.onEvent`/`args.onError` per the fixture and\n * returns a no-op cleanup function.\n */\n run: (args: { onEvent: (e: unknown) => void; onError: (e: unknown) => void }) => () => void;\n };\n\n/**\n * Fixture-shaped default responses for the blocking-UI APIs named in\n * devtools#740 as the smallest viable surface: ads `show*`, permissions\n * `openPermissionDialog`/`requestPermission`, storage `saveBase64Data`.\n *\n * Each entry cites the exact `.ait-capture` record (sdk-example run11,\n * 2026-07-05, 2.x/iOS) it was derived from — see the PR body for the full\n * source table. Only the REPRESENTATIVE shape is kept (one success + one\n * native-error per API where the capture contains both); this is a fixed v1\n * default set, not a scenario-selectable registry — see `resolveStubOutcome`\n * for how a test can still opt into the error branch.\n */\nexport const STUB_REGISTRY: Record<string, StubEntry> = {\n // Source: permissions.2.x.ios.json — scenario \"manual-native-dialog-camera\"\n // (outcome: resolved, value: 'allowed'). The capture's own resolved value\n // was a bare string, not an object — mirrored verbatim.\n openPermissionDialog: {\n kind: 'promise',\n resolve: () => 'allowed',\n },\n\n // Source: permissions.2.x.ios.json — scenario\n // \"manual-native-request-geolocation\" (outcome: rejected, isNativeShape:\n // true, errorCode: 'NO_PERMISSION', errorMessage: 'No permission'). This is\n // the exact envelope DT-2's issue body calls out (\"requestPermission→\n // 'denied' 스텁\") — the native denial path, not a synthetic message.\n requestPermission: {\n kind: 'promise',\n resolve: () => {\n throw makeNativeError('NO_PERMISSION', 'No permission', 'RNPermissions');\n },\n },\n\n // Source: storage.2.x.ios.json — scenario \"manual-happy-varied-mime-png\"\n // (outcome: resolved, returnType: 'undefined') — the real share-sheet call\n // resolves with no value on success.\n saveBase64Data: {\n kind: 'promise',\n resolve: () => undefined,\n },\n\n // Source: ads.2.x.ios.json — scenario \"manual-show\" (outcome: rejected,\n // errorCode: '1006', errorMessage: '광고가 로드 중이거나 준비되지 않았습니다').\n // showFullScreenAd never resolves/rejects a Promise on the real bridge — it\n // reports outcomes via onEvent/onError, so the representative capture\n // outcome is replayed as an onError call using the SAME error shape family\n // (isNativeShape: false in the capture — a plain Error, not the\n // name/code/userInfo/moduleName envelope).\n showFullScreenAd: {\n kind: 'event',\n run: (args) => {\n const err = new Error('광고가 로드 중이거나 준비되지 않았습니다') as Error & { code: string };\n err.code = '1006';\n args.onError(err);\n return () => {};\n },\n },\n};\n\n/** The allowlisted API names {@link STUB_REGISTRY} can intercept. */\nexport type StubbableApiName = keyof typeof STUB_REGISTRY;\n\n/**\n * Wraps a `window.__sdk`-shaped bridge object so that calls to any name in\n * {@link STUB_REGISTRY} are answered from the fixture instead of forwarded to\n * `sdk`. Every other property (including non-stubbed functions) passes\n * through UNCHANGED — this is a targeted intercept, not a blanket mock.\n *\n * Returns `sdk` itself, untouched, when `enabled` is false — the exact\n * object identity is preserved so the zero-diff-when-off contract holds even\n * under strict-equality checks in a caller.\n *\n * @param sdk - The real bridge namespace object (`window.__sdk`).\n * @param enabled - Gate — mirrors the `--stub-blocking` CLI flag /\n * `__AIT_STUB_BLOCKING__` page global. Defaults to `false`.\n */\nexport function wrapSdkWithStub(\n sdk: Record<string, unknown>,\n enabled: boolean,\n): Record<string, unknown> {\n if (!enabled) return sdk;\n\n return new Proxy(sdk, {\n get(target, prop, receiver) {\n if (typeof prop === 'string' && Object.hasOwn(STUB_REGISTRY, prop)) {\n return buildStubFunction(prop);\n }\n return Reflect.get(target, prop, receiver);\n },\n });\n}\n\n/** Builds the callable replacement for a single stubbed API name. */\nfunction buildStubFunction(name: string): (...args: unknown[]) => unknown {\n const entry = STUB_REGISTRY[name];\n if (entry.kind === 'promise') {\n return async (...args: unknown[]) => entry.resolve(args[0]);\n }\n // 'event' kind: (args: { onEvent, onError, ... }) => cleanup\n return (...args: unknown[]) => {\n const callArgs = args[0] as { onEvent: (e: unknown) => void; onError: (e: unknown) => void };\n return entry.run(callArgs);\n };\n}\n\n/**\n * Reads the page-global stub-blocking gate. Mirrors the pattern\n * `runtime.ts`/`cell.ts` use for `__AIT_CELL__`/`__AIT_PERMS__` — a plain\n * `globalThis` property injected via `injectGlobals` (Node side, `cell.ts`)\n * BEFORE the first test bundle runs. Absent/false = disabled (default),\n * matching every other opt-in flag in this runner.\n */\nexport function isStubBlockingEnabled(): boolean {\n return (globalThis as { __AIT_STUB_BLOCKING__?: boolean }).__AIT_STUB_BLOCKING__ === true;\n}\n"],"mappings":";;AA8DA,SAAS,gBACP,MACA,SACA,YACA,WAAoC,EAAE,EACN;CAChC,MAAM,MAAM,IAAI,MAAM,QAAQ;AAC9B,KAAI,OAAO;AACX,KAAI,OAAO;AACX,KAAI,WAAW;AACf,KAAI,aAAa;AACjB,KAAI,YAAY;AAChB,QAAO;;;;;;;;;;;;;;AAwCT,MAAa,gBAA2C;CAItD,sBAAsB;EACpB,MAAM;EACN,eAAe;EAChB;CAOD,mBAAmB;EACjB,MAAM;EACN,eAAe;AACb,SAAM,gBAAgB,iBAAiB,iBAAiB,gBAAgB;;EAE3E;CAKD,gBAAgB;EACd,MAAM;EACN,eAAe,KAAA;EAChB;CASD,kBAAkB;EAChB,MAAM;EACN,MAAM,SAAS;GACb,MAAM,sBAAM,IAAI,MAAM,yBAAyB;AAC/C,OAAI,OAAO;AACX,QAAK,QAAQ,IAAI;AACjB,gBAAa;;EAEhB;CACF;;;;;;;;;;;;;;;AAmBD,SAAgB,gBACd,KACA,SACyB;AACzB,KAAI,CAAC,QAAS,QAAO;AAErB,QAAO,IAAI,MAAM,KAAK,EACpB,IAAI,QAAQ,MAAM,UAAU;AAC1B,MAAI,OAAO,SAAS,YAAY,OAAO,OAAO,eAAe,KAAK,CAChE,QAAO,kBAAkB,KAAK;AAEhC,SAAO,QAAQ,IAAI,QAAQ,MAAM,SAAS;IAE7C,CAAC;;;AAIJ,SAAS,kBAAkB,MAA+C;CACxE,MAAM,QAAQ,cAAc;AAC5B,KAAI,MAAM,SAAS,UACjB,QAAO,OAAO,GAAG,SAAoB,MAAM,QAAQ,KAAK,GAAG;AAG7D,SAAQ,GAAG,SAAoB;EAC7B,MAAM,WAAW,KAAK;AACtB,SAAO,MAAM,IAAI,SAAS;;;;;;;;;;AAW9B,SAAgB,wBAAiC;AAC/C,QAAQ,WAAmD,0BAA0B"}
@@ -93,6 +93,16 @@ const SDK_IMPORT_FILTER = new RegExp(`^${SDK_PACKAGE.replace(/[.*+?^${}()|[\]\\]
93
93
  *
94
94
  * If `window.__sdk` is absent (non-dog-food build), every access throws a
95
95
  * descriptive error rather than returning `undefined` silently.
96
+ *
97
+ * Bridge-stub interception (devtools#740, DT-2): the virtual module runs the
98
+ * resolved `window.__sdk` object through `wrapSdkWithStub` before exporting
99
+ * it. `wrapSdkWithStub` is a pass-through (returns the same object,
100
+ * unwrapped) unless `isStubBlockingEnabled()` reads `true` off
101
+ * `globalThis.__AIT_STUB_BLOCKING__` — an opt-in flag injected the same way
102
+ * `__AIT_CELL__` is (`cell.ts#injectGlobals`, wired from the CLI's
103
+ * `--stub-blocking` flag). Default off => zero behavior change: every existing
104
+ * (non-stub) test run resolves the exact same `window.__sdk` reference it did
105
+ * before this plugin learned about stubbing.
96
106
  */
97
107
  function sdkRedirectPlugin() {
98
108
  return {
@@ -107,16 +117,19 @@ function sdkRedirectPlugin() {
107
117
  namespace: "sdk-redirect"
108
118
  }, () => ({
109
119
  contents: `
110
- var __proxy = (typeof window !== 'undefined' && window.__sdk)
120
+ import { wrapSdkWithStub, isStubBlockingEnabled } from ${JSON.stringify(getBridgeStubPath())};
121
+ var __rawSdk = (typeof window !== 'undefined' && window.__sdk)
111
122
  ? window.__sdk
112
123
  : new Proxy({}, {
113
124
  get: function(_t, p) {
114
125
  throw new Error('window.__sdk is not installed — run in a dog-food build. Missing: ' + String(p));
115
126
  }
116
127
  });
128
+ var __proxy = wrapSdkWithStub(__rawSdk, isStubBlockingEnabled());
117
129
  module.exports = __proxy;
118
130
  `,
119
- loader: "js"
131
+ loader: "js",
132
+ resolveDir: process.cwd()
120
133
  }));
121
134
  }
122
135
  };
@@ -250,8 +263,9 @@ function userFactoryPlugin(absPath) {
250
263
  };
251
264
  }
252
265
  /**
253
- * Returns the absolute filesystem path to the test-runner runtime module
254
- * (dist/test-runner/runtime.js — a fully self-contained page-side bundle).
266
+ * Returns the absolute filesystem path to a page-side leaf module shipped
267
+ * alongside `bundle.ts` in dist (e.g. `runtime.js`, `bridge-stub.js`) — a
268
+ * fully self-contained module with no further page-side deps of its own.
255
269
  *
256
270
  * Rolldown code-splitting duplicates this bundling logic into shared chunks
257
271
  * emitted at ARBITRARY dist depths: the `devtools-test` CLI pulls it from
@@ -261,26 +275,30 @@ function userFactoryPlugin(absPath) {
261
275
  * is therefore wrong from at least one chunk — the live #697 regression.
262
276
  *
263
277
  * This resolves WITHOUT assuming chunk depth: from `import.meta.url`'s dir it
264
- * probes the co-located `runtime.js` and the nested `test-runner/runtime.js`,
278
+ * probes the co-located `<name>.js` and the nested `test-runner/<name>.js`,
265
279
  * then ascends one directory at a time (bounded) repeating both probes. The
266
- * nested probe catches dist/test-runner/runtime.js from the dist/ root level no
280
+ * nested probe catches dist/test-runner/<name>.js from the dist/ root level no
267
281
  * matter which depth the chunk was hoisted to (root, dist/mcp/, or a future
268
- * relocation). The build always emits dist/test-runner/runtime.js (tsdown entry
269
- * `'test-runner/runtime'`; guarded by scripts/check-test-runner-dist.sh).
282
+ * relocation). The build always emits dist/test-runner/<name>.js (tsdown entry
283
+ * `'test-runner/<name>'`; guarded by scripts/check-test-runner-dist.sh for
284
+ * `runtime`).
270
285
  *
271
286
  * An ABSOLUTE path is returned deliberately: esbuild loads it as a literal file
272
287
  * read, bypassing Node module resolution entirely, so this works identically in
273
288
  * the npx-daemon context (its own dist tree) and the consumer-CLI context
274
289
  * (the mini-app's installed @ait-co/devtools dist) — neither needs the package
275
290
  * to be node-resolvable from the caller.
291
+ *
292
+ * @param moduleName - The leaf module's basename without extension, e.g.
293
+ * `'runtime'` or `'bridge-stub'`.
276
294
  */
277
- function getRuntimePath() {
295
+ function getPageSideModulePath(moduleName) {
278
296
  const startDir = path$1.dirname(fileURLToPath(import.meta.url));
279
297
  const RELATIVE_PROBES = [
280
- ["runtime.js"],
281
- ["test-runner", "runtime.js"],
282
- ["runtime.ts"],
283
- ["test-runner", "runtime.ts"]
298
+ [`${moduleName}.js`],
299
+ ["test-runner", `${moduleName}.js`],
300
+ [`${moduleName}.ts`],
301
+ ["test-runner", `${moduleName}.ts`]
284
302
  ];
285
303
  let dir = startDir;
286
304
  for (let i = 0; i < 12; i++) {
@@ -295,7 +313,19 @@ function getRuntimePath() {
295
313
  if (parent === dir) break;
296
314
  dir = parent;
297
315
  }
298
- return path$1.join(startDir, "runtime.js");
316
+ return path$1.join(startDir, `${moduleName}.js`);
317
+ }
318
+ /** Absolute path to the test-runner page-side runtime (describe/it/test/expect). */
319
+ function getRuntimePath() {
320
+ return getPageSideModulePath("runtime");
321
+ }
322
+ /**
323
+ * Absolute path to the bridge-stub interceptor (devtools#740, DT-2) —
324
+ * `wrapSdkWithStub`/`isStubBlockingEnabled`, statically imported by the
325
+ * `sdk-redirect` virtual module.
326
+ */
327
+ function getBridgeStubPath() {
328
+ return getPageSideModulePath("bridge-stub");
299
329
  }
300
330
  /**
301
331
  * Bundles `absPath` into a single IIFE string suitable for `Runtime.evaluate`.
@@ -1 +1 @@
1
- {"version":3,"file":"bundle.js","names":["path"],"sources":["../../src/test-runner/bundle.ts"],"sourcesContent":["/**\n * esbuild-based bundler for user test files.\n *\n * Bundles a single test file into a self-contained IIFE string that can be\n * injected into a WebView via `Runtime.evaluate`. The bundle includes the\n * test runtime (`runtime.ts`), which provides `describe/it/test/expect` and\n * the `runTestModule(factory)` entry point.\n *\n * ## How the wiring works\n *\n * The bundle exposes two exports on `globalThis.__testBundle`:\n * - `runTestModule` — the runtime's entry function.\n * - `__userFactory` — an async function whose body is the user's top-level\n * test registration code (describe/it/test calls).\n *\n * The Node-side RPC (`rpc.ts`) calls:\n * `globalThis.__testBundle.runTestModule(globalThis.__testBundle.__userFactory)`\n *\n * `runTestModule` then installs `describe/it/test/expect` as globals, invokes\n * the factory (which registers all tests), runs them, and returns a `RunReport`.\n *\n * ## Why a factory wrapper is needed\n *\n * Naively adding the runtime to `entryPoints` and bundling the user file would\n * fail for two reasons:\n * 1. `describe/it/test/expect` from the runtime are module-local in the IIFE\n * scope. The user's top-level `describe(...)` calls expect them as globals —\n * they are not globals until `runTestModule` installs them.\n * 2. Even with globals pre-installed, the user file runs at IIFE-evaluation\n * time, before the RPC layer calls `runTestModule` to reset state and start\n * the test clock.\n *\n * The factory approach solves both: the user's registration code is deferred\n * into a function that `runTestModule` calls AFTER installing the globals.\n *\n * ## Factory extraction algorithm\n *\n * The `userFactoryPlugin` reads the user file and splits lines into:\n * - **top-level**: `import …` and re-export lines — kept at module scope\n * (the only valid position for static `import` in ESM).\n * - **body**: all other statements — moved into the body of the exported\n * `__userFactory` async function.\n *\n * esbuild processes the re-generated module, following each static import\n * through the normal dependency graph (including the SDK-redirect plugin).\n *\n * ## SDK redirect\n *\n * Imports of `@apps-in-toss/web-framework` (and sub-paths) are intercepted via\n * the `sdkRedirectPlugin` and replaced with a virtual `window.__sdk` proxy that\n * `src/in-app/auto.ts` installs at runtime. This works for both 2.x and 3.x SDK.\n *\n * SECRET-HANDLING: the returned bundle code is caller-managed; never log it.\n */\n\nimport { accessSync } from 'node:fs';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n// esbuild is imported for TYPES only at module scope; the runtime module is\n// loaded lazily inside `bundleTestFile` via dynamic import. esbuild runs a\n// startup invariant check (`TextEncoder().encode('') instanceof Uint8Array`)\n// that fails in a jsdom realm — a static import would break every MCP/test\n// module that merely *imports* this file's transitive graph (e.g. debug-server →\n// run_tests). Lazy load keeps esbuild off the import graph until a bundle is\n// actually built, and mirrors the cloudflared/chii dynamic-import precedent.\nimport type * as esbuild from 'esbuild';\n\n/** Options accepted by `bundleTestFile`. */\nexport interface BundleOptions {\n /**\n * Additional esbuild `external` patterns. The SDK package\n * (`@apps-in-toss/web-framework` and `@apps-in-toss/web-framework/*`) is\n * always handled by the SDK redirect plugin — callers may add more patterns\n * to be left as globals.\n */\n extraExternals?: string[];\n /**\n * Global name for the IIFE output object. Defaults to `__testBundle`.\n * The runtime entry uses this to call `__testBundle.runTestModule(__userFactory)`.\n */\n globalName?: string;\n}\n\n/**\n * The result of bundling a test file.\n * `code` is a self-contained IIFE string ready for `Runtime.evaluate`.\n */\nexport interface BundleResult {\n code: string;\n warnings: string[];\n}\n\n/** The SDK package name that mini-app test code imports from. */\nconst SDK_PACKAGE = '@apps-in-toss/web-framework';\n\n/**\n * Names the runtime installs as globals before invoking the user factory.\n * The `vitest` virtual module re-exports each as a lazy getter that reads from\n * `globalThis` at access time. Keep in sync with the globals installed in\n * `runtime.ts#runTestModule`.\n */\nconst VITEST_GLOBAL_NAMES = [\n 'describe',\n 'it',\n 'test',\n 'expect',\n 'beforeAll',\n 'afterAll',\n 'beforeEach',\n 'afterEach',\n 'vi',\n] as const;\n\n/**\n * Matches the bare SDK package and any sub-path import\n * (`@apps-in-toss/web-framework`, `@apps-in-toss/web-framework/foo`).\n * Built from {@link SDK_PACKAGE} so the package name has a single source.\n */\nconst SDK_IMPORT_FILTER = new RegExp(`^${SDK_PACKAGE.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}`);\n\n/**\n * esbuild plugin that intercepts SDK imports and redirects them to the\n * `window.__sdk` proxy that `src/in-app/auto.ts` installs at runtime.\n *\n * Strategy: for every import of `@apps-in-toss/web-framework` (or sub-paths),\n * esbuild resolves it to a virtual module that re-exports all named exports\n * via `window.__sdk[name]`. This avoids bundling the real SDK (which may not\n * be available in the test environment) while still making named imports work.\n *\n * If `window.__sdk` is absent (non-dog-food build), every access throws a\n * descriptive error rather than returning `undefined` silently.\n */\nfunction sdkRedirectPlugin(): esbuild.Plugin {\n return {\n name: 'sdk-redirect',\n setup(build) {\n // Match the bare package and any sub-path imports\n build.onResolve({ filter: SDK_IMPORT_FILTER }, (args) => ({\n path: args.path,\n namespace: 'sdk-redirect',\n }));\n\n build.onLoad({ filter: /.*/, namespace: 'sdk-redirect' }, () => ({\n // Generate a virtual CommonJS-style module so that esbuild does NOT perform\n // strict named-export matching. When `format:'iife'` bundles a CJS module,\n // it wraps it with its own __toCommonJS helper and satisfies named imports\n // via property access on the module.exports object — which is our Proxy.\n // This means `import { getPlatformOS } from '...'` becomes\n // `__proxy.getPlatformOS` at runtime, which correctly reads from window.__sdk.\n contents: `\nvar __proxy = (typeof window !== 'undefined' && window.__sdk)\n ? window.__sdk\n : new Proxy({}, {\n get: function(_t, p) {\n throw new Error('window.__sdk is not installed — run in a dog-food build. Missing: ' + String(p));\n }\n });\nmodule.exports = __proxy;\n`,\n loader: 'js',\n }));\n },\n };\n}\n\n/**\n * esbuild plugin that intercepts `import … from 'vitest'` and replaces it with\n * a virtual module that delegates every named import to `globalThis` at ACCESS\n * time (not at bundle-evaluation time).\n *\n * The runtime installs `describe/it/test/expect/beforeAll/afterAll/beforeEach/\n * afterEach/vi` as globals inside `runTestModule`, which runs AFTER the bundle\n * IIFE is evaluated. A value-copy redirect (`export var describe =\n * globalThis.describe`) would therefore capture `undefined` at evaluation time\n * and the user's `describe(...)` calls would be no-ops — registering zero tests.\n *\n * The fix defers the lookup to call time using per-name **getter** exports.\n * We emit a CommonJS module that:\n * 1. sets `__esModule = true` so esbuild's `__toESM` interop maps each named\n * import directly to a property access on the module (NOT wrapped under a\n * `default` shim — which is what happens for a bare Proxy whose own-keys\n * are empty, leaving every named import `undefined`);\n * 2. defines each global name as a getter that reads `globalThis[name]` on\n * every access. So `import { describe } from 'vitest'` compiles to\n * `import_vitest.describe`, whose getter returns the real `describe` only\n * when the factory calls it — after `runTestModule` installs the globals.\n *\n * A plain `module.exports = new Proxy(...)` does NOT work here: esbuild routes\n * the virtual module through `__toESM`, which enumerates own-keys (none on an\n * empty Proxy target) and therefore exposes zero named exports. Explicit getter\n * properties give `__toESM` real keys to map while keeping access lazy.\n */\nfunction vitestRedirectPlugin(): esbuild.Plugin {\n return {\n name: 'vitest-redirect',\n setup(build) {\n build.onResolve({ filter: /^vitest$/ }, () => ({\n path: 'vitest',\n namespace: 'vitest-redirect',\n }));\n\n build.onLoad({ filter: /^vitest$/, namespace: 'vitest-redirect' }, () => {\n const getters = VITEST_GLOBAL_NAMES.map(\n (name) =>\n `Object.defineProperty(exports, ${JSON.stringify(name)}, { enumerable: true, get: function() { return globalThis[${JSON.stringify(name)}]; } });`,\n ).join('\\n');\n // __esModule lets esbuild __toESM map named imports to these getters\n // directly. Each getter reads globalThis lazily so the value resolves at\n // call time — after runTestModule installs the globals.\n return {\n contents: `Object.defineProperty(exports, '__esModule', { value: true });\\n${getters}\\n`,\n loader: 'js',\n };\n });\n },\n };\n}\n\n/**\n * esbuild plugin that transforms the user test file into a module that exports\n * an async `__userFactory` function. The factory defers the user's top-level\n * test registration code (describe/it/test calls) so it only runs when\n * `runTestModule(__userFactory)` explicitly invokes it — AFTER the runtime has\n * installed describe/it/test/expect as globals.\n *\n * Algorithm:\n * - Import declarations and re-export statements are kept at module top-level\n * (the only valid ESM position for static `import`). A statement that spans\n * multiple lines — e.g. a named import with one member per line:\n * import {\n * appLogin,\n * getAnonymousKey,\n * } from '@apps-in-toss/web-framework';\n * is tracked as a single block: every line from the opening `import {` /\n * `export {` through the closing `from '…'` (or side-effect `'…'`) line is\n * kept together at top-level. This prevents the member lines and the\n * closing `} from '…'` line from leaking into the factory body, which would\n * leave an unterminated `import {` at module scope (the #678 env3 failure:\n * esbuild threw `Expected \"as\" but found \"{\"` on multi-line SDK imports).\n * - All other lines (describe/it/test calls, local declarations, etc.) are\n * moved into the body of the exported async factory function.\n *\n * This preserves SDK import resolution (the sdk-redirect plugin processes\n * top-level imports normally) while deferring test registration to the factory.\n */\nfunction userFactoryPlugin(absPath: string): esbuild.Plugin {\n const NAMESPACE = 'user-test-factory';\n return {\n name: 'user-test-factory',\n setup(build) {\n // Resolve the virtual \"user-test-factory\" specifier to our namespace.\n build.onResolve({ filter: /^user-test-factory$/ }, () => ({\n path: absPath,\n namespace: NAMESPACE,\n }));\n\n // Load the user file, split imports from body, wrap body in the factory.\n build.onLoad({ filter: /.*/, namespace: NAMESPACE }, async (args) => {\n const source = await fs.readFile(args.path, 'utf8');\n const lines = source.split('\\n');\n\n const topLevelLines: string[] = [];\n const bodyLines: string[] = [];\n\n // Matches `export` value declarations that cannot appear inside a\n // function body. We strip the `export` keyword so they become plain\n // declarations inside the factory.\n const EXPORT_DECLARATION_RE =\n /^(export\\s+)(default\\s+|async\\s+function\\s+|function\\s+|class\\s+|const\\s+|let\\s+|var\\s+)/;\n\n // True when `trimmed` begins a static `import` statement.\n const isImportStart = (trimmed: string): boolean =>\n trimmed.startsWith('import ') ||\n trimmed.startsWith('import{') ||\n trimmed.startsWith(\"import'\") ||\n trimmed.startsWith('import\"');\n\n // A module-scope `import`/`export … from` statement is \"complete\" on a\n // single line when it ends with a quoted module specifier (optionally\n // followed by `;`/whitespace), e.g. `… from '@x';` or side-effect\n // `import './x';`. A line ending in `{` or `,` (the common multi-line\n // named-import shape) is therefore NOT complete and must accumulate\n // further lines until the closing `from '…'` line.\n const endsStatement = (trimmed: string): boolean =>\n /['\"]\\s*;?\\s*$/.test(trimmed.replace(/\\/\\/.*$/, '').trimEnd());\n\n // When set, we are inside an unterminated multi-line import/re-export\n // block: every subsequent line stays at top level until the block ends.\n let inImportBlock = false;\n\n for (const line of lines) {\n const trimmed = line.trimStart();\n const indent = line.slice(0, line.length - trimmed.length);\n\n // Continuation of a multi-line import / re-export block — keep at\n // top level. The block ends on the line that terminates the\n // statement (closing `from '…'` / side-effect `'…'`).\n if (inImportBlock) {\n topLevelLines.push(line);\n if (endsStatement(trimmed)) {\n inImportBlock = false;\n }\n continue;\n }\n\n // Static import declarations must stay at module top level\n // (the ESM spec forbids `import` inside a function body). If the\n // statement does not terminate on this line, open an import block so\n // the member lines and closing `} from '…'` line stay top-level too.\n if (isImportStart(trimmed)) {\n topLevelLines.push(line);\n if (!endsStatement(trimmed)) {\n inImportBlock = true;\n }\n } else if (trimmed.startsWith('export ')) {\n // Determine whether this is a re-export (stays top-level) or a value\n // declaration (goes into the factory, export keyword stripped).\n const m = trimmed.match(EXPORT_DECLARATION_RE);\n if (m) {\n // Value declaration — strip `export ` and move into factory body.\n // e.g. `export function hello()` → `function hello()`\n // `export const x = 1` → `const x = 1`\n bodyLines.push(indent + trimmed.slice('export '.length));\n } else {\n // Re-export or `export type { … }` — stays at top level. A\n // multi-line `export { … } from '…'` opens an import block too.\n topLevelLines.push(line);\n if (/\\bfrom\\b/.test(trimmed) ? !endsStatement(trimmed) : trimmed.endsWith('{')) {\n inImportBlock = true;\n }\n }\n } else {\n bodyLines.push(line);\n }\n }\n\n const factoryContent = [\n ...topLevelLines,\n '',\n '// biome-ignore lint: generated factory wrapper',\n 'export default async function __userFactory(): Promise<void> {',\n ...bodyLines.map((l) => ` ${l}`),\n '}',\n ].join('\\n');\n\n return {\n contents: factoryContent,\n loader: 'ts',\n resolveDir: path.dirname(absPath),\n };\n });\n },\n };\n}\n\n/**\n * Returns the absolute filesystem path to the test-runner runtime module\n * (dist/test-runner/runtime.js — a fully self-contained page-side bundle).\n *\n * Rolldown code-splitting duplicates this bundling logic into shared chunks\n * emitted at ARBITRARY dist depths: the `devtools-test` CLI pulls it from\n * dist/test-runner/bundle.js (dir = dist/test-runner/), while the `devtools-mcp`\n * daemon (dist/mcp/cli.js) pulls it through a ROOT chunk\n * (dist/debug-server-<hash>.js, dir = dist/). A fixed `..`-hop candidate list\n * is therefore wrong from at least one chunk — the live #697 regression.\n *\n * This resolves WITHOUT assuming chunk depth: from `import.meta.url`'s dir it\n * probes the co-located `runtime.js` and the nested `test-runner/runtime.js`,\n * then ascends one directory at a time (bounded) repeating both probes. The\n * nested probe catches dist/test-runner/runtime.js from the dist/ root level no\n * matter which depth the chunk was hoisted to (root, dist/mcp/, or a future\n * relocation). The build always emits dist/test-runner/runtime.js (tsdown entry\n * `'test-runner/runtime'`; guarded by scripts/check-test-runner-dist.sh).\n *\n * An ABSOLUTE path is returned deliberately: esbuild loads it as a literal file\n * read, bypassing Node module resolution entirely, so this works identically in\n * the npx-daemon context (its own dist tree) and the consumer-CLI context\n * (the mini-app's installed @ait-co/devtools dist) — neither needs the package\n * to be node-resolvable from the caller.\n */\nfunction getRuntimePath(): string {\n const startDir = path.dirname(fileURLToPath(import.meta.url));\n // .js first so a SHIPPED chunk never feeds esbuild raw TypeScript; the .ts\n // probe is last, for the source-tree (tsx) dev case where runtime.ts sits\n // beside bundle.ts and no dist exists.\n const RELATIVE_PROBES: string[][] = [\n ['runtime.js'],\n ['test-runner', 'runtime.js'],\n ['runtime.ts'],\n ['test-runner', 'runtime.ts'],\n ];\n // Bounded ascent: a package's dist is only a few levels deep. 12 is far more\n // than any real layout (root chunk → dist is 0 hops; dist/mcp → dist is 1)\n // and terminates well before the filesystem root in every case.\n let dir = startDir;\n for (let i = 0; i < 12; i++) {\n for (const segs of RELATIVE_PROBES) {\n const candidate = path.join(dir, ...segs);\n try {\n accessSync(candidate);\n return candidate;\n } catch {\n // try next probe\n }\n }\n const parent = path.dirname(dir);\n if (parent === dir) break; // reached filesystem root\n dir = parent;\n }\n // Nothing matched — return the co-located path so esbuild emits a clear\n // \"Could not resolve\" against a concrete path rather than failing cryptically.\n return path.join(startDir, 'runtime.js');\n}\n\n/**\n * Bundles `absPath` into a single IIFE string suitable for `Runtime.evaluate`.\n *\n * The IIFE installs `window.__testBundle` (or the custom `globalName`) with:\n * - `runTestModule` — the runtime entry (from `runtime.ts`).\n * - `__userFactory` — an async function wrapping the user's test registration\n * code so it runs AFTER `runTestModule` installs the globals.\n *\n * Callers (rpc.ts) invoke:\n * `globalThis.__testBundle.runTestModule(globalThis.__testBundle.__userFactory)`\n *\n * @param absPath - Absolute path to the user test file.\n * @param opts - Optional bundling overrides.\n */\nexport async function bundleTestFile(absPath: string, opts?: BundleOptions): Promise<BundleResult> {\n const globalName = opts?.globalName ?? '__testBundle';\n const extraExternals = opts?.extraExternals ?? [];\n\n // Lazy load esbuild at call time (see the module-scope import note).\n const esbuild = await import('esbuild');\n const runtimePath = getRuntimePath();\n\n // Stdin wrapper: import the runtime and the user factory, re-export both.\n // esbuild follows the static imports to include runtime.ts and the user file\n // (via the userFactoryPlugin) in the single IIFE output.\n const wrapperContent = [\n `import { runTestModule } from ${JSON.stringify(runtimePath)};`,\n `import __userFactory from \"user-test-factory\";`,\n `export { runTestModule, __userFactory };`,\n ].join('\\n');\n\n const result = await esbuild.build({\n stdin: {\n contents: wrapperContent,\n loader: 'ts',\n // resolveDir is used for relative imports from the wrapper. Since the\n // wrapper only imports absolute paths (runtimePath) and the virtual\n // \"user-test-factory\" specifier (resolved by plugin), the directory\n // doesn't matter — but we still provide a sensible default.\n resolveDir: path.dirname(absPath),\n },\n bundle: true,\n format: 'iife',\n globalName,\n platform: 'browser',\n target: 'es2022',\n write: false,\n plugins: [userFactoryPlugin(absPath), vitestRedirectPlugin(), sdkRedirectPlugin()],\n external: extraExternals,\n treeShaking: true,\n // Ensure the IIFE result is always reachable via globalThis regardless of\n // the evaluation context. esbuild's `globalName` emits:\n // var __testBundle = (() => { ... })();\n // When `Runtime.evaluate` runs this bundle code inside an outer wrapper\n // (rpc.ts's async IIFE), `var` creates a local variable — NOT a global\n // property — so `globalThis.__testBundle` stays `undefined`. The footer\n // explicitly assigns the local variable to `globalThis` to close that gap.\n footer: {\n js: `globalThis[${JSON.stringify(globalName)}] = ${globalName};`,\n },\n });\n\n const warnings = result.warnings.map(\n (w) =>\n `${path.relative(process.cwd(), w.location?.file ?? '')}:${w.location?.line ?? '?'}: ${w.text}`,\n );\n\n const outputFile = result.outputFiles?.[0];\n if (!outputFile) {\n throw new Error('bundleTestFile: esbuild produced no output — check entryPoints');\n }\n\n return { code: outputFile.text, warnings };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"bundle.js","names":["path"],"sources":["../../src/test-runner/bundle.ts"],"sourcesContent":["/**\n * esbuild-based bundler for user test files.\n *\n * Bundles a single test file into a self-contained IIFE string that can be\n * injected into a WebView via `Runtime.evaluate`. The bundle includes the\n * test runtime (`runtime.ts`), which provides `describe/it/test/expect` and\n * the `runTestModule(factory)` entry point.\n *\n * ## How the wiring works\n *\n * The bundle exposes two exports on `globalThis.__testBundle`:\n * - `runTestModule` — the runtime's entry function.\n * - `__userFactory` — an async function whose body is the user's top-level\n * test registration code (describe/it/test calls).\n *\n * The Node-side RPC (`rpc.ts`) calls:\n * `globalThis.__testBundle.runTestModule(globalThis.__testBundle.__userFactory)`\n *\n * `runTestModule` then installs `describe/it/test/expect` as globals, invokes\n * the factory (which registers all tests), runs them, and returns a `RunReport`.\n *\n * ## Why a factory wrapper is needed\n *\n * Naively adding the runtime to `entryPoints` and bundling the user file would\n * fail for two reasons:\n * 1. `describe/it/test/expect` from the runtime are module-local in the IIFE\n * scope. The user's top-level `describe(...)` calls expect them as globals —\n * they are not globals until `runTestModule` installs them.\n * 2. Even with globals pre-installed, the user file runs at IIFE-evaluation\n * time, before the RPC layer calls `runTestModule` to reset state and start\n * the test clock.\n *\n * The factory approach solves both: the user's registration code is deferred\n * into a function that `runTestModule` calls AFTER installing the globals.\n *\n * ## Factory extraction algorithm\n *\n * The `userFactoryPlugin` reads the user file and splits lines into:\n * - **top-level**: `import …` and re-export lines — kept at module scope\n * (the only valid position for static `import` in ESM).\n * - **body**: all other statements — moved into the body of the exported\n * `__userFactory` async function.\n *\n * esbuild processes the re-generated module, following each static import\n * through the normal dependency graph (including the SDK-redirect plugin).\n *\n * ## SDK redirect\n *\n * Imports of `@apps-in-toss/web-framework` (and sub-paths) are intercepted via\n * the `sdkRedirectPlugin` and replaced with a virtual `window.__sdk` proxy that\n * `src/in-app/auto.ts` installs at runtime. This works for both 2.x and 3.x SDK.\n *\n * SECRET-HANDLING: the returned bundle code is caller-managed; never log it.\n */\n\nimport { accessSync } from 'node:fs';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n// esbuild is imported for TYPES only at module scope; the runtime module is\n// loaded lazily inside `bundleTestFile` via dynamic import. esbuild runs a\n// startup invariant check (`TextEncoder().encode('') instanceof Uint8Array`)\n// that fails in a jsdom realm — a static import would break every MCP/test\n// module that merely *imports* this file's transitive graph (e.g. debug-server →\n// run_tests). Lazy load keeps esbuild off the import graph until a bundle is\n// actually built, and mirrors the cloudflared/chii dynamic-import precedent.\nimport type * as esbuild from 'esbuild';\n\n/** Options accepted by `bundleTestFile`. */\nexport interface BundleOptions {\n /**\n * Additional esbuild `external` patterns. The SDK package\n * (`@apps-in-toss/web-framework` and `@apps-in-toss/web-framework/*`) is\n * always handled by the SDK redirect plugin — callers may add more patterns\n * to be left as globals.\n */\n extraExternals?: string[];\n /**\n * Global name for the IIFE output object. Defaults to `__testBundle`.\n * The runtime entry uses this to call `__testBundle.runTestModule(__userFactory)`.\n */\n globalName?: string;\n}\n\n/**\n * The result of bundling a test file.\n * `code` is a self-contained IIFE string ready for `Runtime.evaluate`.\n */\nexport interface BundleResult {\n code: string;\n warnings: string[];\n}\n\n/** The SDK package name that mini-app test code imports from. */\nconst SDK_PACKAGE = '@apps-in-toss/web-framework';\n\n/**\n * Names the runtime installs as globals before invoking the user factory.\n * The `vitest` virtual module re-exports each as a lazy getter that reads from\n * `globalThis` at access time. Keep in sync with the globals installed in\n * `runtime.ts#runTestModule`.\n */\nconst VITEST_GLOBAL_NAMES = [\n 'describe',\n 'it',\n 'test',\n 'expect',\n 'beforeAll',\n 'afterAll',\n 'beforeEach',\n 'afterEach',\n 'vi',\n] as const;\n\n/**\n * Matches the bare SDK package and any sub-path import\n * (`@apps-in-toss/web-framework`, `@apps-in-toss/web-framework/foo`).\n * Built from {@link SDK_PACKAGE} so the package name has a single source.\n */\nconst SDK_IMPORT_FILTER = new RegExp(`^${SDK_PACKAGE.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}`);\n\n/**\n * esbuild plugin that intercepts SDK imports and redirects them to the\n * `window.__sdk` proxy that `src/in-app/auto.ts` installs at runtime.\n *\n * Strategy: for every import of `@apps-in-toss/web-framework` (or sub-paths),\n * esbuild resolves it to a virtual module that re-exports all named exports\n * via `window.__sdk[name]`. This avoids bundling the real SDK (which may not\n * be available in the test environment) while still making named imports work.\n *\n * If `window.__sdk` is absent (non-dog-food build), every access throws a\n * descriptive error rather than returning `undefined` silently.\n *\n * Bridge-stub interception (devtools#740, DT-2): the virtual module runs the\n * resolved `window.__sdk` object through `wrapSdkWithStub` before exporting\n * it. `wrapSdkWithStub` is a pass-through (returns the same object,\n * unwrapped) unless `isStubBlockingEnabled()` reads `true` off\n * `globalThis.__AIT_STUB_BLOCKING__` — an opt-in flag injected the same way\n * `__AIT_CELL__` is (`cell.ts#injectGlobals`, wired from the CLI's\n * `--stub-blocking` flag). Default off => zero behavior change: every existing\n * (non-stub) test run resolves the exact same `window.__sdk` reference it did\n * before this plugin learned about stubbing.\n */\nfunction sdkRedirectPlugin(): esbuild.Plugin {\n return {\n name: 'sdk-redirect',\n setup(build) {\n // Match the bare package and any sub-path imports\n build.onResolve({ filter: SDK_IMPORT_FILTER }, (args) => ({\n path: args.path,\n namespace: 'sdk-redirect',\n }));\n\n build.onLoad({ filter: /.*/, namespace: 'sdk-redirect' }, () => ({\n // Generate a virtual CommonJS-style module so that esbuild does NOT perform\n // strict named-export matching. When `format:'iife'` bundles a CJS module,\n // it wraps it with its own __toCommonJS helper and satisfies named imports\n // via property access on the module.exports object — which is our Proxy.\n // This means `import { getPlatformOS } from '...'` becomes\n // `__proxy.getPlatformOS` at runtime, which correctly reads from window.__sdk.\n //\n // The bridge-stub import is a STATIC import of the real `bridge-stub.ts`\n // module (not inlined JS) so esbuild bundles it once and the fixture\n // registry has a single source of truth shared with its own unit tests.\n contents: `\nimport { wrapSdkWithStub, isStubBlockingEnabled } from ${JSON.stringify(getBridgeStubPath())};\nvar __rawSdk = (typeof window !== 'undefined' && window.__sdk)\n ? window.__sdk\n : new Proxy({}, {\n get: function(_t, p) {\n throw new Error('window.__sdk is not installed — run in a dog-food build. Missing: ' + String(p));\n }\n });\nvar __proxy = wrapSdkWithStub(__rawSdk, isStubBlockingEnabled());\nmodule.exports = __proxy;\n`,\n loader: 'js',\n // resolveDir is required so esbuild can resolve the absolute\n // getBridgeStubPath() import above from a filesystem base — a virtual\n // module has no implicit directory of its own. process.cwd() is a\n // safe base for an ABSOLUTE import specifier (it is never used to\n // resolve anything relative).\n resolveDir: process.cwd(),\n }));\n },\n };\n}\n\n/**\n * esbuild plugin that intercepts `import … from 'vitest'` and replaces it with\n * a virtual module that delegates every named import to `globalThis` at ACCESS\n * time (not at bundle-evaluation time).\n *\n * The runtime installs `describe/it/test/expect/beforeAll/afterAll/beforeEach/\n * afterEach/vi` as globals inside `runTestModule`, which runs AFTER the bundle\n * IIFE is evaluated. A value-copy redirect (`export var describe =\n * globalThis.describe`) would therefore capture `undefined` at evaluation time\n * and the user's `describe(...)` calls would be no-ops — registering zero tests.\n *\n * The fix defers the lookup to call time using per-name **getter** exports.\n * We emit a CommonJS module that:\n * 1. sets `__esModule = true` so esbuild's `__toESM` interop maps each named\n * import directly to a property access on the module (NOT wrapped under a\n * `default` shim — which is what happens for a bare Proxy whose own-keys\n * are empty, leaving every named import `undefined`);\n * 2. defines each global name as a getter that reads `globalThis[name]` on\n * every access. So `import { describe } from 'vitest'` compiles to\n * `import_vitest.describe`, whose getter returns the real `describe` only\n * when the factory calls it — after `runTestModule` installs the globals.\n *\n * A plain `module.exports = new Proxy(...)` does NOT work here: esbuild routes\n * the virtual module through `__toESM`, which enumerates own-keys (none on an\n * empty Proxy target) and therefore exposes zero named exports. Explicit getter\n * properties give `__toESM` real keys to map while keeping access lazy.\n */\nfunction vitestRedirectPlugin(): esbuild.Plugin {\n return {\n name: 'vitest-redirect',\n setup(build) {\n build.onResolve({ filter: /^vitest$/ }, () => ({\n path: 'vitest',\n namespace: 'vitest-redirect',\n }));\n\n build.onLoad({ filter: /^vitest$/, namespace: 'vitest-redirect' }, () => {\n const getters = VITEST_GLOBAL_NAMES.map(\n (name) =>\n `Object.defineProperty(exports, ${JSON.stringify(name)}, { enumerable: true, get: function() { return globalThis[${JSON.stringify(name)}]; } });`,\n ).join('\\n');\n // __esModule lets esbuild __toESM map named imports to these getters\n // directly. Each getter reads globalThis lazily so the value resolves at\n // call time — after runTestModule installs the globals.\n return {\n contents: `Object.defineProperty(exports, '__esModule', { value: true });\\n${getters}\\n`,\n loader: 'js',\n };\n });\n },\n };\n}\n\n/**\n * esbuild plugin that transforms the user test file into a module that exports\n * an async `__userFactory` function. The factory defers the user's top-level\n * test registration code (describe/it/test calls) so it only runs when\n * `runTestModule(__userFactory)` explicitly invokes it — AFTER the runtime has\n * installed describe/it/test/expect as globals.\n *\n * Algorithm:\n * - Import declarations and re-export statements are kept at module top-level\n * (the only valid ESM position for static `import`). A statement that spans\n * multiple lines — e.g. a named import with one member per line:\n * import {\n * appLogin,\n * getAnonymousKey,\n * } from '@apps-in-toss/web-framework';\n * is tracked as a single block: every line from the opening `import {` /\n * `export {` through the closing `from '…'` (or side-effect `'…'`) line is\n * kept together at top-level. This prevents the member lines and the\n * closing `} from '…'` line from leaking into the factory body, which would\n * leave an unterminated `import {` at module scope (the #678 env3 failure:\n * esbuild threw `Expected \"as\" but found \"{\"` on multi-line SDK imports).\n * - All other lines (describe/it/test calls, local declarations, etc.) are\n * moved into the body of the exported async factory function.\n *\n * This preserves SDK import resolution (the sdk-redirect plugin processes\n * top-level imports normally) while deferring test registration to the factory.\n */\nfunction userFactoryPlugin(absPath: string): esbuild.Plugin {\n const NAMESPACE = 'user-test-factory';\n return {\n name: 'user-test-factory',\n setup(build) {\n // Resolve the virtual \"user-test-factory\" specifier to our namespace.\n build.onResolve({ filter: /^user-test-factory$/ }, () => ({\n path: absPath,\n namespace: NAMESPACE,\n }));\n\n // Load the user file, split imports from body, wrap body in the factory.\n build.onLoad({ filter: /.*/, namespace: NAMESPACE }, async (args) => {\n const source = await fs.readFile(args.path, 'utf8');\n const lines = source.split('\\n');\n\n const topLevelLines: string[] = [];\n const bodyLines: string[] = [];\n\n // Matches `export` value declarations that cannot appear inside a\n // function body. We strip the `export` keyword so they become plain\n // declarations inside the factory.\n const EXPORT_DECLARATION_RE =\n /^(export\\s+)(default\\s+|async\\s+function\\s+|function\\s+|class\\s+|const\\s+|let\\s+|var\\s+)/;\n\n // True when `trimmed` begins a static `import` statement.\n const isImportStart = (trimmed: string): boolean =>\n trimmed.startsWith('import ') ||\n trimmed.startsWith('import{') ||\n trimmed.startsWith(\"import'\") ||\n trimmed.startsWith('import\"');\n\n // A module-scope `import`/`export … from` statement is \"complete\" on a\n // single line when it ends with a quoted module specifier (optionally\n // followed by `;`/whitespace), e.g. `… from '@x';` or side-effect\n // `import './x';`. A line ending in `{` or `,` (the common multi-line\n // named-import shape) is therefore NOT complete and must accumulate\n // further lines until the closing `from '…'` line.\n const endsStatement = (trimmed: string): boolean =>\n /['\"]\\s*;?\\s*$/.test(trimmed.replace(/\\/\\/.*$/, '').trimEnd());\n\n // When set, we are inside an unterminated multi-line import/re-export\n // block: every subsequent line stays at top level until the block ends.\n let inImportBlock = false;\n\n for (const line of lines) {\n const trimmed = line.trimStart();\n const indent = line.slice(0, line.length - trimmed.length);\n\n // Continuation of a multi-line import / re-export block — keep at\n // top level. The block ends on the line that terminates the\n // statement (closing `from '…'` / side-effect `'…'`).\n if (inImportBlock) {\n topLevelLines.push(line);\n if (endsStatement(trimmed)) {\n inImportBlock = false;\n }\n continue;\n }\n\n // Static import declarations must stay at module top level\n // (the ESM spec forbids `import` inside a function body). If the\n // statement does not terminate on this line, open an import block so\n // the member lines and closing `} from '…'` line stay top-level too.\n if (isImportStart(trimmed)) {\n topLevelLines.push(line);\n if (!endsStatement(trimmed)) {\n inImportBlock = true;\n }\n } else if (trimmed.startsWith('export ')) {\n // Determine whether this is a re-export (stays top-level) or a value\n // declaration (goes into the factory, export keyword stripped).\n const m = trimmed.match(EXPORT_DECLARATION_RE);\n if (m) {\n // Value declaration — strip `export ` and move into factory body.\n // e.g. `export function hello()` → `function hello()`\n // `export const x = 1` → `const x = 1`\n bodyLines.push(indent + trimmed.slice('export '.length));\n } else {\n // Re-export or `export type { … }` — stays at top level. A\n // multi-line `export { … } from '…'` opens an import block too.\n topLevelLines.push(line);\n if (/\\bfrom\\b/.test(trimmed) ? !endsStatement(trimmed) : trimmed.endsWith('{')) {\n inImportBlock = true;\n }\n }\n } else {\n bodyLines.push(line);\n }\n }\n\n const factoryContent = [\n ...topLevelLines,\n '',\n '// biome-ignore lint: generated factory wrapper',\n 'export default async function __userFactory(): Promise<void> {',\n ...bodyLines.map((l) => ` ${l}`),\n '}',\n ].join('\\n');\n\n return {\n contents: factoryContent,\n loader: 'ts',\n resolveDir: path.dirname(absPath),\n };\n });\n },\n };\n}\n\n/**\n * Returns the absolute filesystem path to a page-side leaf module shipped\n * alongside `bundle.ts` in dist (e.g. `runtime.js`, `bridge-stub.js`) — a\n * fully self-contained module with no further page-side deps of its own.\n *\n * Rolldown code-splitting duplicates this bundling logic into shared chunks\n * emitted at ARBITRARY dist depths: the `devtools-test` CLI pulls it from\n * dist/test-runner/bundle.js (dir = dist/test-runner/), while the `devtools-mcp`\n * daemon (dist/mcp/cli.js) pulls it through a ROOT chunk\n * (dist/debug-server-<hash>.js, dir = dist/). A fixed `..`-hop candidate list\n * is therefore wrong from at least one chunk — the live #697 regression.\n *\n * This resolves WITHOUT assuming chunk depth: from `import.meta.url`'s dir it\n * probes the co-located `<name>.js` and the nested `test-runner/<name>.js`,\n * then ascends one directory at a time (bounded) repeating both probes. The\n * nested probe catches dist/test-runner/<name>.js from the dist/ root level no\n * matter which depth the chunk was hoisted to (root, dist/mcp/, or a future\n * relocation). The build always emits dist/test-runner/<name>.js (tsdown entry\n * `'test-runner/<name>'`; guarded by scripts/check-test-runner-dist.sh for\n * `runtime`).\n *\n * An ABSOLUTE path is returned deliberately: esbuild loads it as a literal file\n * read, bypassing Node module resolution entirely, so this works identically in\n * the npx-daemon context (its own dist tree) and the consumer-CLI context\n * (the mini-app's installed @ait-co/devtools dist) — neither needs the package\n * to be node-resolvable from the caller.\n *\n * @param moduleName - The leaf module's basename without extension, e.g.\n * `'runtime'` or `'bridge-stub'`.\n */\nfunction getPageSideModulePath(moduleName: string): string {\n const startDir = path.dirname(fileURLToPath(import.meta.url));\n // .js first so a SHIPPED chunk never feeds esbuild raw TypeScript; the .ts\n // probe is last, for the source-tree (tsx) dev case where the module sits\n // beside bundle.ts and no dist exists.\n const RELATIVE_PROBES: string[][] = [\n [`${moduleName}.js`],\n ['test-runner', `${moduleName}.js`],\n [`${moduleName}.ts`],\n ['test-runner', `${moduleName}.ts`],\n ];\n // Bounded ascent: a package's dist is only a few levels deep. 12 is far more\n // than any real layout (root chunk → dist is 0 hops; dist/mcp → dist is 1)\n // and terminates well before the filesystem root in every case.\n let dir = startDir;\n for (let i = 0; i < 12; i++) {\n for (const segs of RELATIVE_PROBES) {\n const candidate = path.join(dir, ...segs);\n try {\n accessSync(candidate);\n return candidate;\n } catch {\n // try next probe\n }\n }\n const parent = path.dirname(dir);\n if (parent === dir) break; // reached filesystem root\n dir = parent;\n }\n // Nothing matched — return the co-located path so esbuild emits a clear\n // \"Could not resolve\" against a concrete path rather than failing cryptically.\n return path.join(startDir, `${moduleName}.js`);\n}\n\n/** Absolute path to the test-runner page-side runtime (describe/it/test/expect). */\nfunction getRuntimePath(): string {\n return getPageSideModulePath('runtime');\n}\n\n/**\n * Absolute path to the bridge-stub interceptor (devtools#740, DT-2) —\n * `wrapSdkWithStub`/`isStubBlockingEnabled`, statically imported by the\n * `sdk-redirect` virtual module.\n */\nfunction getBridgeStubPath(): string {\n return getPageSideModulePath('bridge-stub');\n}\n\n/**\n * Bundles `absPath` into a single IIFE string suitable for `Runtime.evaluate`.\n *\n * The IIFE installs `window.__testBundle` (or the custom `globalName`) with:\n * - `runTestModule` — the runtime entry (from `runtime.ts`).\n * - `__userFactory` — an async function wrapping the user's test registration\n * code so it runs AFTER `runTestModule` installs the globals.\n *\n * Callers (rpc.ts) invoke:\n * `globalThis.__testBundle.runTestModule(globalThis.__testBundle.__userFactory)`\n *\n * @param absPath - Absolute path to the user test file.\n * @param opts - Optional bundling overrides.\n */\nexport async function bundleTestFile(absPath: string, opts?: BundleOptions): Promise<BundleResult> {\n const globalName = opts?.globalName ?? '__testBundle';\n const extraExternals = opts?.extraExternals ?? [];\n\n // Lazy load esbuild at call time (see the module-scope import note).\n const esbuild = await import('esbuild');\n const runtimePath = getRuntimePath();\n\n // Stdin wrapper: import the runtime and the user factory, re-export both.\n // esbuild follows the static imports to include runtime.ts and the user file\n // (via the userFactoryPlugin) in the single IIFE output.\n const wrapperContent = [\n `import { runTestModule } from ${JSON.stringify(runtimePath)};`,\n `import __userFactory from \"user-test-factory\";`,\n `export { runTestModule, __userFactory };`,\n ].join('\\n');\n\n const result = await esbuild.build({\n stdin: {\n contents: wrapperContent,\n loader: 'ts',\n // resolveDir is used for relative imports from the wrapper. Since the\n // wrapper only imports absolute paths (runtimePath) and the virtual\n // \"user-test-factory\" specifier (resolved by plugin), the directory\n // doesn't matter — but we still provide a sensible default.\n resolveDir: path.dirname(absPath),\n },\n bundle: true,\n format: 'iife',\n globalName,\n platform: 'browser',\n target: 'es2022',\n write: false,\n plugins: [userFactoryPlugin(absPath), vitestRedirectPlugin(), sdkRedirectPlugin()],\n external: extraExternals,\n treeShaking: true,\n // Ensure the IIFE result is always reachable via globalThis regardless of\n // the evaluation context. esbuild's `globalName` emits:\n // var __testBundle = (() => { ... })();\n // When `Runtime.evaluate` runs this bundle code inside an outer wrapper\n // (rpc.ts's async IIFE), `var` creates a local variable — NOT a global\n // property — so `globalThis.__testBundle` stays `undefined`. The footer\n // explicitly assigns the local variable to `globalThis` to close that gap.\n footer: {\n js: `globalThis[${JSON.stringify(globalName)}] = ${globalName};`,\n },\n });\n\n const warnings = result.warnings.map(\n (w) =>\n `${path.relative(process.cwd(), w.location?.file ?? '')}:${w.location?.line ?? '?'}: ${w.text}`,\n );\n\n const outputFile = result.outputFiles?.[0];\n if (!outputFile) {\n throw new Error('bundleTestFile: esbuild produced no output — check entryPoints');\n }\n\n return { code: outputFile.text, warnings };\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { i as createRelayPool, n as RelayConnectionFactory, t as RELAY_POOL_NAME } from "../pool-C6TgrcyW.js";
1
+ import { i as createRelayPool, n as RelayConnectionFactory, t as RELAY_POOL_NAME } from "../pool-CmfvXnwh.js";
2
2
 
3
3
  //#region src/test-runner/relay-factory.d.ts
4
4
  /** Options for {@link createRelayConnectionFactory}. */
@@ -35,6 +35,16 @@ interface RelayConnectionFactoryOptions {
35
35
  sdkLine: string;
36
36
  platform: string;
37
37
  };
38
+ /**
39
+ * When `true`, injects `__AIT_STUB_BLOCKING__ = true` before the first test
40
+ * bundle runs (devtools#740, DT-2) — `bundle.ts`'s sdk-redirect module reads
41
+ * this flag via `isStubBlockingEnabled()` and answers the blocking-UI APIs
42
+ * in `bridge-stub.ts`'s `STUB_REGISTRY` from fixtures instead of forwarding
43
+ * them to native. Optional — omitted/false means no flag is injected, which
44
+ * is byte-for-byte the pre-#740 behavior (the sdk-redirect module treats
45
+ * absence the same as `false`). Not a secret.
46
+ */
47
+ stubBlocking?: boolean;
38
48
  /**
39
49
  * Overrides the dashboard HTTP server's bind base port (devtools#752).
40
50
  * Threaded straight through to `startQrHttpServer`'s `dashboardPort`
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","names":[],"sources":["../../src/test-runner/relay-factory.ts","../../src/test-runner/config.ts"],"mappings":";;;;UAiDiB,6BAAA;ECPD;AAIhB;;;;EDSE,SAAA;ECPA;;;;EDYA,WAAA;ECKmC;;;;;;;;AAcrC;EDTE,SAAA;;EAEA,QAAA;ECSA;;;;;EDHA,IAAA;IAAS,OAAA;IAAiB,QAAA;EAAA;;;;;;;;EAQ1B,aAAA;ECqCc;;;;;;;;;;;;;;;;EDpBd,WAAA,GAAc,UAAA;AAAA;;;;;;;;;;;;;;;;;;iBAoBA,4BAAA,CACd,IAAA,EAAM,6BAAA,GACL,sBAAA;;;;;;UCjGc,eAAA;EDgGf;;;;EC3FA,OAAA;;;AALF;EASE,SAAA;;;;;EAKA,cAAA;AAAA;;UAIe,mBAAA;EACf,OAAA;EACA,SAAA;EACA,cAAA;AAAA;;;;;AAgBF;;;iBAAgB,qBAAA,CAAsB,UAAA,GAAa,mBAAA,GAAsB,eAAA;;;;;;AAczE;UAAiB,qBAAA;;EAEf,IAAA,EAAM,UAAA,QAAkB,eAAA;EAAxB;EAEA,OAAA;EAFwB;EAIxB,WAAA;AAAA;;UAIe,qBAAA,SAA8B,mBAAA;EAA9B;;;;;;;EAQf,UAAA,EAAY,sBAAA;EAQG;;AAkBjB;;;;;EAlBE,eAAA;AAAA;;;;;;;;;;;;;;;;iBAkBc,uBAAA,CAAwB,UAAA,EAAY,qBAAA,GAAwB,qBAAA"}
1
+ {"version":3,"file":"config.d.ts","names":[],"sources":["../../src/test-runner/relay-factory.ts","../../src/test-runner/config.ts"],"mappings":";;;;UAiDiB,6BAAA;ECPD;;AAIhB;;;EDSE,SAAA;ECRA;;;;EDaA,WAAA;ECKc;;;;;;;;;EDKd,SAAA;ECSoC;EDPpC,QAAA;ECSgB;;;;;EDHhB,IAAA;IAAS,OAAA;IAAiB,QAAA;EAAA;ECWW;;;;;;;;;EDDrC,YAAA;ECmCqC;;;;;;;ED3BrC,aAAA;;;;;;;;;;;;;;;;;EAiBA,WAAA,GAAc,UAAA;AAAA;;;;;;;;;;;;;;;;;;iBAoBA,4BAAA,CACd,IAAA,EAAM,6BAAA,GACL,sBAAA;;;AAFH;;;AAAA,UCzGiB,eAAA;ED0GT;;;;ECrGN,OAAA;;;;EAIA,SAAA;EAT8B;;;;EAc9B,cAAA;AAAA;;UAIe,mBAAA;EACf,OAAA;EACA,SAAA;EACA,cAAA;AAAA;;;;;;AAgBF;;iBAAgB,qBAAA,CAAsB,UAAA,GAAa,mBAAA,GAAsB,eAAA;;;;;;;UAcxD,qBAAA;EAAqB;EAEpC,IAAA,EAAM,UAAA,QAAkB,eAAA;EAAR;EAEhB,OAAA;EAFM;EAIN,WAAA;AAAA;;UAIe,qBAAA,SAA8B,mBAAA;EAJlC;AAIb;;;;;;EAQE,UAAA,EAAY,sBAAA;EAQZ;;;AAkBF;;;;EAlBE,eAAA;AAAA;;;;;;;;;;;;;;;;iBAkBc,uBAAA,CAAwB,UAAA,EAAY,qBAAA,GAAwB,qBAAA"}
@@ -1,5 +1,5 @@
1
1
  import { RELAY_POOL_NAME, createRelayPool } from "./pool.js";
2
- import { t as createRelayConnectionFactory } from "../relay-factory-CjqAJ3Y8.js";
2
+ import { t as createRelayConnectionFactory } from "../relay-factory-DVzpBRxz.js";
3
3
  //#region src/test-runner/config.ts
4
4
  const DEFAULT_CONFIG = {
5
5
  include: ["**/*.ait.test.ts"],
@@ -1,2 +1,2 @@
1
- import { i as createRelayPool, n as RelayConnectionFactory, r as RelayPoolOptions, t as RELAY_POOL_NAME } from "../pool-C6TgrcyW.js";
1
+ import { i as createRelayPool, n as RelayConnectionFactory, r as RelayPoolOptions, t as RELAY_POOL_NAME } from "../pool-CmfvXnwh.js";
2
2
  export { RELAY_POOL_NAME, RelayConnectionFactory, RelayPoolOptions, createRelayPool };
@@ -1,4 +1,4 @@
1
- import { i as runTestFilesOverRelay } from "../relay-worker-UK0nBfDX.js";
1
+ import { i as runTestFilesOverRelay } from "../relay-worker-6fy1eaIo.js";
2
2
  import { synthesizeFileTask, toTaskEventPacks, toTaskResultPacks } from "./task-graph.js";
3
3
  //#region src/test-runner/pool.ts
4
4
  /** The pool name Vitest matches against `getFilePoolName(project)`. */
@@ -11133,6 +11133,16 @@ interface RelayConnectionFactoryOptions {
11133
11133
  sdkLine: string;
11134
11134
  platform: string;
11135
11135
  };
11136
+ /**
11137
+ * When `true`, injects `__AIT_STUB_BLOCKING__ = true` before the first test
11138
+ * bundle runs (devtools#740, DT-2) — `bundle.ts`'s sdk-redirect module reads
11139
+ * this flag via `isStubBlockingEnabled()` and answers the blocking-UI APIs
11140
+ * in `bridge-stub.ts`'s `STUB_REGISTRY` from fixtures instead of forwarding
11141
+ * them to native. Optional — omitted/false means no flag is injected, which
11142
+ * is byte-for-byte the pre-#740 behavior (the sdk-redirect module treats
11143
+ * absence the same as `false`). Not a secret.
11144
+ */
11145
+ stubBlocking?: boolean;
11136
11146
  /**
11137
11147
  * Overrides the dashboard HTTP server's bind base port (devtools#752).
11138
11148
  * Threaded straight through to `startQrHttpServer`'s `dashboardPort`