stim 1.0.0 → 1.2.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 (66) hide show
  1. package/README.md +29 -10
  2. package/dist/android-BW8i_YxU.mjs +204 -0
  3. package/dist/android-Bcmev2s2.mjs +2334 -0
  4. package/dist/android-K63_0UfF.mjs +727 -0
  5. package/dist/android-cas-D6HPZ6UQ.mjs +1078 -0
  6. package/dist/android-cas-compiler.mjs +2 -2
  7. package/dist/app-install-NFxUahnH.mjs +1149 -0
  8. package/dist/build-lock-BsVoLDaq.mjs +645 -0
  9. package/dist/build-slots-D0b6sJyo.mjs +152 -0
  10. package/dist/{cache-manifest-4tH64LQ9.mjs → cache-manifest-Cu4_Znw9.mjs} +2 -2
  11. package/dist/cache-manifest.mjs +1 -1
  12. package/dist/cli.mjs +24 -27166
  13. package/dist/collector-run.d.mts +1 -0
  14. package/dist/collector-run.mjs +7 -5
  15. package/dist/command-output-8wHMEde0.mjs +289 -0
  16. package/dist/{config-7kmtuhO2.mjs → config-CMtIaTk9.mjs} +44 -13
  17. package/dist/deps-BLBsTNDS.mjs +435 -0
  18. package/dist/dev-client-RpvXxCXV.mjs +620 -0
  19. package/dist/device-CoCPmazS.mjs +250 -0
  20. package/dist/device-lease-Dpik41RY.mjs +335 -0
  21. package/dist/device-pool-B5pEVa3t.mjs +408 -0
  22. package/dist/device-remote-E81KAdXf.mjs +1209 -0
  23. package/dist/doctor-CqPeMsSm.mjs +1130 -0
  24. package/dist/doctor-D_CX7ib5.mjs +530 -0
  25. package/dist/error-diagnostics-Dw8Gn3yw.mjs +636 -0
  26. package/dist/{exec-bsN9MJXb.mjs → exec-CyylIdq9.mjs} +4 -2
  27. package/dist/gc-B7KkDinF.mjs +1665 -0
  28. package/dist/guide-BS2Xc24o.mjs +4503 -0
  29. package/dist/ios-B7KLgIp5.mjs +2733 -0
  30. package/dist/ios-D1h1Di39.mjs +451 -0
  31. package/dist/ios-device-D3pzTKQE.mjs +81 -0
  32. package/dist/ios-device-xNt0Lg6v.mjs +413 -0
  33. package/dist/logs-DcX9AvRF.mjs +176 -0
  34. package/dist/logs-query-oqn47Ffx.mjs +273 -0
  35. package/dist/metro-COslRK_F.mjs +184 -0
  36. package/dist/metro-store-CDDZHtPl.mjs +92 -0
  37. package/dist/ownership-CGPart6T.mjs +60 -0
  38. package/dist/ownership-CYOyqxUo.mjs +475 -0
  39. package/dist/ownership-claim-7zRxxTfZ.mjs +569 -0
  40. package/dist/{project-Dit1CckW.mjs → project-BBTVABUz.mjs} +12 -3
  41. package/dist/reclaim-C5Jmuahx.mjs +348 -0
  42. package/dist/reload-Z8nKifAk.mjs +332 -0
  43. package/dist/remote-cache-DDmJ1Roq.mjs +994 -0
  44. package/dist/{server-bare-BmTVVmBB.mjs → server-bare-DEk37Mz2.mjs} +8 -2
  45. package/dist/{server-expo-CHJe1r6j.mjs → server-expo-C3ktAwUG.mjs} +6 -5
  46. package/dist/server-expo-Drd7ksHu.mjs +2 -0
  47. package/dist/settings-DwU1_U_q.mjs +708 -0
  48. package/dist/spawn-entry-nCE6m770.mjs +14 -0
  49. package/dist/start-BrY4EuIg.mjs +822 -0
  50. package/dist/{state-2nush3MK.mjs → state-CZ-ER-8d.mjs} +21 -22
  51. package/dist/stats-BCWEVLrM.mjs +307 -0
  52. package/dist/stats-BsF15Bti.mjs +62 -0
  53. package/dist/status-DFI1Hfpg.mjs +393 -0
  54. package/dist/stop-B7DR4awK.mjs +3 -0
  55. package/dist/stop-Btg770vV.mjs +591 -0
  56. package/dist/supervisor-run.d.mts +0 -1
  57. package/dist/supervisor-run.mjs +4 -4
  58. package/dist/workspace-process-lock-BKfdys1q.mjs +47 -0
  59. package/dist/worktree-BPTbI-LN.mjs +1322 -0
  60. package/dist/worktree-C79xx_kM.mjs +543 -0
  61. package/dist/xcode-DeKCribl.mjs +1832 -0
  62. package/package.json +5 -5
  63. package/shim/bundle-response.cjs +5 -0
  64. package/dist/android-jc8MnUyL.mjs +0 -514
  65. package/dist/metro-store-COLl1pOk.mjs +0 -1249
  66. package/dist/server-expo-BZJm--wA.mjs +0 -2
