stim 1.5.0 → 1.7.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 +1 -1
  2. package/dist/{android-DJWiTBkz.mjs → android-DWssqHQ8.mjs} +165 -661
  3. package/dist/{android-sobvO9ia.mjs → android-Da-G_o3n.mjs} +646 -548
  4. package/dist/{android-BW8i_YxU.mjs → android-DeYdjjBP.mjs} +6 -2
  5. package/dist/{android-cas-CSS8gjR7.mjs → android-cas-SWIjzeuL.mjs} +54 -18
  6. package/dist/android-cas-compiler.mjs +1 -1
  7. package/dist/{app-install--PAocWvq.mjs → app-install-DJg7bVv-.mjs} +36 -10
  8. package/dist/build-lock-CgtsB6TV.mjs +604 -0
  9. package/dist/{build-slots-CuPszJDu.mjs → build-slots-C-oAHRT1.mjs} +2 -2
  10. package/dist/{cache-manifest-uoOKhgUD.mjs → cache-manifest-CYJi8-Hx.mjs} +2 -2
  11. package/dist/cache-manifest.d.mts +1 -1
  12. package/dist/cache-manifest.mjs +1 -1
  13. package/dist/cli.mjs +14 -14
  14. package/dist/collector-run.d.mts +2 -0
  15. package/dist/collector-run.mjs +7 -7
  16. package/dist/{command-output-BuIb_ZOI.mjs → command-output-B3ycpsYv.mjs} +2 -2
  17. package/dist/{config-DMP7L7H6.mjs → config-B7iyKWkL.mjs} +56 -16
  18. package/dist/{dependency-state-CTL8A9p3.mjs → dependency-state-D0M7oKA7.mjs} +2 -2
  19. package/dist/{deps-BAGOnrc7.mjs → deps-oCXVNEqQ.mjs} +6 -6
  20. package/dist/detached-entry-Bpo6ZY_I.mjs +65 -0
  21. package/dist/{dev-client-BoifdqkU.mjs → dev-client-DDUkWgJL.mjs} +12 -12
  22. package/dist/{device-BUw4JBuH.mjs → device-CBSaC6BU.mjs} +7 -7
  23. package/dist/{device-DvKJ454A.mjs → device-CLeDp-KH.mjs} +17 -15
  24. package/dist/{device-lease-r7yEQ1R8.mjs → device-lease-Ffz_M5t0.mjs} +3 -3
  25. package/dist/{device-pool-DU4FQKp5.mjs → device-pool-DNSHL5z8.mjs} +4 -4
  26. package/dist/{device-remote-BTUqbl0i.mjs → device-remote-BprxZp-m.mjs} +11 -11
  27. package/dist/{doctor-Di_B26uh.mjs → doctor-CTAx9gMu.mjs} +90 -46
  28. package/dist/{error-diagnostics-CTePWAzW.mjs → error-diagnostics-D1Kaf-eT.mjs} +23 -17
  29. package/dist/{exec-CyylIdq9.mjs → exec-B29XNuhP.mjs} +14 -2
  30. package/dist/{gc-C20dWw_8.mjs → gc-CX0KRfLG.mjs} +17 -16
  31. package/dist/{guide-sAnoty2y.mjs → guide-CPmAG9me.mjs} +61 -22
  32. package/dist/{guide-status-BF6RsJJp.mjs → guide-status-Craq9z0t.mjs} +3 -3
  33. package/dist/{ios-BdpkncL6.mjs → ios-BRXYSxSc.mjs} +34 -37
  34. package/dist/{ios-device-DJOi9FR5.mjs → ios-device-CDbvn5H2.mjs} +2 -2
  35. package/dist/{ios-device-D3pzTKQE.mjs → ios-device-CFXA46ez.mjs} +1 -1
  36. package/dist/{ios-Deh5js5V.mjs → ios-lEJ5pKsq.mjs} +3 -3
  37. package/dist/{logs-BfQdIhiF.mjs → logs-CakdBTtm.mjs} +6 -6
  38. package/dist/{logs-query-Cdp5aaCH.mjs → logs-query-BGL-Pl12.mjs} +1 -1
  39. package/dist/metro-C7bcJ0uo.mjs +233 -0
  40. package/dist/{named-ports-DH9SXd0f.mjs → named-ports-DzMOlmaX.mjs} +57 -42
  41. package/dist/{ownership-CD_XnpDu.mjs → ownership-Cp52QAK1.mjs} +3 -3
  42. package/dist/{ownership-CVY9cqCr.mjs → ownership-D5vkdm8H.mjs} +13 -13
  43. package/dist/{ports-A6uTJSPj.mjs → ports-BIbJaKNW.mjs} +3 -3
  44. package/dist/{process-output-C7IlIzd7.mjs → process-output-Dl5uKE03.mjs} +1 -1
  45. package/dist/{project-B_SDt4ip.mjs → project-DDZWf_16.mjs} +3 -3
  46. package/dist/{reclaim-C_rB25f4.mjs → reclaim-D614jI6_.mjs} +11 -11
  47. package/dist/{reload-CYBJbJfI.mjs → reload-ChW46VDb.mjs} +9 -9
  48. package/dist/{server-bare-H3pAhbMs.mjs → server-bare-BQkyxa_b.mjs} +1 -1
  49. package/dist/{server-expo-Ds8_mfng.mjs → server-expo-BVAA1OqJ.mjs} +23 -11
  50. package/dist/server-expo-YDDG3eP7.mjs +2 -0
  51. package/dist/{settings-kO2ZqIKy.mjs → settings-BXXorVM2.mjs} +8 -5
  52. package/dist/{slot-launch-BpHiHH6A.mjs → slot-launch-DbLvyrup.mjs} +4 -4
  53. package/dist/{start-DiEfqEWk.mjs → start-TTVILlUi.mjs} +111 -38
  54. package/dist/{state-CnWzvCWP.mjs → state-DZobzxLW.mjs} +2 -2
  55. package/dist/{stats-Fo9ACIv7.mjs → stats-BYcZ6n1h.mjs} +1 -1
  56. package/dist/{stats-DHrO0frH.mjs → stats-CLg5RuDS.mjs} +4 -4
  57. package/dist/{status-J5LAXpag.mjs → status-DUNy_HZs.mjs} +14 -15
  58. package/dist/{stop-CM1fejKW.mjs → stop-CnUpnFT5.mjs} +2 -2
  59. package/dist/{stop-0jJNahwN.mjs → stop-ZrfODZ51.mjs} +13 -13
  60. package/dist/supervisor-run.mjs +7 -5
  61. package/dist/{teardown-CAVcWeRD.mjs → teardown-5J5UXz2P.mjs} +14 -17
  62. package/dist/{workspace-process-lock-Bz19_R9R.mjs → workspace-process-lock-BYfZz_S-.mjs} +1 -1
  63. package/dist/{workspace-state-WVwWw_hB.mjs → workspace-state-BT5E3SA2.mjs} +3 -3
  64. package/dist/{worktree-CeUKDAQw.mjs → worktree-BfE4lzHK.mjs} +21 -16
  65. package/dist/{worktree-DJnBbQZ9.mjs → worktree-FG_ZKKdb.mjs} +13 -9
  66. package/dist/{xcode-BqDeRkpT.mjs → xcode-B6YaWeg0.mjs} +92 -17
  67. package/package.json +12 -8
  68. package/shim/android-optimizations.gradle +6 -0
  69. package/dist/metro-ato6ef3J.mjs +0 -99
  70. package/dist/ports-BZprTJRa.mjs +0 -90
  71. package/dist/server-expo-CSwUVYOa.mjs +0 -2
