stim 1.8.0 → 1.10.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 (91) hide show
  1. package/README.md +23 -155
  2. package/dist/{android-DWssqHQ8.mjs → android-787HhP5w.mjs} +168 -39
  3. package/dist/{android-BRgBYzU0.mjs → android-DBkeHwpm.mjs} +250 -200
  4. package/dist/{android-DeYdjjBP.mjs → android-DPov-fv2.mjs} +2 -2
  5. package/dist/{android-cas-SWIjzeuL.mjs → android-cas-CITmIeWY.mjs} +22 -14
  6. package/dist/android-cas-compiler.mjs +2 -2
  7. package/dist/app-install-CrJ6t_bv.mjs +819 -0
  8. package/dist/budget-BP4jZZL4.mjs +620 -0
  9. package/dist/build-progress-BXx-ZBe4.mjs +512 -0
  10. package/dist/{cache-manifest-CYJi8-Hx.mjs → cache-manifest-Pubfp8wV.mjs} +4 -4
  11. package/dist/cache-manifest.mjs +1 -1
  12. package/dist/cli.mjs +22 -19
  13. package/dist/collector-run.d.mts +7 -1
  14. package/dist/collector-run.mjs +10 -9
  15. package/dist/{command-output-B3ycpsYv.mjs → command-output-D5z2_bcF.mjs} +11 -2
  16. package/dist/{config-B7iyKWkL.mjs → config-BEq0Qw_a.mjs} +134 -121
  17. package/dist/{dependency-state-D0M7oKA7.mjs → dependency-state-r9UesA9a.mjs} +2 -2
  18. package/dist/{deps-oCXVNEqQ.mjs → deps-DP4eWqea.mjs} +8 -5
  19. package/dist/{detached-entry-Bpo6ZY_I.mjs → detached-entry-DJHoca49.mjs} +1 -1
  20. package/dist/{device-BUoZyMoR.mjs → dev-client-92U593KT.mjs} +1090 -660
  21. package/dist/{device-RdujjHLB.mjs → device-DhTZe7L4.mjs} +13 -20
  22. package/dist/{build-lock-CgtsB6TV.mjs → device-lease-CbEaIk7b.mjs} +481 -27
  23. package/dist/{device-pool-BxRwdufO.mjs → device-pool-Di8Q-9Ro.mjs} +7 -7
  24. package/dist/{device-remote-ByN8P6CB.mjs → device-remote-Dy1jGj3U.mjs} +645 -119
  25. package/dist/{doctor-BMt156qe.mjs → doctor-B-inAJmt.mjs} +137 -113
  26. package/dist/{error-diagnostics-D1Kaf-eT.mjs → error-diagnostics-xxWKNO3-.mjs} +22 -17
  27. package/dist/exec-DK8KaVHK.mjs +133 -0
  28. package/dist/{gc-v2YcoJvm.mjs → gc-ChPFZMRC.mjs} +836 -813
  29. package/dist/{guide-BTAlD1_y.mjs → guide-DwmaD_Wa.mjs} +953 -209
  30. package/dist/guide-status-CWvti2Lg.mjs +125 -0
  31. package/dist/idle-DpRLp5lj.mjs +157 -0
  32. package/dist/{build-slots-C-oAHRT1.mjs → in-use-CjUluqIX.mjs} +102 -8
  33. package/dist/{ios-BcCiI4TS.mjs → ios-B2Fpqvlb.mjs} +60 -71
  34. package/dist/{ios-BP04pY5f.mjs → ios-Dd-hYJBh.mjs} +241 -234
  35. package/dist/{ios-device-CFXA46ez.mjs → ios-device-BiOsSVzd.mjs} +3 -2
  36. package/dist/{ios-device-CauFJiYx.mjs → ios-device-CtRdEVjx.mjs} +149 -52
  37. package/dist/launch-verify-CQS_Kg-C.mjs +365 -0
  38. package/dist/{logs-CakdBTtm.mjs → logs-MARIy6m9.mjs} +20 -15
  39. package/dist/metro-BWLOJ_Wr.mjs +409 -0
  40. package/dist/{named-ports-DzMOlmaX.mjs → named-ports-DiTfSz6p.mjs} +4 -4
  41. package/dist/{ndjson-BZwI1NBv.mjs → ndjson-CDY9IkVC.mjs} +23 -43
  42. package/dist/{ndjson-DvHSsNU9.d.mts → ndjson-Du3j0AQ9.d.mts} +1 -0
  43. package/dist/{ownership-Cp52QAK1.mjs → ownership-D2xbLVoz.mjs} +2 -3
  44. package/dist/{teardown-BJqEzPo_.mjs → ownership-DZjdx8Gq.mjs} +664 -612
  45. package/dist/{ports-BIbJaKNW.mjs → ports-CLSSFOuz.mjs} +3 -3
  46. package/dist/{process-output-Dl5uKE03.mjs → process-output-Fpl8PYi8.mjs} +1 -1
  47. package/dist/{project-DDZWf_16.mjs → project-BXZzOABt.mjs} +8 -3
  48. package/dist/{reload-BdypPbjM.mjs → reload-DoETJ3rl.mjs} +11 -13
  49. package/dist/{server-bare-BQkyxa_b.mjs → server-bare-DVbb7kXH.mjs} +2 -2
  50. package/dist/{server-expo-BVAA1OqJ.mjs → server-expo-BvOCeakp.mjs} +8 -7
  51. package/dist/server-expo-DBVTdult.mjs +2 -0
  52. package/dist/{settings-BXXorVM2.mjs → settings-DOugPlPz.mjs} +59 -159
  53. package/dist/settings-EWYNwe72.mjs +378 -0
  54. package/dist/settings.schema.json +1064 -0
  55. package/dist/simslim-Ca3487H6.mjs +436 -0
  56. package/dist/{slot-launch-ZUUe0TQP.mjs → slot-launch-C5cg0fmk.mjs} +4 -4
  57. package/dist/spawn-claims-CUleC2cM.mjs +49 -0
  58. package/dist/{start-CmxtOKjU.mjs → start-F9gv5dcC.mjs} +159 -114
  59. package/dist/start-VoFsPSes.mjs +2 -0
  60. package/dist/{state-DZobzxLW.mjs → state-WGdIbi_d.mjs} +5 -3
  61. package/dist/{stats-BYcZ6n1h.mjs → stats-BAgJaLA4.mjs} +66 -5
  62. package/dist/{stats-CLg5RuDS.mjs → stats-DBYJ5bxL.mjs} +4 -4
  63. package/dist/status-DLCD-o3s.mjs +764 -0
  64. package/dist/status-Dr4fRkym.mjs +216 -0
  65. package/dist/{guide-status-Craq9z0t.mjs → stim-installations-CINXy_su.mjs} +3 -122
  66. package/dist/{stop-C8JW6kKp.mjs → stop-Dj4pfmyv.mjs} +111 -31
  67. package/dist/{stop-B1kOpYei.mjs → stop-ZvOeTmKb.mjs} +2 -2
  68. package/dist/supervisor-run.d.mts +11 -2
  69. package/dist/supervisor-run.mjs +228 -42
  70. package/dist/{workspace-process-lock-BYfZz_S-.mjs → workspace-process-lock-BXA96myD.mjs} +14 -9
  71. package/dist/workspaces-BTUyFfkv.mjs +1003 -0
  72. package/dist/worktree-4aZg2NnI.mjs +2 -0
  73. package/dist/{worktree-CFLA-6MX.mjs → worktree-BMS7GeqB.mjs} +523 -61
  74. package/dist/{worktree-FG_ZKKdb.mjs → worktree-BlizKDpZ.mjs} +200 -54
  75. package/dist/{xcode-B6YaWeg0.mjs → xcode-B2tBkf0m.mjs} +66 -57
  76. package/package.json +6 -6
  77. package/skill/SKILL.md +2 -0
  78. package/dist/app-install-BDbsSgCl.mjs +0 -1175
  79. package/dist/dev-client-BmT-ioFI.mjs +0 -797
  80. package/dist/device-lease-Ffz_M5t0.mjs +0 -359
  81. package/dist/exec-B29XNuhP.mjs +0 -77
  82. package/dist/logs-query-BGL-Pl12.mjs +0 -290
  83. package/dist/metro-C7bcJ0uo.mjs +0 -233
  84. package/dist/ownership-wolE-0Do.mjs +0 -470
  85. package/dist/public-http-probe-CyMDrQ3c.mjs +0 -61
  86. package/dist/reclaim-KJ-0uWEF.mjs +0 -361
  87. package/dist/server-expo-YDDG3eP7.mjs +0 -2
  88. package/dist/spawn-entry-nCE6m770.mjs +0 -14
  89. package/dist/status-DdkLki47.mjs +0 -426
  90. package/dist/workspace-state-BT5E3SA2.mjs +0 -146
  91. package/dist/{metro-store-CDDZHtPl.mjs → metro-store-DzeWkfqW.mjs} +1 -1