@@ -0,0 +1,620 @@
1
+ import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
+ import { y as ensureWorkspaceStorage } from "./state-CZ-ER-8d.mjs";
3
+ import { C as removeTemporaryEntry, S as makeTemporaryDirectory } from "./worktree-C79xx_kM.mjs";
4
+ import { i as detectIsExpo, o as isPackageResolvable } from "./project-BBTVABUz.mjs";
5
+ import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-C7IlIzd7.mjs";
6
+ import { t as NOT_OURS_FOREIGN_CWD } from "./metro-COslRK_F.mjs";
7
+ import { i as LAUNCH_BUNDLING, s as LAUNCH_UNVERIFIED } from "./app-install-NFxUahnH.mjs";
8
+ import { f as startBuildHeartbeat, p as tailLines, r as HEARTBEAT_INTERVAL_MS } from "./xcode-DeKCribl.mjs";
9
+ import { i as expoBinRefusal, r as expoBinPath, t as cleanLine } from "./server-expo-C3ktAwUG.mjs";
10
+ import { existsSync, mkdirSync, readFileSync, readdirSync } from "node:fs";
11
+ import { basename, join } from "node:path";
12
+ import chalk from "chalk";
13
+ import { request } from "node:http";
14
+ //#region src/engine/metro-warmup.ts
15
+ const WARMUP_TIMEOUT_MS = 6e4;
16
+ async function requestLocal(url, platform, prefetch) {
17
+ return new Promise((resolve) => {
18
+ const req = request(url, { headers: {
19
+ accept: "application/json",
20
+ "expo-platform": platform,
21
+ ...prefetch ? { "x-stim-metro-warmup": "1" } : {}
22
+ } });
23
+ const timer = setTimeout(() => req.destroy(), WARMUP_TIMEOUT_MS);
24
+ timer.unref();
25
+ req.on("socket", (socket) => socket.unref());
26
+ req.on("error", () => resolve(null));
27
+ req.on("close", () => {
28
+ clearTimeout(timer);
29
+ resolve(null);
30
+ });
31
+ req.on("response", (res) => {
32
+ let body = "";
33
+ res.setEncoding("utf8");
34
+ res.on("data", (chunk) => {
35
+ if (!prefetch) body += chunk;
36
+ });
37
+ res.on("error", () => resolve(null));
38
+ res.on("end", () => resolve(res.statusCode === 200 ? body : null));
39
+ });
40
+ req.end();
41
+ });
42
+ }
43
+ async function warmMetro({ port, platform, isExpo, appId, bundleUrl }) {
44
+ try {
45
+ const origin = `http://127.0.0.1:${port}`;
46
+ if (await requestLocal(new URL("/_stim/metro-warmup", origin), platform, false) !== "ready") return;
47
+ const url = new URL("/index.bundle", origin);
48
+ url.search = new URLSearchParams({
49
+ platform,
50
+ dev: "true",
51
+ lazy: "true",
52
+ minify: "false",
53
+ ...platform === "ios" ? { inlineSourceMap: "false" } : {},
54
+ modulesOnly: "false",
55
+ runModule: "true",
56
+ excludeSource: "true",
57
+ sourcePaths: "url-server",
58
+ ...appId ? { app: appId } : {}
59
+ }).toString();
60
+ if (!bundleUrl && isExpo) {
61
+ const body = await requestLocal(new URL(origin), platform, false);
62
+ if (body === null) return;
63
+ const manifest = JSON.parse(body);
64
+ bundleUrl = manifest.launchAsset?.url ?? manifest.bundleUrl;
65
+ if (typeof bundleUrl !== "string") return;
66
+ }
67
+ if (bundleUrl) {
68
+ const bundle = new URL(bundleUrl, origin);
69
+ url.pathname = bundle.pathname;
70
+ url.search = bundle.search;
71
+ }
72
+ await requestLocal(url, platform, true);
73
+ } catch {}
74
+ }
75
+ //#endregion
76
+ //#region src/commands/native-runtime.ts
77
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
78
+ const GATE_RETRY_DELAYS_MS = [
79
+ 3e3,
80
+ 7e3,
81
+ 1e4
82
+ ];
83
+ function gateShouldRetry(resolution) {
84
+ if (resolution?.metro) return false;
85
+ return resolution?.kind !== NOT_OURS_FOREIGN_CWD;
86
+ }
87
+ async function resolveMetroWithRetry(resolve, port, root, { delays = GATE_RETRY_DELAYS_MS, sleep: wait = sleep, onRetry = (_info) => {} } = {}) {
88
+ let resolution = await resolve(port, root);
89
+ for (let i = 0; i < delays.length && gateShouldRetry(resolution); i++) {
90
+ const delayMs = delays[i];
91
+ if (delayMs === void 0) break;
92
+ onRetry({
93
+ attempt: i + 1,
94
+ delayMs,
95
+ resolution
96
+ });
97
+ await wait(delayMs);
98
+ resolution = await resolve(port, root);
99
+ }
100
+ return resolution;
101
+ }
102
+ function noMetroMessage({ port, resolution, supervisor, supervisorAlive }) {
103
+ const foreign = resolution?.notOurs;
104
+ if (supervisor && supervisor.port === port && supervisorAlive) {
105
+ const mode = supervisor.mode ? `${supervisor.mode} ` : "";
106
+ return `A supervisor record exists for port ${port} (pid ${supervisor.pid}, ${mode}dev server) but it did not verify as this workspace's Metro${foreign ? `: ${foreign}` : " -- nothing answered /status"}. Metro may still be indexing this project (a monorepo file-map crawl blocks its event loop for ~20s after the port opens).`;
107
+ }
108
+ if (foreign) return `Port ${port} is in use but is NOT this workspace's dev server: ${foreign}.`;
109
+ return `Nothing is serving this workspace's dev server on port ${port}.`;
110
+ }
111
+ function noMetroRemedy({ port, supervisor, supervisorAlive }) {
112
+ if (supervisor && supervisor.port === port && supervisorAlive) return "Re-run `stim ios` in a few seconds, or give the dev server longer to verify with `stim start --wait <seconds>`.";
113
+ return "Run `stim start` first, or pass --no-metro-check.";
114
+ }
115
+ async function ensureWorkspaceStorageSafely(root, { note = (_line) => {} } = {}) {
116
+ try {
117
+ return ensureWorkspaceStorage(root);
118
+ } catch (err) {
119
+ note(chalk.dim(`Could not prepare this workspace's Stim state: ${err?.message || err}`));
120
+ throw err;
121
+ }
122
+ }
123
+ function launchOutcomeRecord({ launchState, release, bundleId, configuration, metroPort }) {
124
+ const unverified = launchState === LAUNCH_UNVERIFIED;
125
+ const bundling = launchState === LAUNCH_BUNDLING;
126
+ let msg;
127
+ if (release) msg = unverified ? `${bundleId} could not be verified as running after its ${configuration} launch` : `${bundleId} is running its embedded ${configuration} bundle`;
128
+ else if (unverified) msg = `no bundle request from ${bundleId} reached this workspace's Metro on port ${metroPort}`;
129
+ else if (bundling) msg = `${bundleId} requested a bundle from this workspace's Metro on port ${metroPort}; it was still being built when the launch check ended`;
130
+ else msg = `${bundleId} fetched a bundle from this workspace's Metro on port ${metroPort}`;
131
+ return {
132
+ src: "build",
133
+ level: unverified ? "warn" : "info",
134
+ event: unverified ? "launch_unverified" : bundling ? "launch_bundling" : "launch_verified",
135
+ msg
136
+ };
137
+ }
138
+ //#endregion
139
+ //#region src/engine/prebuild.ts
140
+ const PREBUILD_ERROR = "STIM_PREBUILD_FAILED";
141
+ const LAST_LINES$1 = 20;
142
+ function nativeDirName(platform) {
143
+ return platform === "android" ? "android" : "ios";
144
+ }
145
+ function nativeDir(root, platform) {
146
+ return join(root, nativeDirName(platform));
147
+ }
148
+ function shouldPrebuild({ isExpo, nativeDirExists }) {
149
+ return Boolean(isExpo) && !nativeDirExists;
150
+ }
151
+ function needsPrebuild(root, platform, isExpo) {
152
+ return shouldPrebuild({
153
+ isExpo,
154
+ nativeDirExists: existsSync(nativeDir(root, platform))
155
+ });
156
+ }
157
+ function prebuildRefusal({ isExpo, platform, nativeDirExists }) {
158
+ if (nativeDirExists || isExpo) return null;
159
+ const dir = nativeDirName(platform);
160
+ return {
161
+ code: PREBUILD_ERROR,
162
+ message: `This project has no ${dir}/ directory and is not an Expo (CNG) project, so Stim cannot generate one.`,
163
+ remedy: `Check out or generate the native project (\`npx @react-native-community/cli init\` produced one originally), or add \`expo\` to the project so \`expo prebuild\` can create ${dir}/.`
164
+ };
165
+ }
166
+ function prebuildFailure(logWriter, result) {
167
+ logWriter?.write?.({
168
+ src: "build",
169
+ level: "error",
170
+ msg: result.reason || "Expo prebuild failed.",
171
+ event: "prebuild_failed"
172
+ });
173
+ return result;
174
+ }
175
+ async function runPrebuild(root, platform, logWriter, { spawnFn = null, now = Date.now, isExpo = null } = {}) {
176
+ const dirExists = existsSync(nativeDir(root, platform));
177
+ const refusal = prebuildRefusal({
178
+ isExpo: isExpo === null ? detectIsExpo(root) : isExpo,
179
+ platform,
180
+ nativeDirExists: dirExists
181
+ });
182
+ if (refusal) return prebuildFailure(logWriter, {
183
+ failed: true,
184
+ code: refusal.code,
185
+ reason: refusal.message,
186
+ remedy: refusal.remedy,
187
+ error: refusal,
188
+ lastLines: []
189
+ });
190
+ const bin = expoBinPath(root);
191
+ if (!bin) {
192
+ const binRefusal = expoBinRefusal(root, "prebuild");
193
+ return prebuildFailure(logWriter, {
194
+ failed: true,
195
+ code: PREBUILD_ERROR,
196
+ reason: binRefusal.message,
197
+ remedy: binRefusal.remedy,
198
+ lastLines: []
199
+ });
200
+ }
201
+ const spawn = spawnFn || ((cmd, args, opts) => getExecutor().spawn(cmd, args, opts));
202
+ const startedAt = now();
203
+ const tail = [];
204
+ const push = (line) => {
205
+ const msg = stripAnsi(String(line)).trimEnd();
206
+ if (!msg.trim()) return;
207
+ tail.push(msg);
208
+ if (tail.length > LAST_LINES$1) tail.shift();
209
+ logWriter?.write?.({
210
+ src: "build",
211
+ level: "debug",
212
+ msg,
213
+ raw: true,
214
+ event: "prebuild"
215
+ });
216
+ };
217
+ let child;
218
+ try {
219
+ child = spawn(bin, [
220
+ "prebuild",
221
+ "-p",
222
+ platform,
223
+ "--no-install"
224
+ ], {
225
+ cwd: root,
226
+ stdio: [
227
+ "ignore",
228
+ "pipe",
229
+ "pipe"
230
+ ],
231
+ env: {
232
+ ...process.env,
233
+ FORCE_COLOR: "0",
234
+ CI: "1"
235
+ }
236
+ });
237
+ } catch (err) {
238
+ return prebuildFailure(logWriter, {
239
+ failed: true,
240
+ code: PREBUILD_ERROR,
241
+ reason: `Could not run \`expo prebuild\`: ${err?.message || err}`,
242
+ lastLines: []
243
+ });
244
+ }
245
+ const outReader = createLineReader(push);
246
+ const errReader = createLineReader(push);
247
+ child.stdout?.setEncoding?.("utf-8");
248
+ child.stderr?.setEncoding?.("utf-8");
249
+ child.stdout?.on("data", (chunk) => outReader.push(chunk));
250
+ child.stderr?.on("data", (chunk) => errReader.push(chunk));
251
+ const result = await waitForChild(child);
252
+ outReader.flush();
253
+ errReader.flush();
254
+ const durationMs = now() - startedAt;
255
+ if (result.error) return prebuildFailure(logWriter, {
256
+ failed: true,
257
+ code: PREBUILD_ERROR,
258
+ reason: `Could not run \`expo prebuild\`: ${result.error?.message || result.error}`,
259
+ lastLines: tail.slice(),
260
+ durationMs
261
+ });
262
+ if (result.code !== 0) {
263
+ const how = result.signal ? `signal ${result.signal}` : `exit code ${result.code}`;
264
+ return prebuildFailure(logWriter, {
265
+ failed: true,
266
+ code: PREBUILD_ERROR,
267
+ reason: `\`expo prebuild -p ${platform}\` failed (${how}).`,
268
+ lastLines: tail.slice(),
269
+ durationMs
270
+ });
271
+ }
272
+ if (!existsSync(nativeDir(root, platform))) return prebuildFailure(logWriter, {
273
+ failed: true,
274
+ code: PREBUILD_ERROR,
275
+ reason: `\`expo prebuild -p ${platform}\` succeeded but did not create ${nativeDirName(platform)}/.`,
276
+ lastLines: tail.slice(),
277
+ durationMs
278
+ });
279
+ return {
280
+ ok: true,
281
+ durationMs,
282
+ nativeDir: nativeDir(root, platform)
283
+ };
284
+ }
285
+ //#endregion
286
+ //#region src/engine/js-swap.ts
287
+ const JS_BUNDLE_NAME = "main.jsbundle";
288
+ const LAST_LINES = 5;
289
+ function hermesEnabledFromProperties(text) {
290
+ if (typeof text !== "string") return true;
291
+ let data;
292
+ try {
293
+ data = JSON.parse(text);
294
+ } catch {
295
+ return true;
296
+ }
297
+ if (!data || typeof data !== "object") return true;
298
+ const raw = data.hermesEnabled;
299
+ return raw !== "false" && raw !== false;
300
+ }
301
+ function readHermesEnabled(root) {
302
+ try {
303
+ return hermesEnabledFromProperties(readFileSync(join(root, "ios", "Podfile.properties.json"), "utf-8"));
304
+ } catch {
305
+ return true;
306
+ }
307
+ }
308
+ const ENTRY_CANDIDATES = [
309
+ "index.js",
310
+ "index.ts",
311
+ "index.tsx",
312
+ "index.jsx"
313
+ ];
314
+ function pickEntryFile(entries) {
315
+ const names = new Set((Array.isArray(entries) ? entries : []).filter((e) => typeof e === "string"));
316
+ return ENTRY_CANDIDATES.find((c) => names.has(c)) ?? "index.js";
317
+ }
318
+ function detectEntryFile(root) {
319
+ let entries;
320
+ try {
321
+ entries = readdirSync(root);
322
+ } catch {
323
+ return "index.js";
324
+ }
325
+ return pickEntryFile(entries);
326
+ }
327
+ function bundleCommand({ isExpo, entryFile, bundleOutput, assetsDest }) {
328
+ if (isExpo) return {
329
+ file: "npx",
330
+ args: [
331
+ "expo",
332
+ "export:embed",
333
+ "--platform",
334
+ "ios",
335
+ "--dev",
336
+ "false",
337
+ "--bundle-output",
338
+ bundleOutput,
339
+ "--assets-dest",
340
+ assetsDest
341
+ ]
342
+ };
343
+ return {
344
+ file: "npx",
345
+ args: [
346
+ "react-native",
347
+ "bundle",
348
+ "--platform",
349
+ "ios",
350
+ "--dev",
351
+ "false",
352
+ "--entry-file",
353
+ entryFile,
354
+ "--bundle-output",
355
+ bundleOutput,
356
+ "--assets-dest",
357
+ assetsDest
358
+ ]
359
+ };
360
+ }
361
+ function hermescPath(root, { exists = existsSync } = {}) {
362
+ const candidates = [
363
+ join(root, "node_modules", "hermes-compiler", "hermesc", "osx-bin", "hermesc"),
364
+ join(root, "ios", "Pods", "hermes-engine", "destroot", "bin", "hermesc"),
365
+ join(root, "node_modules", "react-native", "sdks", "hermesc", "osx-bin", "hermesc")
366
+ ];
367
+ return candidates.find(exists) ?? candidates[candidates.length - 1];
368
+ }
369
+ function hermescArgs({ bundle, out }) {
370
+ return [
371
+ "-emit-binary",
372
+ "-out",
373
+ out,
374
+ bundle
375
+ ];
376
+ }
377
+ async function swapJsBundle({ root, isExpo, cachedAppPath, logWriter = null, exec = null, spawnFn = null, mkdtemp = () => makeTemporaryDirectory(cachedAppPath, "stim-js-swap-"), exists = existsSync, hermesEnabled = null, now = Date.now, heartbeatMs = HEARTBEAT_INTERVAL_MS, onHeartbeat = (line) => console.error(line) }) {
378
+ const e = exec || getExecutor();
379
+ const startedAt = now();
380
+ const elapsed = () => now() - startedAt;
381
+ let tmp;
382
+ const fail = (step, reason, lastLines = []) => {
383
+ logWriter?.write?.({
384
+ src: "build",
385
+ level: "error",
386
+ msg: `JS swap failed at ${step}: ${reason}`,
387
+ event: "js_swap"
388
+ });
389
+ if (tmp) removeTemporaryEntry(tmp);
390
+ return {
391
+ failed: true,
392
+ step,
393
+ reason,
394
+ lastLines,
395
+ durationMs: elapsed()
396
+ };
397
+ };
398
+ let appCopy;
399
+ try {
400
+ tmp = mkdtemp();
401
+ appCopy = join(tmp, basename(cachedAppPath));
402
+ try {
403
+ e.runFile("cp", [
404
+ "-c",
405
+ "-R",
406
+ cachedAppPath,
407
+ appCopy
408
+ ]);
409
+ } catch {
410
+ e.runFile("cp", [
411
+ "-R",
412
+ cachedAppPath,
413
+ appCopy
414
+ ]);
415
+ }
416
+ } catch (err) {
417
+ return fail("copy", `could not copy ${cachedAppPath} aside: ${describe(err)}`);
418
+ }
419
+ const bundleOutput = join(tmp, JS_BUNDLE_NAME);
420
+ const assetsDest = join(tmp, "assets");
421
+ const command = bundleCommand({
422
+ isExpo,
423
+ entryFile: isExpo ? "index.js" : detectEntryFile(root),
424
+ bundleOutput,
425
+ assetsDest
426
+ });
427
+ try {
428
+ mkdirSync(assetsDest, { recursive: true });
429
+ } catch (err) {
430
+ return fail("bundle", `could not create ${assetsDest}: ${describe(err)}`);
431
+ }
432
+ logWriter?.write?.({
433
+ src: "build",
434
+ level: "info",
435
+ msg: `${command.file} ${command.args.join(" ")}`,
436
+ event: "js_swap"
437
+ });
438
+ const spawn = spawnFn || ((cmd, args, opts) => e.spawn(cmd, args, opts));
439
+ const transcript = [];
440
+ const push = (line) => {
441
+ const msg = cleanLine(line);
442
+ if (msg.trim() === "") return;
443
+ transcript.push(msg);
444
+ logWriter?.write?.({
445
+ src: "build",
446
+ level: "debug",
447
+ msg,
448
+ raw: true,
449
+ event: "js_swap"
450
+ });
451
+ };
452
+ let child;
453
+ try {
454
+ child = spawn(command.file, command.args, {
455
+ cwd: root,
456
+ stdio: [
457
+ "ignore",
458
+ "pipe",
459
+ "pipe"
460
+ ],
461
+ env: {
462
+ ...process.env,
463
+ FORCE_COLOR: "0"
464
+ }
465
+ });
466
+ } catch (err) {
467
+ return fail("bundle", `could not run ${command.file} ${command.args[0]}: ${describe(err)}`);
468
+ }
469
+ const reader = {
470
+ out: createLineReader(push),
471
+ err: createLineReader(push)
472
+ };
473
+ child.stdout?.setEncoding?.("utf-8");
474
+ child.stderr?.setEncoding?.("utf-8");
475
+ child.stdout?.on("data", (chunk) => reader.out.push(chunk));
476
+ child.stderr?.on("data", (chunk) => reader.err.push(chunk));
477
+ const stopHeartbeat = startBuildHeartbeat({
478
+ intervalMs: heartbeatMs,
479
+ elapsed,
480
+ emit: onHeartbeat,
481
+ label: "swap"
482
+ });
483
+ let outcome;
484
+ try {
485
+ outcome = await waitForChild(child);
486
+ } finally {
487
+ stopHeartbeat();
488
+ }
489
+ reader.out.flush();
490
+ reader.err.flush();
491
+ if (outcome.error) return fail("bundle", `could not run ${command.file} ${command.args[0]}: ${describe(outcome.error)}`);
492
+ if (outcome.code !== 0) {
493
+ const how = outcome.signal ? `signal ${outcome.signal}` : `exit code ${outcome.code}`;
494
+ return fail("bundle", `\`${command.args.slice(0, 2).join(" ")}\` failed (${how})`, tailLines(transcript, LAST_LINES));
495
+ }
496
+ if (!exists(bundleOutput)) return fail("bundle", `the bundle command exited 0 but wrote no ${JS_BUNDLE_NAME} at ${bundleOutput}`);
497
+ let hermes = false;
498
+ let note;
499
+ if (hermesEnabled ?? readHermesEnabled(root)) {
500
+ const hermesc = hermescPath(root);
501
+ if (!exists(hermesc)) note = `hermesc not found at ${hermesc}; embedding the plain JS bundle instead of Hermes bytecode`;
502
+ else {
503
+ const hbc = join(tmp, `${JS_BUNDLE_NAME}.hbc`);
504
+ try {
505
+ e.runFile(hermesc, hermescArgs({
506
+ bundle: bundleOutput,
507
+ out: hbc
508
+ }));
509
+ e.runFile("mv", [hbc, bundleOutput]);
510
+ hermes = true;
511
+ } catch (err) {
512
+ return fail("hermesc", `hermesc failed on ${bundleOutput}: ${describe(err)}`);
513
+ }
514
+ }
515
+ }
516
+ try {
517
+ e.runFile("cp", [bundleOutput, join(appCopy, JS_BUNDLE_NAME)]);
518
+ e.runFile("cp", [
519
+ "-R",
520
+ `${assetsDest}/.`,
521
+ `${appCopy}/`
522
+ ]);
523
+ } catch (err) {
524
+ return fail("replace", `could not replace the JS bundle inside ${appCopy}: ${describe(err)}`);
525
+ }
526
+ try {
527
+ e.runFile("codesign", [
528
+ "--force",
529
+ "--sign",
530
+ "-",
531
+ appCopy
532
+ ]);
533
+ } catch (err) {
534
+ return fail("codesign", `codesign --force --sign - ${appCopy} failed: ${describe(err)}`);
535
+ }
536
+ logWriter?.write?.({
537
+ src: "build",
538
+ level: "info",
539
+ msg: `JS swap done: ${hermes ? "hermes bytecode" : "plain JS"} into ${appCopy} in ${elapsed()}ms`,
540
+ event: "js_swap"
541
+ });
542
+ const result = {
543
+ ok: true,
544
+ appPath: appCopy,
545
+ tmpDir: tmp,
546
+ hermes,
547
+ durationMs: elapsed()
548
+ };
549
+ if (note) result.note = note;
550
+ return result;
551
+ }
552
+ function describe(err) {
553
+ return String(err?.message || err);
554
+ }
555
+ //#endregion
556
+ //#region src/commands/dev-client.ts
557
+ function devClientScheme(root, appPath = null, { exec = null } = {}) {
558
+ if (!hasDevClient(root)) return void 0;
559
+ const fromBundle = pickDevClientScheme(readBundleSchemes(appPath, { exec }));
560
+ if (fromBundle) return fromBundle;
561
+ const app = readJson(join(root, "app.json"));
562
+ const raw = app?.expo?.scheme ?? app?.scheme ?? null;
563
+ const scheme = Array.isArray(raw) ? raw.find((s) => typeof s === "string" && s.trim() !== "") : raw;
564
+ if (typeof scheme !== "string" || scheme.trim() === "") return void 0;
565
+ return scheme.trim();
566
+ }
567
+ function schemesFromInfoPlist(plist) {
568
+ const types = plist?.CFBundleURLTypes;
569
+ if (!Array.isArray(types)) return [];
570
+ const out = [];
571
+ for (const type of types) {
572
+ const schemes = type?.CFBundleURLSchemes;
573
+ if (Array.isArray(schemes)) out.push(...schemes.filter((s) => typeof s === "string" && s.trim() !== ""));
574
+ }
575
+ return out;
576
+ }
577
+ function readBundleSchemes(appPath, { exec = null } = {}) {
578
+ if (typeof appPath !== "string" || appPath.trim() === "") return [];
579
+ const e = exec || getExecutor();
580
+ let out;
581
+ try {
582
+ out = e.runFile("plutil", [
583
+ "-convert",
584
+ "json",
585
+ "-o",
586
+ "-",
587
+ join(appPath, "Info.plist")
588
+ ]);
589
+ } catch {
590
+ return [];
591
+ }
592
+ try {
593
+ return schemesFromInfoPlist(JSON.parse(String(out)));
594
+ } catch {
595
+ return [];
596
+ }
597
+ }
598
+ const THIRD_PARTY_SCHEME = /^(?:fb\d+|com\.googleusercontent\.apps\.|msauth\.|msauthv2|twitterkit-|db-[a-z0-9]+$|spotify|snapchat|com\.facebook)/i;
599
+ function pickDevClientScheme(schemes) {
600
+ const all = (Array.isArray(schemes) ? schemes : []).filter((s) => typeof s === "string" && s.trim() !== "").map((s) => s.trim()).filter((s) => !/^(?:https?|mailto|tel|sms|itms(?:-apps)?)$/i.test(s));
601
+ const expo = all.filter((s) => s.startsWith("exp+"));
602
+ return (expo.length ? expo : all.filter((s) => !THIRD_PARTY_SCHEME.test(s))).toSorted((a, b) => b.length - a.length)[0] ?? null;
603
+ }
604
+ function hasDevClient(root) {
605
+ const pkg = readJson(join(root, "package.json"));
606
+ if ("expo-dev-client" in {
607
+ ...pkg?.dependencies,
608
+ ...pkg?.devDependencies
609
+ }) return true;
610
+ return isPackageResolvable(root, "expo-dev-client");
611
+ }
612
+ function readJson(file) {
613
+ try {
614
+ return JSON.parse(readFileSync(file, "utf-8"));
615
+ } catch {
616
+ return null;
617
+ }
618
+ }
619
+ //#endregion
620
+ export { swapJsBundle as a, ensureWorkspaceStorageSafely as c, noMetroMessage as d, noMetroRemedy as f, warmMetro as h, detectEntryFile as i, gateShouldRetry as l, sleep as m, pickDevClientScheme as n, needsPrebuild as o, resolveMetroWithRetry as p, schemesFromInfoPlist as r, runPrebuild as s, devClientScheme as t, launchOutcomeRecord as u };