@@ -1,4 +1,5 @@
1
- import { t as getExecutor } from "./exec-CyylIdq9.mjs";
1
+ import { t as getExecutor } from "./exec-B29XNuhP.mjs";
2
+ import { s as androidToolCwd } from "./android-DWssqHQ8.mjs";
2
3
  //#region src/collector/android.ts
3
4
  const LEVEL_BY_LETTER = {
4
5
  V: "debug",
@@ -100,7 +101,10 @@ function androidClockOffset(serial, { exec = getExecutor(), now = Date.now } = {
100
101
  "shell",
101
102
  "date",
102
103
  "+%s%3N"
103
- ], { timeoutMs: 1e3 }).trim();
104
+ ], {
105
+ timeoutMs: 1e3,
106
+ cwd: androidToolCwd()
107
+ }).trim();
104
108
  const after = now();
105
109
  if (!/^\d{13}$/.test(output) || after < before || after - before > 1e3) return null;
106
110
  return Math.round((before + after) / 2) - Number(output);
@@ -1,16 +1,37 @@
1
- import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { D as withDirLock, u as getConfigDir } from "./config-DMP7L7H6.mjs";
3
- import { r as register } from "./cache-manifest-uoOKhgUD.mjs";
4
- import { _ as workspaceLogsDir, c as ensureWorkspaceStorage, u as sharedCcache } from "./workspace-state-WVwWw_hB.mjs";
5
- import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-C7IlIzd7.mjs";
6
- import { l as phaseLine } from "./command-output-BuIb_ZOI.mjs";
7
- import { n as androidHome } from "./android-DJWiTBkz.mjs";
8
- import { d as startBuildHeartbeat, n as HEARTBEAT_INTERVAL_MS } from "./xcode-BqDeRkpT.mjs";
1
+ import { t as getExecutor } from "./exec-B29XNuhP.mjs";
2
+ import { D as withDirLock, u as getConfigDir } from "./config-B7iyKWkL.mjs";
3
+ import { r as register } from "./cache-manifest-CYJi8-Hx.mjs";
4
+ import { _ as workspaceLogsDir, c as ensureWorkspaceStorage, u as sharedCcache } from "./workspace-state-BT5E3SA2.mjs";
5
+ import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-Dl5uKE03.mjs";
6
+ import { r as androidHome } from "./android-DWssqHQ8.mjs";
7
+ import { l as phaseLine } from "./command-output-B3ycpsYv.mjs";
8
+ import { d as startBuildHeartbeat, n as HEARTBEAT_INTERVAL_MS } from "./xcode-B6YaWeg0.mjs";
9
9
  import { accessSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
10
10
  import { createHash, randomUUID } from "node:crypto";
11
- import { dirname, isAbsolute, join, relative } from "node:path";
11
+ import { dirname, isAbsolute, join, relative, sep } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
13
  import chalk from "chalk";
14
+ /** The ABI name a build for every ABI has to fit; `stim android` narrows a build to the device's. */
15
+ const LONGEST_ANDROID_ABI = "armeabi-v7a";
16
+ function androidPathRoom(root, { abi = null, variant = null, platform = process.platform } = {}) {
17
+ if (platform !== "win32") return null;
18
+ const abiName = abi ?? LONGEST_ANDROID_ABI;
19
+ const tail = 198 + (/release$/i.test(variant ?? "") ? 14 : 5) - 5 + abiName.length;
20
+ const longest = root.length + tail;
21
+ if (longest < 240) return null;
22
+ return {
23
+ root,
24
+ abi: abiName,
25
+ longest,
26
+ maxRootLength: 239 - tail
27
+ };
28
+ }
29
+ function androidPathRoomMessage({ root, abi, longest, maxRootLength }) {
30
+ return `The project root ${root} is ${root.length} characters, so the longest known Android native object path (react_codegen_safeareacontext for ${abi}) reaches ${longest} characters, past the 239 the NDK's ninja can be given on Windows; the root can be at most ${maxRootLength} characters.`;
31
+ }
32
+ function androidPathRoomRemedy({ root }) {
33
+ return `Map the project to a drive letter (\`subst X: "${root}"\`) and run Stim from X:\\, or move it under a shorter root.`;
34
+ }
14
35
  const MAX_MESSAGE_LENGTH = 300;
15
36
  const KOTLIN_URI = /^e:\s+file:\/\/(\/\S+?):(\d+):(\d+)\s+(.*)$/;
16
37
  const KOTLIN_PAREN = /^e:\s+(\S+?):\s*\((\d+),\s*(\d+)\):\s*(.*)$/;
@@ -251,7 +272,7 @@ function decodePath(path) {
251
272
  }
252
273
  }