@@ -1,4 +1,4 @@
1
- import { t as getExecutor } from "./exec-B29XNuhP.mjs";
1
+ import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
2
2
  //#region src/collector/ios-device.ts
3
3
  const CONSOLE_ENV = { OS_ACTIVITY_DT_MODE: "enable" };
4
4
  const DEV_MENU_LAUNCH_ARGS = [
@@ -13,6 +13,7 @@ const FATAL_MARKERS = [
13
13
  /^\*\*\* Assertion failure in/,
14
14
  /^(?:\S+:\d+: )?Fatal error: /
15
15
  ];
16
+ const TOOL_ERROR_PREFIX = "ERROR: ";
16
17
  const MIRROR_LINE = /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+[+-]\d{4}) (.+?)\[(\d+):(\d+)\] ?(?:\[([^\]\s]+)\] )?([\s\S]*)$/;
17
18
  function deviceConsoleLevel(message) {
18
19
  const text = typeof message === "string" ? message : "";
@@ -78,4 +79,4 @@ function startIosDeviceConsole({ udid, bundleId, payloadUrl = null, spawnFn = nu
78
79
  });
79
80
  }
80
81
  //#endregion
81
- export { startIosDeviceConsole as i, deviceConsoleLevel as n, parseDeviceConsoleLine as r, DEV_MENU_LAUNCH_ARGS as t };
82
+ export { startIosDeviceConsole as a, parseDeviceConsoleLine as i, TOOL_ERROR_PREFIX as n, deviceConsoleLevel as r, DEV_MENU_LAUNCH_ARGS as t };
@@ -1,5 +1,6 @@
1
- import { t as getExecutor } from "./exec-B29XNuhP.mjs";
2
- import { r as INSTALL_ERROR } from "./app-install-BDbsSgCl.mjs";
1
+ import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
2
+ import { n as TOOL_ERROR_PREFIX } from "./ios-device-BiOsSVzd.mjs";
3
+ import { i as INSTALL_ERROR } from "./app-install-CrJ6t_bv.mjs";
3
4
  import { mkdtempSync, readFileSync, rmSync } from "node:fs";
