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,394 @@
1
+ import { T as upsertProject, p as getProject } from "./config-0jCcMM0E.mjs";
2
+ import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
+ import { o as readNdjsonGenerations } from "./ndjson-CDY9IkVC.mjs";
4
+ import { M as workspaceLogsDir, c as resolveProjectMetro, f as reserveMetroPort, g as recordWorkspaceUse, h as readWorkspaceState, k as workspaceDir } from "./metro-BPeY1YMh.mjs";
5
+ import { s as gitCommonDir, y as repoRoot } from "./worktree-DeBQSVtR.mjs";
6
+ import { i as findCommandWorkspace, r as detectIsExpo, s as isPackageResolvable } from "./project-DGeclhvO.mjs";
7
+ import { n as windowsLauncherArgs } from "./detached-entry-DJHoca49.mjs";
8
+ import { a as withWorkspaceProcessLock, i as reserveBrowserPort, n as getNamedPort } from "./named-ports-n7tnJsI8.mjs";
9
+ import { f as refuseNoProject, l as phaseLine } from "./command-output-BIL7rlX7.mjs";
10
+ import { P as settingShapeErrors, R as webSettings, a as SETTING_SHAPE_REMEDY, j as resolveSettings } from "./settings-xB0Frkg6.mjs";
11
+ import { d as teardownBrowserHeld, t as resolveSupervisorTarget } from "./ownership-PUP4Dwai.mjs";
12
+ import { a as readWebRecord, f as webSupervisorLogFile, o as updateWebRecord, r as cdpEndpoint, u as webLogFile } from "./state-l1pYwOre.mjs";
13
+ import { i as findChrome, t as CHROME_INSTALL_REMEDY } from "./chrome-B5hWgQJS.mjs";
14
+ import { c as readMetroRecords } from "./launch-verify-CuJaBpEa.mjs";
15
+ import { f as spawnEntry } from "./start-BZhItkiL.mjs";
16
+ import { a as sleep, n as ensureWorkspaceStorageSafely } from "./native-runtime-CiiFHt0e.mjs";
17
+ import { n as sendToOwnedPage, t as liveWebRecord } from "./page-BTu7I60K.mjs";
18
+ import { mkdirSync, openSync } from "node:fs";
19
+ import { randomUUID } from "node:crypto";
20
+ import chalk from "chalk";
21
+ //#region src/web/launch.ts
22
+ const VERIFY_WAIT_MS = 2e4;
23
+ const LOADING_WAIT_MS = 6e4;
24
+ const METRO_WEB_BUNDLE = /\bWeb\s+Bundl(?:ing|ed)\b/i;
25
+ function after(record, since) {
26
+ const ts = Number(record.ts);
27
+ return Number.isFinite(ts) && ts >= since;
28
+ }
29
+ /**
30
+ * Decides `launched` for a `stim web` navigation from the owned page's evidence since `since`: `true` needs the
31
+ * page's load event after a successful document response and, for Metro, a web bundle response. A failed
32
+ * document is decided at once. The check ends after 20 seconds, or 60 once a document not served by Metro has
33
+ * answered; until then it returns null while the evidence is incomplete.
34
+ */
35
+ function webLaunchVerdict({ records, metroRecords = [], since, expectBundle, elapsedMs }) {
36
+ const recent = records.filter((record) => after(record, since));
37
+ const failed = recent.find((record) => record.event === "web_document_failed");
38
+ if (failed) return {
39
+ launched: "unverified",
40
+ kind: "document-failed",
41
+ reason: String(failed.msg ?? "the page did not load")
42
+ };
43
+ const documentLoaded = recent.some((record) => record.event === "web_document_response" && Number(record.status) < 400);
44
+ const pageLoaded = recent.some((record) => record.event === "web_page_loaded");
45
+ const bundled = recent.some((record) => record.event === "web_bundle_response" && Number(record.status) < 400);
46
+ if (documentLoaded && pageLoaded && (!expectBundle || bundled)) return {
47
+ launched: true,
48
+ kind: "loaded",
49
+ reason: null
50
+ };
51
+ if (documentLoaded && pageLoaded) return {
52
+ launched: "unverified",
53
+ kind: "no-bundle",
54
+ reason: "the page loaded without requesting a web bundle from Metro"
55
+ };
56
+ const waitMs = documentLoaded && !expectBundle ? LOADING_WAIT_MS : VERIFY_WAIT_MS;
57
+ if (elapsedMs < waitMs) return null;
58
+ if (expectBundle && documentLoaded && metroRecords.some((record) => after(record, since) && METRO_WEB_BUNDLE.test(String(record.msg ?? "")))) return {
59
+ launched: "bundling",
60
+ kind: "bundling",
61
+ reason: "Metro was still building the web bundle"
62
+ };
63
+ return documentLoaded ? {
64
+ launched: "unverified",
65
+ kind: "loading",
66
+ reason: `the server answered, but the page did not fire its load event within ${waitMs / 1e3} seconds`
67
+ } : {
68
+ launched: "unverified",
69
+ kind: "no-response",
70
+ reason: "the page never received a response"
71
+ };
72
+ }
73
+ const EXPO_WEB_PACKAGES = [
74
+ "react-dom",
75
+ "react-native-web",
76
+ "@expo/metro-runtime"
77
+ ];
78
+ const EXPO_WEB_DEPENDENCIES = `npx expo install ${EXPO_WEB_PACKAGES.join(" ")}`;
79
+ const WEB_SERVER_EXAMPLE = "pnpm exec vite --port \"$(stim ports get web)\" --strictPort";
80
+ function webServePlan({ usesMetro, metro, supervisorHeld, missingWebPackages }) {
81
+ if (!usesMetro) return {
82
+ serve: `Start the web dev server on that port, for example \`${WEB_SERVER_EXAMPLE}\``,
83
+ foreign: null
84
+ };
85
+ if (metro?.notOurs && (!supervisorHeld || metro.kind === "foreign-cwd")) return {
86
+ serve: null,
87
+ foreign: {
88
+ reason: `Another process holds this workspace's Metro port: ${metro.notOurs}`,
89
+ remedy: supervisorHeld ? "Run `stim stop` and follow its output, then `stim start`, then run `stim web` again." : "Run `stim start`, which reserves a free Metro port for this workspace, then run `stim web` again."
90
+ }
91
+ };
92
+ if (metro?.metro || supervisorHeld) return {
93
+ serve: null,
94
+ foreign: null
95
+ };
96
+ return {
97
+ serve: missingWebPackages.length ? `Run \`${EXPO_WEB_DEPENDENCIES}\` and \`stim start\`` : "Start this workspace's Metro with `stim start`",
98
+ foreign: null
99
+ };
100
+ }
101
+ const RETRY = "then run `stim web` again";
102
+ const METRO_BUILD = (url) => `Run \`stim logs --errors\`; Metro may have failed to build the web bundle for ${url}.`;
103
+ function webLaunchRemedy(verdict, { url, template, usesMetro, serve }) {
104
+ const nothingServed = serve ? `Nothing served ${url}. ${serve}, ${RETRY}.` : METRO_BUILD(url);
105
+ const https = url.startsWith("https:");
106
+ const reason = verdict.reason ?? "";
107
+ const withScheme = (scheme) => template ? `\`stim settings set web.url '${template.replace(/^https?:/, `${scheme}:`)}' --scope workspace\`` : `web.url with ${scheme}://`;
108
+ const status = Number(/failed: HTTP (\d+)/.exec(reason)?.[1]);
109
+ switch (verdict.kind) {
110
+ case "loaded": return null;
111
+ case "bundling": return "Metro is still building the web bundle. Run `stim logs --errors` in a moment to confirm the page rendered.";
112
+ case "loading":
113
+ if (usesMetro) break;
114
+ return `A cold dev server can take longer on its first load. Read \`stim logs --errors\`, ${RETRY}.`;
115
+ case "document-failed":
116
+ if (https && /net::ERR_CERT_/.test(reason)) return `The dev server's certificate is not trusted, as with a self-signed development certificate. Accept it in the owned profile only: \`stim settings set web.ignoreCertificateErrors true --scope workspace\`, ${RETRY}.`;
117
+ if (https && /net::ERR_SSL_PROTOCOL_ERROR/.test(reason)) return `The server on that port did not answer HTTPS. If it serves plain HTTP, set ${withScheme("http")}, ${RETRY}.`;
118
+ if (!https && /net::ERR_EMPTY_RESPONSE/.test(reason)) return `The server on that port closed the connection without an HTTP answer. If it serves HTTPS, set ${withScheme("https")}, ${RETRY}.`;
119
+ if (usesMetro) return Number.isNaN(status) ? nothingServed : METRO_BUILD(url);
120
+ if (status >= 400 && status < 500) return `The server has no page at ${url}. Check web.url's path, including the app's base path, ${RETRY}.`;
121
+ if (status >= 500) return `The dev server failed to serve ${url}. Read its output and \`stim logs --errors\`, ${RETRY}.`;
122
+ break;
123
+ case "no-response": return nothingServed;
124
+ }
125
+ return usesMetro ? METRO_BUILD(url) : nothingServed;
126
+ }
127
+ //#endregion
128
+ //#region src/commands/web.ts
129
+ const PORT_PLACEHOLDER = /\{port:([^}]*)\}/g;
130
+ const REGISTER_WAIT_MS = 3e4;
131
+ const POLL_MS = 250;
132
+ const printNote = (line) => console.error(line);
133
+ function failure(code, message, remedy) {
134
+ return {
135
+ ok: false,
136
+ error: {
137
+ code,
138
+ message,
139
+ remedy
140
+ }
141
+ };
142
+ }
143
+ /** Replaces each `{port:<label>}` in a `web.url` template; `metro` is the workspace's Metro port. */
144
+ async function resolveWebUrl(template, { metroPort, namedPort }) {
145
+ let url = template;
146
+ for (const [placeholder, label] of template.matchAll(PORT_PLACEHOLDER)) {
147
+ const port = label === "metro" ? metroPort : await namedPort(label);
148
+ if (port === null) throw new Error(`${placeholder} needs a Metro port, and this workspace has none reserved.`);
149
+ url = url.replace(placeholder, String(port));
150
+ }
151
+ return url;
152
+ }
153
+ function sameLaunch(record, config) {
154
+ return record.chrome === config.chrome && record.headless === config.headless && record.viewport === config.viewport && record.ignoreCertificateErrors === config.ignoreCertificateErrors;
155
+ }
156
+ function startSupervisor(root, { url, port, config, launchId }) {
157
+ const entry = spawnEntry("web-run");
158
+ const args = [
159
+ "--root",
160
+ root,
161
+ "--chrome",
162
+ config.chrome,
163
+ "--url",
164
+ url,
165
+ "--port",
166
+ String(port),
167
+ "--launch-id",
168
+ launchId
169
+ ];
170
+ if (!config.headless) args.push("--headed");
171
+ if (config.viewport !== "desktop") args.push("--viewport", config.viewport);
172
+ if (config.ignoreCertificateErrors) args.push("--ignore-certificate-errors");
173
+ mkdirSync(workspaceLogsDir(root), { recursive: true });
174
+ const logFile = webSupervisorLogFile(root);
175
+ if (process.platform === "win32") {
176
+ const launcher = windowsLauncherArgs({
177
+ entry,
178
+ args,
179
+ cwd: root,
180
+ logFile
181
+ });
182
+ return getExecutor().spawn(launcher.file, launcher.args, {
183
+ cwd: root,
184
+ stdio: "ignore",
185
+ env: {
186
+ ...process.env,
187
+ ...launcher.env
188
+ },
189
+ windowsHide: true
190
+ });
191
+ }
192
+ const fd = openSync(logFile, "a");
193
+ const child = getExecutor().spawn(process.execPath, [entry, ...args], {
194
+ cwd: root,
195
+ detached: true,
196
+ stdio: [
197
+ "ignore",
198
+ fd,
199
+ fd
200
+ ],
201
+ env: process.env
202
+ });
203
+ child.unref?.();
204
+ return child;
205
+ }
206
+ async function waitForSupervisor(root, child, launchId) {
207
+ let exited = false;
208
+ if (process.platform !== "win32") child.once("exit", () => {
209
+ exited = true;
210
+ });
211
+ const deadline = Date.now() + REGISTER_WAIT_MS;
212
+ while (Date.now() < deadline) {
213
+ const record = readWebRecord(root);
214
+ if (record?.launchId === launchId && record.targetId) return record;
215
+ if (exited) return null;
216
+ await sleep(POLL_MS);
217
+ }
218
+ return null;
219
+ }
220
+ async function verifyLaunch(root, since, expectBundle) {
221
+ const startedAt = Date.now();
222
+ for (;;) {
223
+ const verdict = webLaunchVerdict({
224
+ records: readNdjsonGenerations(webLogFile(root)),
225
+ metroRecords: expectBundle ? readMetroRecords(workspaceLogsDir(root)) : [],
226
+ since,
227
+ expectBundle,
228
+ elapsedMs: Date.now() - startedAt
229
+ });
230
+ if (verdict) return verdict;
231
+ await sleep(POLL_MS);
232
+ }
233
+ }
234
+ async function runWeb({ root, headed, note }) {
235
+ const startedAt = Date.now();
236
+ await ensureWorkspaceStorageSafely(root, { note });
237
+ if (!getProject(root)) upsertProject(root, {});
238
+ const settings = resolveSettings({
239
+ projectPath: root,
240
+ gitCommonDir: gitCommonDir(root),
241
+ repoRoot: repoRoot(root)
242
+ });
243
+ const [shapeError] = settingShapeErrors(settings);
244
+ if (shapeError) return failure("STIM_BAD_ARG", shapeError, SETTING_SHAPE_REMEDY);
245
+ const web = webSettings(settings);
246
+ const chrome = findChrome();
247
+ if (!chrome) return failure("STIM_WEB_NO_CHROME", "Google Chrome or Chromium is not installed.", CHROME_INSTALL_REMEDY);
248
+ const usesMetro = web.url === null || web.url.includes("{port:metro}");
249
+ if (web.url === null && !detectIsExpo(root)) return failure("STIM_WEB_NO_URL", "This is not an Expo app, so Stim does not know which page to open.", "Start your web dev server on a named port, then set the page: `stim settings set web.url 'http://localhost:{port:web}/' --scope workspace`. See `stim guide web`.");
250
+ if (web.url === null && !isPackageResolvable(root, "react-native-web")) return failure("STIM_WEB_DEPS_MISSING", "This Expo app cannot render on the web: react-native-web is not installed.", `Run \`${EXPO_WEB_DEPENDENCIES}\` and \`stim start\`, then run \`stim web\` again.`);
251
+ let metroPort = getProject(root)?.metroPort ?? null;
252
+ if (usesMetro && metroPort === null) metroPort = await reserveMetroPort(root);
253
+ const metro = usesMetro && metroPort !== null ? await resolveProjectMetro(metroPort, root) : null;
254
+ const supervisor = resolveSupervisorTarget({
255
+ state: readWorkspaceState(root)?.supervisor,
256
+ record: getProject(root)?.supervisor,
257
+ reservedPort: metroPort
258
+ });
259
+ const { serve, foreign } = webServePlan({
260
+ usesMetro,
261
+ metro,
262
+ supervisorHeld: supervisor.status !== "none" && supervisor.status !== "stale",
263
+ missingWebPackages: usesMetro && detectIsExpo(root) ? EXPO_WEB_PACKAGES.filter((name) => !isPackageResolvable(root, name)) : []
264
+ });
265
+ let url;
266
+ try {
267
+ url = await resolveWebUrl(web.url ?? "http://localhost:{port:metro}/", {
268
+ metroPort,
269
+ namedPort: (label) => getNamedPort(root, label, { log: note })
270
+ });
271
+ } catch (error) {
272
+ return failure("STIM_BAD_ARG", `web.url: ${error.message}`, SETTING_SHAPE_REMEDY);
273
+ }
274
+ const config = {
275
+ chrome,
276
+ headless: !headed,
277
+ viewport: web.viewport,
278
+ ignoreCertificateErrors: web.ignoreCertificateErrors
279
+ };
280
+ const launch = await withWorkspaceProcessLock(workspaceDir(root), "web", async () => {
281
+ const live = liveWebRecord(readWebRecord(root));
282
+ if (live && sameLaunch(live, config)) {
283
+ const since = Date.now();
284
+ try {
285
+ await sendToOwnedPage(live, "Page.navigate", { url });
286
+ updateWebRecord(root, live, { url });
287
+ note(chalk.dim(phaseLine("device", `reusing the owned Chrome (pid ${live.chromeProcess?.pid})`)));
288
+ return {
289
+ ok: true,
290
+ record: {
291
+ ...live,
292
+ url
293
+ },
294
+ since,
295
+ reused: true
296
+ };
297
+ } catch (error) {
298
+ note(chalk.dim(phaseLine("device", `restarting the owned Chrome: ${error.message}`)));
299
+ }
300
+ }
301
+ const stopped = await teardownBrowserHeld(root);
302
+ if (stopped.status === "failed" || stopped.status === "skipped") return failure("STIM_WEB_BROWSER_HELD", `The previous owned Chrome could not be stopped: ${stopped.reason ?? "unknown reason"}.`, "Run `stim status` and `stim guide errors teardown`.");
303
+ const port = await reserveBrowserPort(root, { log: note });
304
+ const since = Date.now();
305
+ note(chalk.dim(phaseLine("device", `starting ${config.headless ? "headless " : ""}Chrome on DevTools port ${port}`)));
306
+ const launchId = randomUUID();
307
+ const record = await waitForSupervisor(root, startSupervisor(root, {
308
+ url,
309
+ port,
310
+ config,
311
+ launchId
312
+ }), launchId);
313
+ if (!record) return failure("STIM_WEB_LAUNCH_FAILED", "The owned Chrome did not start.", `Read ${webSupervisorLogFile(root)} and \`stim logs --errors\`, then run \`stim web\` again.`);
314
+ return {
315
+ ok: true,
316
+ record,
317
+ since,
318
+ reused: false
319
+ };
320
+ }, {
321
+ external: true,
322
+ ownerPurpose: "stim web"
323
+ });
324
+ if (!launch.ok) return launch;
325
+ const measured = await verifyLaunch(root, launch.since, usesMetro);
326
+ const live = liveWebRecord(readWebRecord(root));
327
+ const record = live ?? launch.record;
328
+ const verdict = foreign ? {
329
+ launched: "unverified",
330
+ kind: "no-response",
331
+ reason: foreign.reason
332
+ } : measured;
333
+ const remedy = foreign ? foreign.remedy : webLaunchRemedy(verdict, {
334
+ url,
335
+ template: web.url,
336
+ usesMetro,
337
+ serve
338
+ });
339
+ return {
340
+ ok: true,
341
+ remedy: verdict.reason && remedy ? `${verdict.reason}. ${remedy}` : remedy,
342
+ facts: {
343
+ platform: "web",
344
+ browser: "chrome",
345
+ version: record.version ?? null,
346
+ running: live !== null,
347
+ pid: live?.chromeProcess?.pid ?? null,
348
+ supervisorPid: live?.pid ?? null,
349
+ url,
350
+ headless: record.headless,
351
+ viewport: record.viewport,
352
+ profile: record.profile,
353
+ cdpEndpoint: live ? cdpEndpoint(live.cdpPort) : null,
354
+ reused: launch.reused,
355
+ launched: verdict.launched,
356
+ metroPort: usesMetro ? metroPort : null,
357
+ logs: { dir: workspaceLogsDir(root) },
358
+ durationMs: Date.now() - startedAt
359
+ }
360
+ };
361
+ }
362
+ function webCommand(program) {
363
+ program.command("web").description("Open this workspace's page in a Stim-owned headless Chrome and capture its console, errors and failed requests in stim logs").option("--headed", "Show the Chrome window instead of running headless").option("--json", "Print the result as one JSON object; progress goes to stderr").action(async (opts) => {
364
+ const json = Boolean(opts.json);
365
+ const root = findCommandWorkspace(process.cwd());
366
+ if (!root) {
367
+ refuseNoProject({ json });
368
+ return;
369
+ }
370
+ recordWorkspaceUse(root);
371
+ const result = await runWeb({
372
+ root,
373
+ headed: Boolean(opts.headed),
374
+ note: printNote
375
+ });
376
+ if (!result.ok) {
377
+ printNote(chalk.red(phaseLine("error", `${result.error.code}: ${result.error.message}`)));
378
+ if (result.error.remedy) printNote(phaseLine("remedy", result.error.remedy));
379
+ if (json) console.log(JSON.stringify(result.error));
380
+ process.exitCode = 1;
381
+ return;
382
+ }
383
+ const { facts, remedy } = result;
384
+ if (remedy) printNote(chalk.yellow(phaseLine("launch", remedy)));
385
+ if (json) {
386
+ console.log(JSON.stringify(facts));
387
+ return;
388
+ }
389
+ const launched = facts.launched === true ? chalk.green("loaded") : chalk.yellow(facts.launched === "bundling" ? "bundling" : "unverified");
390
+ console.log(facts.running ? `${facts.url} ${launched} in ${facts.version ?? "Chrome"} (pid ${facts.pid}, ${facts.headless ? "headless" : "headed"}, ${facts.viewport}). DevTools: ${facts.cdpEndpoint}. Logs: stim logs --errors.` : `${facts.url} ${launched}, and the owned Chrome is no longer running. Run stim logs --errors, then stim web again.`);
391
+ });
392
+ }
393
+ //#endregion
394
+ export { webCommand as default, resolveWebUrl, runWeb };
@@ -1,2 +1,2 @@
1
- import { a as reclaimKeys, c as removalBlockers, d as removeWorktreeTarget, f as worktreeCommand, i as porcelainPath, l as removalPath, n as excludePodChurn, o as registerRemove, r as matchWorktreeEntry, s as registerWarm, t as dependencyInstallCommand, u as removalRemedy } from "./worktree-ChDL5fO1.mjs";
1
+ import { a as reclaimKeys, c as removalBlockers, d as removeWorktreeTarget, f as worktreeCommand, i as porcelainPath, l as removalPath, n as excludePodChurn, o as registerRemove, r as matchWorktreeEntry, s as registerWarm, t as dependencyInstallCommand, u as removalRemedy } from "./worktree-D4Do_XiQ.mjs";
2
2
  export { worktreeCommand as default, dependencyInstallCommand, excludePodChurn, matchWorktreeEntry, porcelainPath, reclaimKeys, registerRemove, registerWarm, removalBlockers, removalPath, removalRemedy, removeWorktreeTarget };