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.
- package/README.md +3 -1
- package/dist/{activity-CCZJs__P.mjs → activity-DfVKZTEB.mjs} +42 -25
- package/dist/{android-BQiKJYK0.mjs → android-BHfAu2mf.mjs} +1 -1
- package/dist/{android-B7X-8H7h.mjs → android-CD8pr8Q2.mjs} +62 -47
- package/dist/{android-cas-DzLPXiEn.mjs → android-cas-B9Kv9aIy.mjs} +9 -7
- package/dist/{android-75Sk2rIR.mjs → android-qx_31wJo.mjs} +395 -116
- package/dist/{app-install-CsZi0SAT.mjs → app-install-CDFY2nUL.mjs} +1 -1
- package/dist/{budget-DbzW5VTD.mjs → budget-E5o91NR1.mjs} +154 -37
- package/dist/{build-plan-CqntbR7b.mjs → build-plan-BTa93o_r.mjs} +21 -88
- package/dist/{build-progress-h60GPV27.mjs → build-progress-l7d-X-ZP.mjs} +75 -7
- package/dist/cdp-Dq_ypAlJ.mjs +115 -0
- package/dist/chrome-B5hWgQJS.mjs +61 -0
- package/dist/cli.mjs +16 -15
- package/dist/collector-run.mjs +14 -8
- package/dist/{command-output-BWCj5Vh-.mjs → command-output-BIL7rlX7.mjs} +1 -1
- package/dist/created-devices-Cd558h31.mjs +37 -0
- package/dist/{deps-CO-_7UOm.mjs → deps-2Wo81np9.mjs} +1 -1
- package/dist/{device-9r9xRYDa.mjs → device-Cf7vl-so.mjs} +6 -6
- package/dist/{device-lease-B9LAXIUH.mjs → device-lease-CL9dZf73.mjs} +52 -3
- package/dist/{device-pool-BoezJd-c.mjs → device-pool-MuoiyRcu.mjs} +3 -3
- package/dist/{device-remote-C0_o-tJH.mjs → device-remote-DT9ngZiD.mjs} +10 -10
- package/dist/{doctor-LfDlbR-N.mjs → doctor-D2iysNNH.mjs} +47 -18
- package/dist/{error-diagnostics-CCxjn31V.mjs → error-diagnostics-BoiOhck8.mjs} +6 -6
- package/dist/{gc-C4nQFcTX.mjs → gc-eA-PRBX8.mjs} +853 -126
- package/dist/{guide-BPBlh9ML.mjs → guide-BfZ0OmiP.mjs} +736 -110
- package/dist/{guide-status-i9ogHt4v.mjs → guide-status-BaMYu55M.mjs} +1 -1
- package/dist/{idle-S3tFZ53_.mjs → idle-DxLi5OkN.mjs} +42 -9
- package/dist/{in-use-D9XYEgFD.mjs → in-use-wYEaKQM9.mjs} +9 -5
- package/dist/{ios-CDzWL1mj.mjs → ios-CcfCeZ19.mjs} +98 -54
- package/dist/{ios-CZWOHTTc.mjs → ios-CmEzZiOj.mjs} +10 -5
- package/dist/{ios-device-D9cLxVr5.mjs → ios-device-BW-t_Lkv.mjs} +1 -1
- package/dist/{launch-verify-B2oR_kFN.mjs → launch-verify-CuJaBpEa.mjs} +33 -20
- package/dist/{logs-SzuYudOj.mjs → logs-DcSRbC2t.mjs} +11 -10
- package/dist/{metro-Dzo30hvJ.mjs → metro-BPeY1YMh.mjs} +4 -4
- package/dist/{named-ports-Dn1o61D9.mjs → named-ports-n7tnJsI8.mjs} +78 -6
- package/dist/native-runtime-CiiFHt0e.mjs +74 -0
- package/dist/{ownership-Dqnrjrio.mjs → ownership-Bz2kETAS.mjs} +1 -1
- package/dist/{ownership-B8d4kXBx.mjs → ownership-PUP4Dwai.mjs} +134 -209
- package/dist/page-BTu7I60K.mjs +29 -0
- package/dist/{ports-Dp_QTwTC.mjs → ports-C5UutqpJ.mjs} +4 -4
- package/dist/{project-B8rbutVC.mjs → project-DGeclhvO.mjs} +53 -17
- package/dist/{reload-nocmx8KB.mjs → reload-C-cV5yy_.mjs} +79 -20
- package/dist/{remote-cache-BRsRZru5.mjs → remote-cache--aehr4Pr.mjs} +3 -3
- package/dist/{server-bare-Gn3ssSdK.mjs → server-bare-Bq8BpSIo.mjs} +1 -1
- package/dist/server-expo-BeYwP_Xy.mjs +2 -0
- package/dist/{server-expo-CeT1nIIk.mjs → server-expo-DUCg52-K.mjs} +2 -2
- package/dist/{settings-bE-Gu3Ti.mjs → settings-ssVBToDR.mjs} +10 -10
- package/dist/{settings-BlyDn2Oz.mjs → settings-xB0Frkg6.mjs} +11 -2
- package/dist/settings.schema.json +53 -1
- package/dist/{simslim-COtEEP7x.mjs → simslim-Bbg9UWrt.mjs} +12 -6
- package/dist/{slot-launch-DIACGEfH.mjs → slot-launch-BIWEW-wI.mjs} +3 -3
- package/dist/{start-BboE1HIS.mjs → start-BZhItkiL.mjs} +11 -10
- package/dist/{start-BFstVBlx.mjs → start-Rw2GD3nV.mjs} +1 -1
- package/dist/{state-DcDgzfu3.mjs → state-DwAU2_-D.mjs} +1 -1
- package/dist/state-l1pYwOre.mjs +148 -0
- package/dist/{stats-DiCtmluY.mjs → stats-CaSwmsSZ.mjs} +2 -2
- package/dist/{status-DI9Tjo7T.mjs → status-TJlL0HWo.mjs} +624 -103
- package/dist/{status-BUASYyy_.mjs → status-sszP4XKU.mjs} +59 -2
- package/dist/{stim-desktop-D0xhpZt8.mjs → stim-desktop-CXt8-Z3J.mjs} +13 -2
- package/dist/{stop-Bn6v-GvS.mjs → stop-BZ2qMiBP.mjs} +37 -12
- package/dist/{stop-BI7QY76M.mjs → stop-DskDKNTU.mjs} +2 -2
- package/dist/supervisor-run.mjs +10 -10
- package/dist/web-run.d.mts +22 -0
- package/dist/web-run.mjs +481 -0
- package/dist/web-sWXd5iek.mjs +394 -0
- package/dist/{worktree-B5sTRskL.mjs → worktree-3cBa-bQt.mjs} +1 -1
- package/dist/{worktree-ChDL5fO1.mjs → worktree-D4Do_XiQ.mjs} +226 -19
- package/dist/{xcode-BzplpQzc.mjs → xcode-LM6A8cNZ.mjs} +5 -5
- package/package.json +5 -5
- package/dist/server-expo-eWi9Coof.mjs +0 -2
- 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-
|
|
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 };
|