4
5
  import { join } from "node:path";
5
6
  import { tmpdir } from "node:os";
@@ -34,7 +35,10 @@ function parseDevicectlDevices(payload) {
34
35
  bootState: text(properties.bootState),
35
36
  developerModeStatus: text(properties.developerModeStatus),
36
37
  pairingState: text(connection.pairingState),
37
- transportType: text(connection.transportType)
38
+ transportType: text(connection.transportType),
39
+ platform: text(hardware.platform),
40
+ reality: text(hardware.reality),
41
+ tunnelState: text(connection.tunnelState)
38
42
  });
39
43
  }
40
44
  return out;
@@ -63,18 +67,47 @@ function listIosDevices({ exec = null } = {}) {
63
67
  }
64
68
  const DEVELOPER_MODE_REMEDY = "Turn on Settings > Privacy & Security > Developer Mode on the phone, restart it, then reconnect.";
65
69
  const PAIRING_REMEDY = "Unlock the phone, tap Trust on the pairing prompt, then reconnect the cable.";
66
- const CABLE_REMEDY = "Connect the phone with a cable and check `xcrun devicectl list devices`, then retry.";
70
+ const NOT_A_PHONE_REMEDY = "Name a physical iPhone or iPad from `xcrun devicectl list devices`, or run `stim ios` without --device to use this workspace's simulator.";
71
+ const UNREACHABLE_REMEDY = "Connect the phone with a cable, or unlock it on the same Wi-Fi network as this Mac, then check `xcrun devicectl list devices`.";
67
72
  const WIRED_TRANSPORTS = /* @__PURE__ */ new Set(["wired", "usb"]);
