stim 1.12.0 → 1.13.0

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 (71) hide show
  1. package/README.md +3 -1
  2. package/dist/{activity-CCZJs__P.mjs → activity-DfVKZTEB.mjs} +42 -25
  3. package/dist/{android-BQiKJYK0.mjs → android-BHfAu2mf.mjs} +1 -1
  4. package/dist/{android-B7X-8H7h.mjs → android-CD8pr8Q2.mjs} +62 -47
  5. package/dist/{android-cas-DzLPXiEn.mjs → android-cas-B9Kv9aIy.mjs} +9 -7
  6. package/dist/{android-75Sk2rIR.mjs → android-qx_31wJo.mjs} +395 -116
  7. package/dist/{app-install-CsZi0SAT.mjs → app-install-CDFY2nUL.mjs} +1 -1
  8. package/dist/{budget-DbzW5VTD.mjs → budget-E5o91NR1.mjs} +154 -37
  9. package/dist/{build-plan-CqntbR7b.mjs → build-plan-BTa93o_r.mjs} +21 -88
  10. package/dist/{build-progress-h60GPV27.mjs → build-progress-l7d-X-ZP.mjs} +75 -7
  11. package/dist/cdp-Dq_ypAlJ.mjs +115 -0
  12. package/dist/chrome-B5hWgQJS.mjs +61 -0
  13. package/dist/cli.mjs +16 -15
  14. package/dist/collector-run.mjs +14 -8
  15. package/dist/{command-output-BWCj5Vh-.mjs → command-output-BIL7rlX7.mjs} +1 -1
  16. package/dist/created-devices-Cd558h31.mjs +37 -0
  17. package/dist/{deps-CO-_7UOm.mjs → deps-2Wo81np9.mjs} +1 -1
  18. package/dist/{device-9r9xRYDa.mjs → device-Cf7vl-so.mjs} +6 -6
  19. package/dist/{device-lease-B9LAXIUH.mjs → device-lease-CL9dZf73.mjs} +52 -3
  20. package/dist/{device-pool-BoezJd-c.mjs → device-pool-MuoiyRcu.mjs} +3 -3
  21. package/dist/{device-remote-C0_o-tJH.mjs → device-remote-DT9ngZiD.mjs} +10 -10
  22. package/dist/{doctor-LfDlbR-N.mjs → doctor-D2iysNNH.mjs} +47 -18
  23. package/dist/{error-diagnostics-CCxjn31V.mjs → error-diagnostics-BoiOhck8.mjs} +6 -6
  24. package/dist/{gc-C4nQFcTX.mjs → gc-eA-PRBX8.mjs} +853 -126
  25. package/dist/{guide-BPBlh9ML.mjs → guide-BfZ0OmiP.mjs} +736 -110
  26. package/dist/{guide-status-i9ogHt4v.mjs → guide-status-BaMYu55M.mjs} +1 -1
  27. package/dist/{idle-S3tFZ53_.mjs → idle-DxLi5OkN.mjs} +42 -9
  28. package/dist/{in-use-D9XYEgFD.mjs → in-use-wYEaKQM9.mjs} +9 -5
  29. package/dist/{ios-CDzWL1mj.mjs → ios-CcfCeZ19.mjs} +98 -54
  30. package/dist/{ios-CZWOHTTc.mjs → ios-CmEzZiOj.mjs} +10 -5
  31. package/dist/{ios-device-D9cLxVr5.mjs → ios-device-BW-t_Lkv.mjs} +1 -1
  32. package/dist/{launch-verify-B2oR_kFN.mjs → launch-verify-CuJaBpEa.mjs} +33 -20
  33. package/dist/{logs-SzuYudOj.mjs → logs-DcSRbC2t.mjs} +11 -10
  34. package/dist/{metro-Dzo30hvJ.mjs → metro-BPeY1YMh.mjs} +4 -4
  35. package/dist/{named-ports-Dn1o61D9.mjs → named-ports-n7tnJsI8.mjs} +78 -6
  36. package/dist/native-runtime-CiiFHt0e.mjs +74 -0
  37. package/dist/{ownership-Dqnrjrio.mjs → ownership-Bz2kETAS.mjs} +1 -1
  38. package/dist/{ownership-B8d4kXBx.mjs → ownership-PUP4Dwai.mjs} +134 -209
  39. package/dist/page-BTu7I60K.mjs +29 -0
  40. package/dist/{ports-Dp_QTwTC.mjs → ports-C5UutqpJ.mjs} +4 -4
  41. package/dist/{project-B8rbutVC.mjs → project-DGeclhvO.mjs} +53 -17
  42. package/dist/{reload-nocmx8KB.mjs → reload-C-cV5yy_.mjs} +79 -20
  43. package/dist/{remote-cache-BRsRZru5.mjs → remote-cache--aehr4Pr.mjs} +3 -3
  44. package/dist/{server-bare-Gn3ssSdK.mjs → server-bare-Bq8BpSIo.mjs} +1 -1
  45. package/dist/server-expo-BeYwP_Xy.mjs +2 -0
  46. package/dist/{server-expo-CeT1nIIk.mjs → server-expo-DUCg52-K.mjs} +2 -2
  47. package/dist/{settings-bE-Gu3Ti.mjs → settings-ssVBToDR.mjs} +10 -10
  48. package/dist/{settings-BlyDn2Oz.mjs → settings-xB0Frkg6.mjs} +11 -2
  49. package/dist/settings.schema.json +53 -1
  50. package/dist/{simslim-COtEEP7x.mjs → simslim-Bbg9UWrt.mjs} +12 -6
  51. package/dist/{slot-launch-DIACGEfH.mjs → slot-launch-BIWEW-wI.mjs} +3 -3
  52. package/dist/{start-BboE1HIS.mjs → start-BZhItkiL.mjs} +11 -10
  53. package/dist/{start-BFstVBlx.mjs → start-Rw2GD3nV.mjs} +1 -1
  54. package/dist/{state-DcDgzfu3.mjs → state-DwAU2_-D.mjs} +1 -1
  55. package/dist/state-l1pYwOre.mjs +148 -0
  56. package/dist/{stats-DiCtmluY.mjs → stats-CaSwmsSZ.mjs} +2 -2
  57. package/dist/{status-DI9Tjo7T.mjs → status-TJlL0HWo.mjs} +624 -103
  58. package/dist/{status-BUASYyy_.mjs → status-sszP4XKU.mjs} +59 -2
  59. package/dist/{stim-desktop-D0xhpZt8.mjs → stim-desktop-CXt8-Z3J.mjs} +13 -2
  60. package/dist/{stop-Bn6v-GvS.mjs → stop-BZ2qMiBP.mjs} +37 -12
  61. package/dist/{stop-BI7QY76M.mjs → stop-DskDKNTU.mjs} +2 -2
  62. package/dist/supervisor-run.mjs +10 -10
  63. package/dist/web-run.d.mts +22 -0
  64. package/dist/web-run.mjs +481 -0
  65. package/dist/web-sWXd5iek.mjs +394 -0
  66. package/dist/{worktree-B5sTRskL.mjs → worktree-3cBa-bQt.mjs} +1 -1
  67. package/dist/{worktree-ChDL5fO1.mjs → worktree-D4Do_XiQ.mjs} +226 -19
  68. package/dist/{xcode-BzplpQzc.mjs → xcode-LM6A8cNZ.mjs} +5 -5
  69. package/package.json +5 -5
  70. package/dist/server-expo-eWi9Coof.mjs +0 -2
  71. package/dist/workspace-process-lock-BvvJTfo_.mjs +0 -57