253
274
  function stripCarriage(line) {
254
- const text = String(line ?? "");
275
+ const text = String(line ?? "").replace(/\r$/, "");
255
276
  const idx = text.lastIndexOf("\r");
256
277
  return idx === -1 ? text : text.slice(idx + 1);
257
278
  }
@@ -335,7 +356,7 @@ function ccacheActivityLine(activity) {
335
356
  }
336
357
  function lookupCcache() {
337
358
  try {
338
- return parseCcacheBinary(getExecutor().runQuiet("command -v ccache", { timeoutMs: 5e3 }));
359
+ return parseCcacheBinary(getExecutor().findExecutable("ccache"));
339
360
  } catch {
340
361
  return null;
341
362
  }
@@ -399,7 +420,7 @@ function androidDir(root) {
399
420
  return join(root, "android");
400
421
  }
401
422
  function gradlewPath(root) {
402
- return join(androidDir(root), "gradlew");
423
+ return join(androidDir(root), process.platform === "win32" ? "gradlew.bat" : "gradlew");
403
424
  }
404
425
  function apkOutputsDir(root) {
405
426
  return join(androidDir(root), "app", "build", "outputs", "apk");
@@ -472,7 +493,7 @@ function apkInDir(dir) {
472
493
  if (metadata) {
473
494
  const named = parseOutputMetadata(metadata);
474
495
  if (named) {
475
- const abs = named.startsWith("/") ? named : join(dir, named);
496
+ const abs = isAbsolute(named) ? named : join(dir, named);
476
497
  if (existsSync(abs)) return abs;
477
498
  }
478
499
  }
@@ -518,7 +539,7 @@ function findDebugApksUnder(base) {
518
539
  function locateApk(root, transcript = "", variant = null) {
519
540
  const fromTranscript = parseApkFromTranscript(transcript);
520
541
  if (fromTranscript) {
521
- const abs = fromTranscript.startsWith("/") ? fromTranscript : join(androidDir(root), fromTranscript);
542
+ const abs = isAbsolute(fromTranscript) ? fromTranscript : join(androidDir(root), fromTranscript);
522
543
  if (existsSync(abs)) return { apkPath: abs };
523
544
  }
524
545
  if (variant) {
@@ -530,7 +551,7 @@ function locateApk(root, transcript = "", variant = null) {
530
551
  const found = findDebugApksUnder(apkOutputsDir(root));
531
552
  if (found.length === 1) {
532
553
  const apk = found[0];
533
- const suggested = variantNameOf(relative(apkOutputsDir(root), apk).split("/").slice(0, -1));
554
+ const suggested = variantNameOf(relative(apkOutputsDir(root), apk).split(sep).slice(0, -1));
534
555
  return {
535
556
  apkPath: apk,
536
557
  note: `no APK in ${relative(androidDir(root), debugApkDir(root))}; using ${relative(androidDir(root), apk)}${suggested ? ` -- set the android.variant setting to "${suggested}" to build this variant explicitly` : ""}`
@@ -714,7 +735,7 @@ function gradleArgs(task, { buildCache = true, abi = null } = {}) {
714
735
  ...abi ? [`-PreactNativeArchitectures=${abi}`] : []
715
736
  ];
716
737
  }
717
- async function buildAndroid({ root, logWriter, variant = null, abi = null }, { spawnFn = null, now = Date.now, env = process.env, buildCache = true, ccache = null, cas = null, pch = "auto", compilerCacheDisabled = false, heartbeatMs = HEARTBEAT_INTERVAL_MS, estimateMs = null, onHeartbeat = (line) => console.error(line), onNote = (line) => console.error(line) } = {}) {
738
+ async function buildAndroid({ root, logWriter, variant = null, abi = null }, { spawnFn = null, now = Date.now, env = process.env, buildCache = true, ccache = null, cas = null, pch = "auto", compilerCacheDisabled = false, heartbeatMs = HEARTBEAT_INTERVAL_MS, estimateMs = null, onHeartbeat = (line) => console.error(line), onNote = (line) => console.error(line), platform = process.platform } = {}) {
718
739
  const project = discoverAndroidProject(root);
719
740
  if (project.failed) return {
720
741
  ok: false,
@@ -740,6 +761,21 @@ async function buildAndroid({ root, logWriter, variant = null, abi = null }, { s
740
761
  lastLines: [],
741
762
  durationMs: 0
742
763
  };
764
+ const room = androidPathRoom(root, {
765
+ abi,
766
+ variant,
767
+ platform
768
+ });
769
+ if (room) return {
770
+ ok: false,
771
+ code: "STIM_PATH_TOO_LONG",
772
+ reason: androidPathRoomMessage(room),
773
+ remedy: androidPathRoomRemedy(room),
774
+ diagnostics: [],
775
+ truncated: 0,
776
+ lastLines: [],
777
+ durationMs: 0
778
+ };
743
779
  const spawn = spawnFn || ((cmd, args, opts) => getExecutor().spawn(cmd, args, opts));
744
780
  const task = assembleTaskFor(variant);
745
781
  const args = gradleArgs(task, {
@@ -859,7 +895,7 @@ async function buildAndroid({ root, logWriter, variant = null, abi = null }, { s
859
895
  ok: false,
860
896
  code: BUILD_ERROR,
861
897
  reason: `\`./gradlew ${task}\` left ${located.candidates.length} debug APKs under ${apkOutputsDir(root)}, and nothing says which flavor to install.`,
862
- remedy: `Set the android.variant setting to the variant to install -- e.g. {"android": {"variant": "${variantNameOf(relative(apkOutputsDir(root), located.candidates[0]).split("/").slice(0, -1))}"}} in .stim.json.`,
898
+ remedy: `Set the android.variant setting to the variant to install -- e.g. {"android": {"variant": "${variantNameOf(relative(apkOutputsDir(root), located.candidates[0]).split(sep).slice(0, -1))}"}} in .stim.json.`,
863
899
  diagnostics: [],
864
900
  truncated: 0,
865
901
  lastLines: located.candidates.map((c) => relative(androidDir(root), c)),
@@ -1078,4 +1114,4 @@ function resolveAndroidCas(root, env = process.env) {
1078
1114
  };
1079
1115
  }
1080
1116
  //#endregion
1081
- export { buildAndroid as a, CCACHE_NOT_RUN as c, projectCmakeLauncher as d, resolveCcache as f, apkOutputsDir as i, CCACHE_UNAVAILABLE as l, resolveAndroidCas as n, productFlavorRefusal as o, formatDiagnostic as p, resolveAndroidCompilerCache as r, readProductFlavors as s, readAndroidCasToolchain as t, ccacheActivityLine as u };
1117
+ export { buildAndroid as a, CCACHE_NOT_RUN as c, projectCmakeLauncher as d, resolveCcache as f, androidPathRoomRemedy as g, androidPathRoomMessage as h, apkOutputsDir as i, CCACHE_UNAVAILABLE as l, androidPathRoom as m, resolveAndroidCas as n, productFlavorRefusal as o, formatDiagnostic as p, resolveAndroidCompilerCache as r, readProductFlavors as s, readAndroidCasToolchain as t, ccacheActivityLine as u };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
+ import { t as getExecutor } from "./exec-B29XNuhP.mjs";
3
3
  import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
4
4
  import { createHash, randomUUID } from "node:crypto";
5
5
  import { join } from "node:path";
@@ -1,11 +1,11 @@
1
- import { t as getExecutor } from "./exec-CyylIdq9.mjs";
1
+ import { t as getExecutor } from "./exec-B29XNuhP.mjs";
2
2
  import { o as parseNdjsonText } from "./ndjson-BZwI1NBv.mjs";
3
- import { t as DEV_MENU_LAUNCH_ARGS } from "./ios-device-D3pzTKQE.mjs";
4
- import { s as isAppLaunchError } from "./command-output-BuIb_ZOI.mjs";
5
- import { l as iosSimulatorFailureAdvice, p as listUserApps, w as uninstallIosApp } from "./ios-Deh5js5V.mjs";
3
+ import { t as DEV_MENU_LAUNCH_ARGS } from "./ios-device-CFXA46ez.mjs";
4
+ import { s as isAppLaunchError } from "./command-output-B3ycpsYv.mjs";
5
+ import { l as iosSimulatorFailureAdvice, p as listUserApps, w as uninstallIosApp } from "./ios-lEJ5pKsq.mjs";
6
6
  import { closeSync, lstatSync, openSync, readFileSync, readSync, readdirSync } from "node:fs";
7
7
  import { createHash } from "node:crypto";
8
- import { join } from "node:path";
8
+ import { isAbsolute, join } from "node:path";
9
9
  //#region src/engine/installed-artifact.ts
10
10
  const READ_CHUNK_BYTES = 1048576;
11
11
  const DEVICE_PATH_TIMEOUT_MS = 3e4;
@@ -30,7 +30,7 @@ function parseDeviceSha256(text) {
30
30
  function parseAppContainerPath(text) {
31
31
  if (typeof text !== "string") return null;
32
32
  const lines = text.split("\n").map((line) => line.trim()).filter(Boolean);
33
- return lines.length === 1 && lines[0].startsWith("/") ? lines[0] : null;
33
+ return lines.length === 1 && isAbsolute(lines[0]) ? lines[0] : null;
34
34
  }
35
35
  function artifactsMatch(local, installed) {
36
36
  return Boolean(local) && local === installed;
@@ -305,6 +305,11 @@ function iosAppProcess(udid, bundleId, { exec = null } = {}) {
305
305
  }
306
306
  return null;
307
307
  }
308
+ function launchedIosAppAfterNoHandle(error, udid, bundleId, exec) {
309
+ const stderr = String(error?.stderr ?? "");
310
+ if (!stderr.includes("domain=NSPOSIXErrorDomain, code=3") || !stderr.includes(`Application launch for '${bundleId}' did not return a process handle nor launch error.`)) return null;
311
+ return iosAppProcess(udid, bundleId, { exec }) ?? null;
312
+ }
308
313
  function launchIosApp({ udid, bundleId, metroPort, devClientScheme = null, consolePaths }, { exec = null } = {}) {
309
314
  const e = exec || getExecutor();
310
315
  const launchArgs = [
@@ -335,9 +340,11 @@ function launchIosApp({ udid, bundleId, metroPort, devClientScheme = null, conso
335
340
  }
336
341
  if (devClientScheme) {
337
342
  const url = devClientUrl(devClientScheme, metroPort);
343
+ let launchedWithInitialUrl = false;
338
344
  try {
339
345
  if (consolePaths && iosAppProcess(udid, bundleId, { exec: e }) === null) {
340
346
  const initialUrl = new URL(url).searchParams.get("url");
347
+ launchedWithInitialUrl = true;
341
348
  const pid = parseLaunchedPid(e.runFile("xcrun", [
342
349
  ...launchArgs,
343
350
  "--initialUrl",
@@ -364,14 +371,23 @@ function launchIosApp({ udid, bundleId, metroPort, devClientScheme = null, conso
364
371
  jsLocation: jsLocationValue(metroPort)
365
372
  };
366
373
  } catch (err) {
374
+ const pid = launchedWithInitialUrl ? launchedIosAppAfterNoHandle(err, udid, bundleId, e) : null;
375
+ if (pid) return {
376
+ ok: true,
377
+ mode: "launch",
378
+ url,
379
+ jsLocation: jsLocationValue(metroPort),
380
+ pid
381
+ };
367
382
  return {
368
383
  failed: true,
369
384
  code: LAUNCH_ERROR,
370
- reason: `simctl openurl ${url} failed: ${describeIosSimulatorFailure(err, e)}`
385
+ reason: `simctl ${launchedWithInitialUrl ? `launch ${bundleId}` : `openurl ${url}`} failed: ${describeIosSimulatorFailure(err, e)}`
371
386
  };
372
387
  }
373
388
  }
374
389
  }
390
+ const preLaunchPid = iosAppProcess(udid, bundleId, { exec: e });
375
391
  try {
376
392
  const result = {
377
393
  ok: true,
@@ -381,6 +397,16 @@ function launchIosApp({ udid, bundleId, metroPort, devClientScheme = null, conso
381
397
  if (metroPort !== null) result.jsLocation = jsLocationValue(metroPort);
382
398
  return result;
383
399
  } catch (err) {
400
+ const pid = preLaunchPid === null ? launchedIosAppAfterNoHandle(err, udid, bundleId, e) : null;
401
+ if (pid) {
402
+ const result = {
403
+ ok: true,
404
+ mode: "launch",
405
+ pid
406
+ };
407
+ if (metroPort !== null) result.jsLocation = jsLocationValue(metroPort);
408
+ return result;
409
+ }
384
410
  return {
385
411
  failed: true,
386
412
  code: LAUNCH_ERROR,
@@ -513,7 +539,7 @@ function reverseMetroPorts({ serial, metroPort, devicePorts = null }, { exec = n
513
539
  reversed: pairs.map(([device, host]) => `tcp:${device}->tcp:${host}`)
514
540
  };
515
541
  }
516
- const EMULATOR_HOST_LOOPBACK = "10.0.2.2";
542
+ const EMULATOR_HOST_LOOPBACK = "127.0.0.1";
517
543
  const PHYSICAL_HOST_LOOPBACK = "localhost";
518
544
  function androidMetroHost(physical) {
519
545
  return physical ? PHYSICAL_HOST_LOOPBACK : EMULATOR_HOST_LOOPBACK;
@@ -1134,12 +1160,12 @@ function unverifiedLaunchLines({ platform, metroPort, waitedMs = VERIFY_TIMEOUT_
1134
1160
  return lines;
1135
1161
  }
1136
1162
  if (platform === "ios") {
1137
- push(picker);
1163
+ if (devClient) push(picker);
1138
1164
  if (url && udid) push(`Retry the deep link: xcrun simctl openurl ${udid} '${url}'`);
1139
1165
  else if (udid && bundleId) push(`Re-launch: xcrun simctl launch --terminate-running-process ${udid} ${bundleId}`);
1140
1166
  } else {
1141
1167
  if (url && serial) push(`Re-send the dev-client deep link -- this is the command that points the app at THIS workspace's Metro: adb -s ${serial} shell am start -a android.intent.action.VIEW -d ${deviceShellArg(deviceShellArg(url))} --ez ${ANDROID_DISABLE_AUTO_LAUNCH_EXTRA} true`);
1142
- push(picker);
1168
+ if (devClient) push(picker);
1143
1169
  if (serial && bundleId) push(`If the app is stuck, restart its process: ${component ? `adb -s ${serial} shell am force-stop ${bundleId} && adb -s ${serial} shell am start -n ${component}` : url ? `adb -s ${serial} shell am force-stop ${bundleId} && adb -s ${serial} shell am start -a android.intent.action.VIEW -d ${deviceShellArg(deviceShellArg(url))} --ez ${ANDROID_DISABLE_AUTO_LAUNCH_EXTRA} true` : `adb -s ${serial} shell am force-stop ${bundleId} && adb -s ${serial} shell monkey -p ${bundleId} 1`}`);
1144
1170
  }
1145
1171
  lines.push(`Then check \`stim logs --source metro\`${mode ? ` (${mode})` : ""} for a bundle request.`);