73
+ const WIRELESS_TRANSPORT = "localnetwork";
74
+ function isIosHardware(device) {
75
+ return (device.platform == null || device.platform.toLowerCase() === "ios") && (device.reality == null || device.reality.toLowerCase() === "physical");
76
+ }
77
+ function isReachable(device) {
78
+ return device.tunnelState?.toLowerCase() !== "unavailable";
79
+ }
68
80
  function isWired(device) {
69
81
  return device.transportType === null || WIRED_TRANSPORTS.has(device.transportType.toLowerCase());
70
82
  }
83
+ function isWirelessIosDevice(device) {
84
+ return device.transportType?.toLowerCase() === WIRELESS_TRANSPORT;
85
+ }
86
+ function isPhysical(device) {
87
+ return isIosHardware(device) && isReachable(device) && (isWired(device) || isWirelessIosDevice(device));
88
+ }
89
+ function notUsableRefusal(device) {
90
+ if (!isIosHardware(device)) return {
91
+ error: `${describe(device)} is a ${[device.reality, device.platform].filter(Boolean).join(" ")} device, and \`ios --device\` installs only on a physical iPhone or iPad.`,
92
+ remedy: NOT_A_PHONE_REMEDY
93
+ };
94
+ if (!isReachable(device)) return {
95
+ error: `${describe(device)} is paired, but devicectl reports it unavailable: it cannot reach the phone right now.`,
96
+ remedy: UNREACHABLE_REMEDY
97
+ };
98
+ return {
99
+ error: `${describe(device)} is reached over ${device.transportType}, and Stim installs on a phone only over a cable or Wi-Fi.`,
100
+ remedy: NOT_A_PHONE_REMEDY
101
+ };
102
+ }
71
103
  function describe(device) {
72
104
  return `${device.udid} (${device.name})`;
73
105
  }