@@ -0,0 +1,481 @@
1
+ import { A as ownership_claim_exports, O as process_identity_exports } from "./config-0jCcMM0E.mjs";
2
+ import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
+ import { r as createNdjsonWriter } from "./ndjson-CDY9IkVC.mjs";
4
+ import { l as signalProcessTree } from "./metro-BPeY1YMh.mjs";
5
+ import { t as relaunchWithLogFile } from "./detached-entry-DJHoca49.mjs";
6
+ import { r as recordCreatedDevice } from "./created-devices-Cd558h31.mjs";
7
+ import { c as webDir, d as webProfileDir, g as removeSingletonFiles, h as liveProfileHolder, i as clearWebRecord, m as chromeProcessState, n as browserLogFile, o as updateWebRecord, p as writeWebRecord, s as webClaimRoot, u as webLogFile } from "./state-l1pYwOre.mjs";
8
+ import { t as connectOwnedBrowser } from "./cdp-Dq_ypAlJ.mjs";
9
+ import { n as PHONE_SCREEN, r as chromeArgs } from "./chrome-B5hWgQJS.mjs";
10
+ import { existsSync, mkdirSync, openSync, realpathSync } from "node:fs";
11
+ import { LOG_ROTATE_BYTES } from "@stim-cli/core";
12
+ import { WEB_VIEWPORTS } from "@stim-cli/core/state";
13
+ import { isAbsolute, resolve } from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+ //#region src/web/events.ts
16
+ const MAX_FRAMES = 20;
17
+ const CONSOLE_LEVELS = {
18
+ debug: "debug",
19
+ log: "info",
20
+ info: "info",
21
+ dir: "info",
22
+ table: "info",
23
+ trace: "info",
24
+ warning: "warn",
25
+ error: "error",
26
+ assert: "error"
27
+ };
28
+ const LOG_ENTRY_LEVELS = {
29
+ verbose: "debug",
30
+ info: "info",
31
+ warning: "warn",
32
+ error: "error"
33
+ };
34
+ function describeArg(arg) {
35
+ if (arg.type === "string") return String(arg.value);
36
+ if (arg.unserializableValue !== void 0) return arg.unserializableValue;
37
+ if (arg.type === "undefined") return "undefined";
38
+ if (arg.value !== void 0) return typeof arg.value === "object" ? JSON.stringify(arg.value) : String(arg.value);
39
+ if (arg.type === "object" && arg.subtype !== "error" && arg.preview?.properties) return describePreview(arg.preview, arg.subtype ?? arg.preview.subtype);
40
+ return arg.description ?? arg.type ?? "";
41
+ }
42
+ function describePreview(preview, subtype) {
43
+ const array = subtype === "array";
44
+ const entries = (preview.properties ?? []).map((property) => {
45
+ const value = property.type === "string" ? JSON.stringify(property.value ?? "") : property.value ?? "";
46
+ return array ? value : `${property.name}: ${value}`;
47
+ });
48
+ if (preview.overflow) entries.push("...");
49
+ return array ? `[${entries.join(", ")}]` : `{${entries.join(", ")}}`;
50
+ }
51
+ function frames(stackTrace) {
52
+ const callFrames = stackTrace?.callFrames;
53
+ if (!Array.isArray(callFrames)) return [];
54
+ return callFrames.slice(0, MAX_FRAMES).map((frame) => ({
55
+ file: frame.url || null,
56
+ line: typeof frame.lineNumber === "number" ? frame.lineNumber + 1 : null,
57
+ column: typeof frame.columnNumber === "number" ? frame.columnNumber + 1 : null,
58
+ fn: frame.functionName || null
59
+ }));
60
+ }
61
+ function withStack(record, stack) {
62
+ return stack.length ? {
63
+ ...record,
64
+ stack
65
+ } : record;
66
+ }
67
+ /** `Runtime.consoleAPICalled`: a console call in the page, as a `client` record. */
68
+ function consoleRecord(params) {
69
+ const type = String(params.type ?? "log");
70
+ const args = Array.isArray(params.args) ? params.args : [];
71
+ return withStack({
72
+ src: "client",
73
+ platform: "web",
74
+ level: CONSOLE_LEVELS[type] ?? "info",
75
+ msg: args.map(describeArg).join(" ")
76
+ }, type === "error" || type === "assert" || type === "trace" ? frames(params.stackTrace) : []);
77
+ }
78
+ /** `Runtime.exceptionThrown`: an uncaught error or rejected promise in the page, as a `client` error. */
79
+ function exceptionRecord(params) {
80
+ const details = params.exceptionDetails ?? {};
81
+ const description = (details.exception?.description ?? describeArg(details.exception ?? {})).split("\n")[0];
82
+ const prefix = details.text ?? "Uncaught";
83
+ const text = description ? `${prefix} ${description}` : prefix;
84
+ const stack = frames(details.stackTrace);
85
+ return withStack({
86
+ src: "client",
87
+ platform: "web",
88
+ level: "error",
89
+ event: "web_exception",
90
+ msg: text
91
+ }, stack.length ? stack : details.url ? [{
92
+ file: details.url,
93
+ line: typeof details.lineNumber === "number" ? details.lineNumber + 1 : null,
94
+ column: typeof details.columnNumber === "number" ? details.columnNumber + 1 : null,
95
+ fn: null
96
+ }] : []);
97
+ }
98
+ /**
99
+ * `Log.entryAdded`: a message the browser itself logged, such as a CSP or mixed-content violation. Null for its
100
+ * `network` entries, which repeat the failed requests the Network domain already reports.
101
+ */
102
+ function logEntryRecord(params) {
103
+ const entry = params.entry ?? {};
104
+ if (entry.source === "network") return null;
105
+ const where = entry.url ? ` (${entry.url})` : "";
106
+ return {
107
+ src: "device",
108
+ platform: "web",
109
+ level: LOG_ENTRY_LEVELS[entry.level ?? ""] ?? "info",
110
+ event: "web_browser_log",
111
+ msg: `${entry.source ? `${entry.source}: ` : ""}${entry.text ?? ""}${where}`
112
+ };
113
+ }
114
+ /** A request the page made that failed to load or answered with an HTTP error. */
115
+ function networkFailureRecord({ url, method, errorText, status, canceled, document }) {
116
+ const failure = status === void 0 ? errorText ?? "failed" : `HTTP ${status}`;
117
+ return {
118
+ src: "device",
119
+ platform: "web",
120
+ level: canceled ? "debug" : document || status === void 0 || status >= 500 ? "error" : "warn",
121
+ event: canceled ? "web_request_canceled" : document ? "web_document_failed" : "web_request_failed",
122
+ ...status === void 0 ? {} : { status },
123
+ msg: `${method} ${url} ${canceled ? "canceled" : `failed: ${failure}`}`
124
+ };
125
+ }
126
+ //#endregion
127
+ //#region src/web/run.ts
128
+ const USAGE = "Usage: web-run --root <path> --chrome <path> --url <url> --port <n> [--launch-id <id>] [--headed] [--viewport desktop|phone] [--ignore-certificate-errors]";
129
+ function parseArgs(argv) {
130
+ const values = {};
131
+ let headless = true;
132
+ let ignoreCertificateErrors = false;
133
+ for (let i = 0; i < argv.length; i++) {
134
+ const arg = argv[i];
135
+ if (arg === "--headed") headless = false;
136
+ else if (arg === "--ignore-certificate-errors") ignoreCertificateErrors = true;
137
+ else if ([
138
+ "--root",
139
+ "--chrome",
140
+ "--url",
141
+ "--port",
142
+ "--viewport",
143
+ "--launch-id"
144
+ ].includes(arg)) {
145
+ const value = argv[++i];
146
+ if (value === void 0) return { error: `${arg} needs a value. ${USAGE}` };
147
+ values[arg.slice(2)] = value;
148
+ } else return { error: `Unknown browser supervisor argument "${arg}". ${USAGE}` };
149
+ }
150
+ const { root, chrome, url } = values;
151
+ if (!root || !isAbsolute(root)) return { error: `--root must be an absolute path. ${USAGE}` };
152
+ if (!chrome || !isAbsolute(chrome)) return { error: `--chrome must be an absolute path. ${USAGE}` };
153
+ if (!url || !/^https?:\/\//.test(url)) return { error: `--url must be an http or https URL. ${USAGE}` };
154
+ const port = Number(values.port);
155
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) return { error: `--port must be a TCP port. ${USAGE}` };
156
+ const viewport = values.viewport ?? "desktop";
157
+ if (!WEB_VIEWPORTS.includes(viewport)) return { error: `--viewport must be desktop or phone. ${USAGE}` };
158
+ return {
159
+ root: resolve(root),
160
+ launchId: values["launch-id"] ?? null,
161
+ chrome,
162
+ url,
163
+ port,
164
+ headless,
165
+ viewport,
166
+ ignoreCertificateErrors
167
+ };
168
+ }
169
+ const DEVTOOLS_WAIT_MS = 2e4;
170
+ const CHROME_EXIT_WAIT_MS = 5e3;
171
+ const POLL_MS = 50;
172
+ const sleep = (ms) => new Promise((done) => setTimeout(done, ms));
173
+ function describe(error) {
174
+ return error instanceof Error ? error.message : String(error);
175
+ }
176
+ function isWebBundle(url) {
177
+ return /\.bundle(?:\?|$)/.test(url) && /[?&]platform=web(?:&|$)/.test(url);
178
+ }
179
+ async function runWebSupervisor(options, { onExit = (code) => process.exit(code), stderr = (line) => console.error(line) } = {}) {
180
+ const root = realpathSync(options.root);
181
+ mkdirSync(webDir(root), { recursive: true });
182
+ const writer = createNdjsonWriter(webLogFile(root), { maxBytes: LOG_ROTATE_BYTES });
183
+ const log = (level, event, msg, extra = {}) => writer.write({
184
+ src: "device",
185
+ platform: "web",
186
+ level,
187
+ event,
188
+ msg,
189
+ ...extra
190
+ });
191
+ const processToken = (0, process_identity_exports.captureProcessToken)(process.pid);
192
+ if (!processToken) {
193
+ stderr("Stim browser supervisor: could not capture process identity; refusing to start an unmanaged browser.");
194
+ writer.close();
195
+ onExit(1);
196
+ return;
197
+ }
198
+ const owner = {
199
+ pid: process.pid,
200
+ processToken
201
+ };
202
+ let claim;
203
+ try {
204
+ const attempt = (0, ownership_claim_exports.tryAcquireClaim)({
205
+ root: webClaimRoot(root),
206
+ mode: "exclusive",
207
+ label: "browser supervisor",
208
+ details: { purpose: "browser supervisor" }
209
+ });
210
+ if (attempt.pending) (0, ownership_claim_exports.releaseClaim)(attempt.pending);
211
+ claim = attempt.acquired;
212
+ if (!claim) throw new Error(`another browser supervisor holds ${attempt.held?.path ?? webClaimRoot(root)}`);
213
+ } catch (error) {
214
+ stderr(`Stim browser supervisor: ${describe(error)}`);
215
+ writer.close();
216
+ onExit(1);
217
+ return;
218
+ }
219
+ const profile = webProfileDir(root);
220
+ let chrome = null;
221
+ let chromeRecord = null;
222
+ let cdp = null;
223
+ let finished = false;
224
+ let stopping = false;
225
+ const chromeGone = () => !chromeRecord || chromeProcessState(chromeRecord) === "gone";
226
+ const finish = (code, level, event, msg) => {
227
+ if (finished) return;
228
+ finished = true;
229
+ cdp?.close();
230
+ const gone = chromeGone();
231
+ if (gone) {
232
+ try {
233
+ removeSingletonFiles(profile);
234
+ } catch {}
235
+ if (claim) (0, ownership_claim_exports.clearClaimChild)(claim);
236
+ }
237
+ if (!gone) {
238
+ log("error", event, `${msg}; Chrome pid ${chromeRecord?.pid} is still running, so its record and claim are kept for stim stop`);
239
+ writer.close();
240
+ onExit(1);
241
+ return;
242
+ }
243
+ log(level, event, msg);
244
+ try {
245
+ clearWebRecord(root, owner);
246
+ } catch {}
247
+ (0, ownership_claim_exports.releaseClaim)(claim);
248
+ writer.close();
249
+ onExit(code);
250
+ };
251
+ const stopChrome = async () => {
252
+ stopping = true;
253
+ try {
254
+ await Promise.race([cdp?.send("Browser.close"), sleep(3e3)]);
255
+ } catch {}
256
+ const deadline = Date.now() + CHROME_EXIT_WAIT_MS;
257
+ while (!chromeGone() && Date.now() < deadline) await sleep(POLL_MS);
258
+ const state = chromeRecord ? chromeProcessState(chromeRecord) : "gone";
259
+ if (chromeRecord && (state === "running" || state === "lingering")) {
260
+ try {
261
+ signalProcessTree(chromeRecord.pid, "SIGKILL", { group: true });
262
+ } catch {}
263
+ const killDeadline = Date.now() + 2e3;
264
+ while (!chromeGone() && Date.now() < killDeadline) await sleep(POLL_MS);
265
+ }
266
+ };
267
+ for (const signal of ["SIGTERM", "SIGINT"]) process.on(signal, () => {
268
+ stopChrome().then(() => finish(0, "info", "web_browser_stopped", `browser supervisor received ${signal}; closed Chrome`));
269
+ });
270
+ try {
271
+ mkdirSync(profile, { recursive: true });
272
+ recordCreatedDevice("web", profile);
273
+ const holder = liveProfileHolder(profile);
274
+ if (holder !== null) throw new Error(`Chrome pid ${holder} still holds the profile ${profile}`);
275
+ removeSingletonFiles(profile);
276
+ const launch = {
277
+ chrome: options.chrome,
278
+ headless: options.headless,
279
+ viewport: options.viewport,
280
+ ignoreCertificateErrors: options.ignoreCertificateErrors
281
+ };
282
+ writeWebRecord(root, {
283
+ pid: process.pid,
284
+ processToken,
285
+ ...launch,
286
+ cdpPort: options.port,
287
+ profile,
288
+ url: options.url,
289
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
290
+ ...options.launchId ? { launchId: options.launchId } : {}
291
+ });
292
+ const fd = openSync(browserLogFile(root), "a");
293
+ (0, ownership_claim_exports.markClaimChildPending)(claim);
294
+ chrome = getExecutor().spawn(options.chrome, chromeArgs({
295
+ profile,
296
+ port: options.port,
297
+ ...launch
298
+ }), {
299
+ detached: true,
300
+ stdio: [
301
+ "ignore",
302
+ fd,
303
+ fd
304
+ ]
305
+ });
306
+ const pid = chrome.pid;
307
+ const captured = pid === void 0 ? null : (0, process_identity_exports.captureProcessIdentity)(pid);
308
+ if (!pid || !captured?.ok) {
309
+ chrome.kill("SIGKILL");
310
+ throw new Error(`could not identify the Chrome process${captured && !captured.ok ? `: ${captured.reason}` : ""}`);
311
+ }
312
+ chromeRecord = {
313
+ pid,
314
+ processToken: captured.token
315
+ };
316
+ (0, ownership_claim_exports.setClaimChild)(claim, chromeRecord);
317
+ updateWebRecord(root, owner, { chromeProcess: chromeRecord });
318
+ chrome.on("exit", (code, signal) => {
319
+ if (stopping) return;
320
+ (async () => {
321
+ const deadline = Date.now() + 2e3;
322
+ while (!chromeGone() && Date.now() < deadline) await sleep(POLL_MS);
323
+ finish(1, "error", "web_browser_exited", `Chrome exited (${signal ? `signal ${signal}` : `exit code ${code}`}); see ${browserLogFile(root)}`);
324
+ })();
325
+ });
326
+ let connection = null;
327
+ let lastError = null;
328
+ const deadline = Date.now() + DEVTOOLS_WAIT_MS;
329
+ const isFinished = () => finished;
330
+ while (!connection && Date.now() < deadline && !isFinished()) try {
331
+ connection = await connectOwnedBrowser(options.port, pid);
332
+ } catch (error) {
333
+ lastError = error;
334
+ await sleep(100);
335
+ }
336
+ if (!connection) throw new Error(`Chrome did not open DevTools on port ${options.port} within ${DEVTOOLS_WAIT_MS / 1e3}s: ${describe(lastError)}`);
337
+ cdp = connection;
338
+ const version = String((await connection.send("Browser.getVersion")).product ?? "");
339
+ await connection.send("Target.setDiscoverTargets", { discover: true });
340
+ const { targetInfos } = await connection.send("Target.getTargets");
341
+ const page = targetInfos?.find((target) => target.type === "page");
342
+ if (!page) throw new Error("Chrome opened no page");
343
+ const targetId = page.targetId;
344
+ const { sessionId } = await connection.send("Target.attachToTarget", {
345
+ targetId,
346
+ flatten: true
347
+ });
348
+ const requests = /* @__PURE__ */ new Map();
349
+ connection.onEvent((event) => {
350
+ if (event.method === "Target.targetDestroyed" && event.params.targetId === targetId && !stopping) {
351
+ stopChrome().then(() => finish(0, "info", "web_page_closed", "the owned page was closed; closed Chrome"));
352
+ return;
353
+ }
354
+ if (event.method === "Target.targetCrashed" && event.params.targetId === targetId) {
355
+ log("error", "web_page_crashed", `the page renderer crashed (${String(event.params.status ?? "unknown")})`);
356
+ return;
357
+ }
358
+ if (event.sessionId !== sessionId) return;
359
+ const params = event.params;
360
+ switch (event.method) {
361
+ case "Runtime.consoleAPICalled":
362
+ writer.write(consoleRecord(params));
363
+ return;
364
+ case "Runtime.exceptionThrown":
365
+ writer.write(exceptionRecord(params));
366
+ return;
367
+ case "Log.entryAdded": {
368
+ const record = logEntryRecord(params);
369
+ if (record) writer.write(record);
370
+ return;
371
+ }
372
+ case "Page.loadEventFired":
373
+ log("info", "web_page_loaded", "the page fired its load event");
374
+ return;
375
+ case "Network.requestWillBeSent": {
376
+ const request = params.request;
377
+ const document = params.type === "Document" && params.frameId === targetId;
378
+ requests.set(String(params.requestId), {
379
+ url: request.url,
380
+ method: request.method,
381
+ document
382
+ });
383
+ if (document) log("info", "web_navigation", `navigating to ${request.url}`, {
384
+ url: request.url,
385
+ marker: true
386
+ });
387
+ return;
388
+ }
389
+ case "Network.responseReceived": {
390
+ const facts = requests.get(String(params.requestId));
391
+ const response = params.response;
392
+ if (!facts) return;
393
+ if (facts.document) log("info", "web_document_response", `${response.status} ${response.url}`, { status: response.status });
394
+ if (isWebBundle(response.url)) log(response.status < 400 ? "info" : "error", "web_bundle_response", `${response.status} ${response.url}`, { status: response.status });
395
+ if (response.status >= 400) writer.write(networkFailureRecord({
396
+ ...facts,
397
+ url: response.url,
398
+ status: response.status
399
+ }));
400
+ return;
401
+ }
402
+ case "Network.loadingFinished":
403
+ requests.delete(String(params.requestId));
404
+ return;
405
+ case "Network.loadingFailed": {
406
+ const facts = requests.get(String(params.requestId));
407
+ requests.delete(String(params.requestId));
408
+ if (!facts) return;
409
+ writer.write(networkFailureRecord({
410
+ ...facts,
411
+ errorText: String(params.errorText ?? "failed"),
412
+ canceled: params.canceled === true
413
+ }));
414
+ return;
415
+ }
416
+ }
417
+ });
418
+ connection.onClose(() => {
419
+ if (!stopping && chromeGone()) finish(1, "error", "web_browser_exited", "Chrome closed its DevTools connection");
420
+ });
421
+ for (const domain of [
422
+ "Runtime",
423
+ "Log",
424
+ "Network",
425
+ "Page"
426
+ ]) await connection.send(`${domain}.enable`, {}, sessionId);
427
+ if (options.viewport === "phone") {
428
+ await connection.send("Emulation.setDeviceMetricsOverride", {
429
+ ...PHONE_SCREEN,
430
+ mobile: true
431
+ }, sessionId);
432
+ await connection.send("Emulation.setTouchEmulationEnabled", {
433
+ enabled: true,
434
+ maxTouchPoints: 5
435
+ }, sessionId);
436
+ }
437
+ updateWebRecord(root, owner, {
438
+ targetId,
439
+ version
440
+ });
441
+ log("info", "web_browser_started", `browser supervisor pid ${process.pid} runs ${version} (pid ${pid}) on ${profile}`);
442
+ connection.send("Page.navigate", { url: options.url }, sessionId).catch((error) => {
443
+ log("debug", "web_navigation_slow", `Page.navigate did not answer: ${describe(error)}; the page may still load`);
444
+ });
445
+ } catch (error) {
446
+ stderr(`Stim browser supervisor: ${describe(error)}`);
447
+ await stopChrome();
448
+ finish(1, "error", "web_browser_failed", `could not start the owned Chrome: ${describe(error)}`);
449
+ }
450
+ }
451
+ async function main(argv = process.argv.slice(2)) {
452
+ if (relaunchWithLogFile(argv)) return;
453
+ const parsed = parseArgs(argv);
454
+ if ("error" in parsed) {
455
+ console.error(`Stim browser supervisor: ${parsed.error}`);
456
+ process.exit(2);
457
+ return;
458
+ }
459
+ if (!existsSync(parsed.root)) {
460
+ console.error(`Stim browser supervisor: --root ${parsed.root} does not exist.`);
461
+ process.exit(2);
462
+ return;
463
+ }
464
+ process.title = "stim-web";
465
+ await runWebSupervisor(parsed);
466
+ }
467
+ function invokedDirectly() {
468
+ const entry = process.argv[1];
469
+ if (!entry) return false;
470
+ try {
471
+ return resolve(entry) === fileURLToPath(import.meta.url);
472
+ } catch {
473
+ return false;
474
+ }
475
+ }
476
+ if (invokedDirectly()) main().catch((error) => {
477
+ console.error(`Stim browser supervisor: ${describe(error)}`);
478
+ process.exit(1);
479
+ });
480
+ //#endregion
481
+ export { main, parseArgs, runWebSupervisor };