74
- function wirelessRefusal(device) {
106
+ function selected(device) {
75
107
  return {
76
- error: `${describe(device)} is paired over ${device.transportType}, not a cable, and Stim installs over the cable only.`,
77
- remedy: CABLE_REMEDY
108
+ udid: device.udid,
109
+ name: device.name,
110
+ ...isWirelessIosDevice(device) ? { wireless: true } : {}
78
111
  };
79
112
  }
80
113
  function unhealthy(device) {
@@ -89,13 +122,13 @@ function unhealthy(device) {
89
122
  return null;
90
123
  }
91
124
  function iosPoolCandidates(devices) {
92
- return (Array.isArray(devices) ? devices : []).filter((device) => isWired(device) && unhealthy(device) === null);
125
+ return (Array.isArray(devices) ? devices : []).filter((device) => isPhysical(device) && unhealthy(device) === null);
93
126
  }
94
127
  function iosPoolNoCandidatesRefusal(devices) {
95
128
  const listed = Array.isArray(devices) ? devices : [];
96
- const cabled = listed.filter(isWired);
97
- const problems = cabled.map((device) => unhealthy(device));
98
- if (cabled.length > 0 && problems.every((problem) => problem !== null)) {
129
+ const physical = listed.filter(isPhysical);
130
+ const problems = physical.map((device) => unhealthy(device));
131
+ if (physical.length > 0 && problems.every((problem) => problem !== null)) {
99
132
  const reasons = problems;
100
133
  return {
101
134
  error: reasons.map((reason) => reason.error).join(" "),
@@ -104,42 +137,52 @@ function iosPoolNoCandidatesRefusal(devices) {
104
137
  }
105
138
  return resolveIosPhysicalDevice(null, listed);
106
139
  }
140
+ function pickOne(devices) {
141
+ if (devices.length === 1) return unhealthy(devices[0]) ?? selected(devices[0]);
142
+ if (devices.length > 1) return {
143
+ error: `Several devices are connected: ${devices.map(describe).join(", ")}.`,
144
+ remedy: "Name the one to build for with `stim ios --device <udid>`."
145
+ };
146
+ return null;
147
+ }
107
148
  function resolveIosPhysicalDevice(requested, devices) {
108
149
  const listed = Array.isArray(devices) ? devices : [];
109
- const cabled = listed.filter(isWired);
110
- const wireless = listed.filter((d) => !isWired(d));
150
+ const physical = listed.filter(isPhysical);
111
151
  if (requested) {
112
- const match = cabled.find((d) => d.udid.toLowerCase() === requested.toLowerCase());
113
- if (match) return unhealthy(match) ?? {
114
- udid: match.udid,
115
- name: match.name
116
- };
117
- const overNetwork = wireless.find((d) => d.udid.toLowerCase() === requested.toLowerCase());
118
- if (overNetwork) return wirelessRefusal(overNetwork);
152
+ const same = (d) => d.udid.toLowerCase() === requested.toLowerCase();
153
+ const match = physical.find(same);
154
+ if (match) return unhealthy(match) ?? selected(match);
155
+ const other = listed.find(same);
156
+ if (other) return notUsableRefusal(other);
119
157
  return {
120
- error: cabled.length ? `${requested} is not connected. devicectl reports these cabled devices: ${cabled.map(describe).join(", ")}.` : `${requested} is not connected by cable, and devicectl reports no cabled device at all.`,
158
+ error: physical.length ? `${requested} is not connected. devicectl reports these devices: ${physical.map(describe).join(", ")}.` : `${requested} is not connected, and devicectl reports no device over a cable or Wi-Fi at all.`,
121
159
  remedy: "Check the cable and `xcrun devicectl list devices`, then retry with a UDID it lists."
122
160
  };
123
161
  }
124
- if (cabled.length === 1) {
125
- const only = cabled[0];
126
- return unhealthy(only) ?? {
127
- udid: only.udid,
128
- name: only.name
129
- };
130
- }
131
- if (cabled.length > 1) return {
132
- error: `Several devices are connected: ${cabled.map(describe).join(", ")}.`,
133
- remedy: "Name the one to build for with `stim ios --device <udid>`."
134
- };
135
- const onlyWireless = wireless[0];
136
- if (onlyWireless) return wirelessRefusal(onlyWireless);
162
+ const chosen = pickOne(physical);
163
+ if (chosen) return chosen;
137
164
  return {
138
165
  error: "No physical iOS device is connected.",
139
166
  remedy: "Plug the phone in, unlock it, tap Trust, turn on Settings > Privacy & Security > Developer Mode, then check `xcrun devicectl list devices`."
140
167
  };
141
168
  }
142
169
  const DEVICECTL_INSTALL_TIMEOUT_MS = 3e5;
170
+ const WIRELESS_INSTALL_TIMEOUT_MS = 9e5;
171
+ const LAUNCH_PROBE_TIMEOUT_MS = 45e3;
172
+ const WIRELESS_LAUNCH_PROBE_TIMEOUT_MS = 12e4;
173
+ const WIRELESS_ERROR = "STIM_DEVICE_WIRELESS_FAILED";
174
+ function iosDeviceBounds(wireless) {
175
+ return wireless ? {
176
+ installMs: WIRELESS_INSTALL_TIMEOUT_MS,
177
+ launchMs: WIRELESS_LAUNCH_PROBE_TIMEOUT_MS
178
+ } : {
179
+ installMs: DEVICECTL_INSTALL_TIMEOUT_MS,
180
+ launchMs: LAUNCH_PROBE_TIMEOUT_MS
181
+ };
182
+ }
183
+ const WIRELESS_REMEDY = "Connect the phone with a cable so devicectl uses it instead of Wi-Fi, keep the phone unlocked, then run the command again.";
184
+ const WIRELESS_DROP = /disconnected|not connected|connection (?:was )?(?:lost|interrupted|reset)|network connection was lost|timed out/i;
185
+ const WIRELESS_CONSOLE_DROP = /disconnected|connection (?:was )?(?:lost|interrupted|reset)|not connected|timed out/i;
143
186
  const LOCKED_REMEDY = "Unlock the phone and keep it awake, then run the command again.";
144
187
  function errorText(error) {
145
188
  const withOutput = error;
@@ -170,8 +213,33 @@ function iosInstallRemedy(kind, { udid, bundleId }) {
170
213
  default: return `Check that ${udid} is still connected and unlocked (\`xcrun devicectl list devices\`), and that the app was built for the iphoneos SDK.`;
171
214
  }
172
215
  }
173
- function installIosDeviceApp({ udid, appPath, bundleId = null }, { exec = null } = {}) {
216
+ function wirelessInstallFailure(error, { udid, appPath, wireless }) {
217
+ if (!wireless) return null;
218
+ const failure = errorText(error);
219
+ if (iosInstallFailureKind(failure) !== null) return null;
220
+ const timedOut = error?.code === "ETIMEDOUT";
221
+ if (!timedOut && !WIRELESS_DROP.test(failure)) return null;
222
+ const what = timedOut ? `did not finish within ${Math.round(WIRELESS_INSTALL_TIMEOUT_MS / 1e3)}s` : "lost the connection to the phone";
223
+ return {
224
+ failed: true,
225
+ code: WIRELESS_ERROR,
226
+ reason: `devicectl ${what} while installing ${appPath} on ${udid} over Wi-Fi (localNetwork): ${failure}`,
227
+ remedy: WIRELESS_REMEDY
228
+ };
229
+ }
230
+ /**
231
+ * Installs with one bounded devicectl call. A signer conflict adds an uninstall and a
232
+ * second install, each with the same bound; `beforeStep` runs before each of those two
233
+ * so the caller can extend its device lease.
234
+ */
235
+ function installIosDeviceApp({ udid, appPath, bundleId = null, wireless = false }, { exec = null, beforeStep = () => {} } = {}) {
174
236
  const e = exec || getExecutor();
237
+ const timeoutMs = iosDeviceBounds(wireless).installMs;
238
+ const target = {
239
+ udid,
240
+ appPath,
241
+ wireless
242
+ };
175
243
  const install = () => {
176
244
  e.runFile("xcrun", [
177
245
  "devicectl",
@@ -181,7 +249,7 @@ function installIosDeviceApp({ udid, appPath, bundleId = null }, { exec = null }
181
249
  "--device",
182
250
  udid,
183
251
  appPath
184
- ], { timeoutMs: DEVICECTL_INSTALL_TIMEOUT_MS });
252
+ ], { timeoutMs });
185
253
  };
186
254
  try {
187
255
  install();
@@ -190,6 +258,8 @@ function installIosDeviceApp({ udid, appPath, bundleId = null }, { exec = null }
190
258
  appPath
191
259
  };
192
260
  } catch (err) {
261
+ const dropped = wirelessInstallFailure(err, target);
262
+ if (dropped) return dropped;
193
263
  const failure = errorText(err);
194
264
  const kind = iosInstallFailureKind(failure);
195
265
  if (kind !== "signer" || !bundleId) return {
@@ -202,6 +272,7 @@ function installIosDeviceApp({ udid, appPath, bundleId = null }, { exec = null }
202
272
  })
203
273
  };
204
274
  try {
275
+ beforeStep();
205
276
  e.runFile("xcrun", [
206
277
  "devicectl",
207
278
  "device",
@@ -210,26 +281,28 @@ function installIosDeviceApp({ udid, appPath, bundleId = null }, { exec = null }
210
281
  "--device",
211
282
  udid,
212
283
  bundleId
213
- ], { timeoutMs: DEVICECTL_INSTALL_TIMEOUT_MS });
284
+ ], { timeoutMs });
214
285
  } catch (uninstallErr) {
215
- return {
286
+ const uninstallKind = iosInstallFailureKind(errorText(uninstallErr));
287
+ return wirelessInstallFailure(uninstallErr, target) ?? {
216
288
  failed: true,
217
- code: INSTALL_ERROR,
289
+ code: "STIM_INSTALL_FAILED",
218
290
  reason: `devicectl refused to install ${appPath} over the copy of ${bundleId} already on ${udid}, which a different team signed, and the uninstall failed too: ${errorText(uninstallErr)}`,
219
- remedy: iosInstallRemedy("signer", {
291
+ remedy: iosInstallRemedy(uninstallKind ?? "signer", {
220
292
  udid,
221
293
  bundleId
222
294
  })
223
295
  };
224
296
  }
225
297
  try {
298
+ beforeStep();
226
299
  install();
227
300
  } catch (retryErr) {
228
- return {
301
+ return wirelessInstallFailure(retryErr, target) ?? {
229
302
  failed: true,
230
- code: INSTALL_ERROR,
303
+ code: "STIM_INSTALL_FAILED",
231
304
  reason: `devicectl could not install ${appPath} on ${udid} even after uninstalling ${bundleId}: ${errorText(retryErr)}`,
232
- remedy: iosInstallRemedy(null, {
305
+ remedy: iosInstallRemedy(iosInstallFailureKind(errorText(retryErr)), {
233
306
  udid,
234
307
  bundleId
235
308
  })
@@ -301,6 +374,8 @@ const LAUNCH_REFUSALS = [
301
374
  ["untrusted-developer", /FBSOpenApplicationErrorDomain error 3\b/],
302
375
  ["untrusted-developer", /denied by service delegate[^\n]*for reason: Security/],
303
376
  ["locked", /device is locked|is locked\b|passcode/i],
377
+ ["locked", /BSErrorCodeDescription = Locked\b/],
378
+ ["locked", /\bunlocked\b[^\n]*FBSOpenApplicationErrorDomain error 7\b/],
304
379
  ["not-installed", /(?:application|app)[^\n]*\b(?:is )?(?:unknown to FrontBoard|not installed)/i]
305
380
  ];
306
381
  function iosLaunchRefusalKind(output) {
@@ -315,7 +390,6 @@ function iosLaunchRemedy(kind, { udid, bundleId }) {
315
390
  default: return `Run \`xcrun devicectl device process launch --console --device ${udid} ${bundleId}\` to see what the phone reports.`;
316
391
  }
317
392
  }
318
- const LAUNCH_PROBE_TIMEOUT_MS = 45e3;
319
393
  const LAUNCH_PROBE_POLL_MS = 1e3;
320
394
  const COLLECTOR_ENDED_EVENTS = /* @__PURE__ */ new Set(["collector_failed", "collector_stopped"]);
321
395
  function launchEvidence(records) {
@@ -326,7 +400,12 @@ function launchEvidence(records) {
326
400
  if (entry.event === "collector_started" || entry.event === "collector_empty") continue;
327
401
  lines.push(msg);
328
402
  }
329
- return lines.slice(-6);
403
+ return lines;
404
+ }
405
+ function devicectlErrorText(records) {
406
+ const own = records.filter((entry) => entry.proc === void 0 && typeof entry.msg === "string");
407
+ const start = own.findIndex((entry) => entry.msg.startsWith(TOOL_ERROR_PREFIX));
408
+ return start < 0 ? "" : own.slice(start).map((entry) => entry.msg).join("\n");
330
409
  }
331
410
  function collectorRecordsFor(records, collectorPid) {
332
411
  if (!collectorPid) return [];
@@ -334,7 +413,7 @@ function collectorRecordsFor(records, collectorPid) {
334
413
  const start = records.findIndex((entry) => entry.event === "collector_started" && typeof entry.msg === "string" && marker.test(entry.msg));
335
414
  return start < 0 ? [] : records.slice(start);
336
415
  }
337
- async function awaitIosDeviceLaunch({ udid, bundleId, appName, collectorPid = null, readRecords, probe, timeoutMs = LAUNCH_PROBE_TIMEOUT_MS, pollMs = LAUNCH_PROBE_POLL_MS, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)) }) {
416
+ async function awaitIosDeviceLaunch({ udid, bundleId, appName, collectorPid = null, readRecords, probe, wireless = false, timeoutMs = iosDeviceBounds(wireless).launchMs, pollMs = LAUNCH_PROBE_POLL_MS, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)) }) {
338
417
  const probeProcess = probe ?? (() => iosDeviceProcess({
339
418
  udid,
340
419
  appName
@@ -346,8 +425,16 @@ async function awaitIosDeviceLaunch({ udid, bundleId, appName, collectorPid = nu
346
425
  const pid = probeProcess();
347
426
  if (typeof pid === "number") return { pid };
348
427
  if (ended) {
349
- const lines = launchEvidence(records);
350
- const kind = iosLaunchRefusalKind(lines.join("\n"));
428
+ const evidence = launchEvidence(records);
429
+ const lines = evidence.slice(-6);
430
+ const kind = iosLaunchRefusalKind(evidence.join("\n"));
431
+ if (wireless && kind === null && WIRELESS_CONSOLE_DROP.test(devicectlErrorText(records))) return {
432
+ failed: true,
433
+ code: WIRELESS_ERROR,
434
+ reason: `devicectl lost the Wi-Fi (localNetwork) connection to ${udid} while launching ${bundleId}.`,
435
+ remedy: WIRELESS_REMEDY,
436
+ lines
437
+ };
351
438
  return {
352
439
  failed: true,
353
440
  reason: `devicectl could not keep ${bundleId} running on ${udid}: the console ended before the app appeared in the device's process list.`,
@@ -359,11 +446,21 @@ async function awaitIosDeviceLaunch({ udid, bundleId, appName, collectorPid = nu
359
446
  };
360
447
  }
361
448
  if (now() >= deadline) {
362
- const lines = launchEvidence(records);
449
+ const evidence = launchEvidence(records);
450
+ const lines = evidence.slice(-6);
451
+ const kind = iosLaunchRefusalKind(evidence.join("\n"));
452
+ const reason = `${bundleId} did not appear in ${udid}'s process list within ${Math.round(timeoutMs / 1e3)}s of the launch`;
453
+ if (wireless && kind === null) return {
454
+ failed: true,
455
+ code: WIRELESS_ERROR,
456
+ reason: `${reason} over Wi-Fi (localNetwork).`,
457
+ remedy: WIRELESS_REMEDY,
458
+ lines
459
+ };
363
460
  return {
364
461
  failed: true,
365
- reason: `${bundleId} did not appear in ${udid}'s process list within ${Math.round(timeoutMs / 1e3)}s of the launch.`,
366
- remedy: iosLaunchRemedy(iosLaunchRefusalKind(lines.join("\n")), {
462
+ reason: `${reason}.`,
463
+ remedy: iosLaunchRemedy(kind, {
367
464
  udid,
368
465
  bundleId
369
466
  }),
@@ -410,4 +507,4 @@ function localNetworkPending(records, { since = 0, pid = null, lanOrigin }) {
410
507
  return false;
411
508
  }
412
509
  //#endregion
413
- export { iosDeviceProcess as a, listIosDevices as c, verifyIosDeviceReleaseLaunch as d, installIosDeviceApp as i, localNetworkPending as l, LAUNCH_PROBE_TIMEOUT_MS as n, iosPoolCandidates as o, awaitIosDeviceLaunch as r, iosPoolNoCandidatesRefusal as s, DEVICECTL_INSTALL_TIMEOUT_MS as t, resolveIosPhysicalDevice as u };
510
+ export { iosPoolCandidates as a, listIosDevices as c, verifyIosDeviceReleaseLaunch as d, iosDeviceProcess as i, localNetworkPending as l, installIosDeviceApp as n, iosPoolNoCandidatesRefusal as o, iosDeviceBounds as r, isWirelessIosDevice as s, awaitIosDeviceLaunch as t, resolveIosPhysicalDevice as u };