stim 1.1.0 → 1.3.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 (64) hide show
  1. package/README.md +37 -0
  2. package/dist/{android-CC5bUer7.mjs → android-BW8i_YxU.mjs} +1 -1
  3. package/dist/android-BqVIJoG7.mjs +1368 -0
  4. package/dist/{android-DnZlT3aU.mjs → android-CqcW1hrP.mjs} +205 -58
  5. package/dist/{android-cas-DGlTStWu.mjs → android-cas-BR5YJBYv.mjs} +6 -6
  6. package/dist/android-cas-compiler.mjs +1 -1
  7. package/dist/{app-install-oZIQRbdD.mjs → app-install-DIp9riNx.mjs} +29 -21
  8. package/dist/{build-slots-CgacA9zQ.mjs → build-slots-C3-R4XQr.mjs} +3 -3
  9. package/dist/{cache-manifest-oeH18ERr.mjs → cache-manifest-CXR-Y8pH.mjs} +1 -1
  10. package/dist/cache-manifest.mjs +1 -1
  11. package/dist/cli.mjs +13 -13
  12. package/dist/collector-run.d.mts +4 -1
  13. package/dist/collector-run.mjs +36 -11
  14. package/dist/{config-D7uu8Gbq.mjs → config-CcefMY2Q.mjs} +94 -13
  15. package/dist/{deps-64wVnpZc.mjs → deps-Spz8ozgo.mjs} +2 -2
  16. package/dist/{dev-client-Bvky1hya.mjs → dev-client-BjisHDB5.mjs} +250 -10
  17. package/dist/{device-DJKv5Qej.mjs → device-BkiW-gkk.mjs} +19 -11
  18. package/dist/{device-lease-D-G3SgVe.mjs → device-lease-BJYybF-2.mjs} +107 -83
  19. package/dist/{device-pool-BlHXnYYs.mjs → device-pool-CPbLGfrB.mjs} +19 -13
  20. package/dist/{device-remote-CCgkG82J.mjs → device-remote-AkYCKS8U.mjs} +8 -8
  21. package/dist/{doctor-DlVxTtMq.mjs → doctor-Bmv86UUW.mjs} +18 -26
  22. package/dist/{doctor-BRF9aAJ3.mjs → doctor-fAMXC4vl.mjs} +10 -10
  23. package/dist/{error-diagnostics-B5IJwv3-.mjs → error-diagnostics-DHC31rXA.mjs} +65 -55
  24. package/dist/{exec-bsN9MJXb.mjs → exec-CyylIdq9.mjs} +4 -2
  25. package/dist/{gc-DhXdgSdt.mjs → gc-GGu-8ULN.mjs} +90 -67
  26. package/dist/{guide-DrhrDBo5.mjs → guide-92CPA21s.mjs} +414 -47
  27. package/dist/{ios-CK1Vrk6Q.mjs → ios-BSXFeO5H.mjs} +200 -59
  28. package/dist/{ios-BZxKPuqP.mjs → ios-Deh5js5V.mjs} +147 -40
  29. package/dist/{ios-device-CdSm0GDw.mjs → ios-device-CWOM_S44.mjs} +2 -2
  30. package/dist/{ios-device-CYbuRNXR.mjs → ios-device-D3pzTKQE.mjs} +1 -1
  31. package/dist/{logs-Dxoy16Qv.mjs → logs-DPNF4C9s.mjs} +9 -6
  32. package/dist/{logs-query-oqn47Ffx.mjs → logs-query-R-Cqc6Qv.mjs} +22 -5
  33. package/dist/{metro-B01H3EuD.mjs → metro-CORqXyTL.mjs} +3 -3
  34. package/dist/{ndjson-DcAtEx_K.mjs → ndjson-BZwI1NBv.mjs} +5 -2
  35. package/dist/{ownership-D8HmSv2E.mjs → ownership-8O073GIC.mjs} +3 -3
  36. package/dist/{ownership-B9QIdQOw.mjs → ownership-BpCKiRlQ.mjs} +4 -4
  37. package/dist/{ownership-claim-CeJLEpjD.mjs → ownership-claim-Bf2CAfw3.mjs} +4 -3
  38. package/dist/{project-COj6nzzh.mjs → project-fKmuIaMI.mjs} +2 -2
  39. package/dist/{reclaim-BPvN2HlP.mjs → reclaim-iW9Cad7k.mjs} +80 -73
  40. package/dist/{reload-DYZEBbZy.mjs → reload-DazHDSFA.mjs} +32 -24
  41. package/dist/{server-bare-BYHqEeyw.mjs → server-bare-DEzPZLvf.mjs} +7 -2
  42. package/dist/server-expo-44em5Pmg.mjs +2 -0
  43. package/dist/{server-expo-BEMr_m6p.mjs → server-expo-DizT4OMk.mjs} +5 -5
  44. package/dist/{settings-BSwrY4FZ.mjs → settings-DEJf6_kJ.mjs} +26 -5
  45. package/dist/slot-launch-cci0xvmC.mjs +19 -0
  46. package/dist/{start-ByCylYtB.mjs → start-mObjQH2W.mjs} +12 -12
  47. package/dist/{state-IYEY-zi8.mjs → state-B3XA2wp2.mjs} +9 -9
  48. package/dist/{stats-wqxPm7Tv.mjs → stats-BR-kwj0B.mjs} +1 -1
  49. package/dist/{stats-DZ4Q5J-7.mjs → stats-CaaQr4yN.mjs} +3 -3
  50. package/dist/{status-Dia0XTqG.mjs → status-BxQNqu9t.mjs} +86 -25
  51. package/dist/{stop-D6o5zAdm.mjs → stop-CrHGcjrV.mjs} +2 -2
  52. package/dist/{stop-BApyaF21.mjs → stop-DimGXEKY.mjs} +93 -42
  53. package/dist/supervisor-run.d.mts +1 -2
  54. package/dist/supervisor-run.mjs +5 -5
  55. package/dist/{remote-cache-DOi5-zLj.mjs → teardown-ClFp1Aax.mjs} +889 -830
  56. package/dist/{workspace-process-lock-CxOO9-pf.mjs → workspace-process-lock-3NcQnZ0X.mjs} +1 -1
  57. package/dist/{worktree-DxEmUI_n.mjs → worktree-DxTgN1CJ.mjs} +16 -16
  58. package/dist/{worktree-Dx3ZC5lS.mjs → worktree-Eq6TLH8N.mjs} +7 -3
  59. package/dist/{xcode-CDB6fytw.mjs → xcode-D7sAp-Zy.mjs} +256 -103
  60. package/package.json +5 -5
  61. package/shim/bundle-response.cjs +5 -0
  62. package/dist/android-xmyGBNy5.mjs +0 -665
  63. package/dist/build-lock-vE45ZzJZ.mjs +0 -645
  64. package/dist/server-expo-rb0k-0w7.mjs +0 -2
@@ -1,11 +1,11 @@
1
- import { D as sameProcessRecord, O as waitForProcessExit, f as getProject, v as removeProject } from "./config-D7uu8Gbq.mjs";
2
- import { T as workspaceDir, a as clearRemoteSession, c as readMetroTunnel, d as readRemoteSessionId, i as clearManagedMetroTunnel, p as readWorkspaceState } from "./state-IYEY-zi8.mjs";
3
- import { i as resolveProjectMetro, n as killMetroTree, r as pidExists } from "./metro-B01H3EuD.mjs";
4
- import { n as verifyCollectorOwnership } from "./ownership-D8HmSv2E.mjs";
5
- import { d as resolveEasCliBin, h as teardownOwnedIosSim, m as teardownOwnedAvd, x as parkedMaxSetting } from "./remote-cache-DOi5-zLj.mjs";
6
- import { l as releaseWorkspaceLeases } from "./device-lease-D-G3SgVe.mjs";
7
- import { i as endRecordedSession } from "./device-remote-CCgkG82J.mjs";
8
- import { r as stopTunnel, t as resolveSupervisorTarget } from "./ownership-B9QIdQOw.mjs";
1
+ import { D as sameProcessRecord, O as waitForProcessExit, f as getProject, v as removeProject, z as projectDeviceSlots } from "./config-CcefMY2Q.mjs";
2
+ import { T as workspaceDir, a as clearRemoteSession, c as readMetroTunnel, d as readRemoteSessionId, i as clearManagedMetroTunnel, p as readWorkspaceState } from "./state-B3XA2wp2.mjs";
3
+ import { i as resolveProjectMetro, n as killMetroTree, r as pidExists } from "./metro-CORqXyTL.mjs";
4
+ import { n as verifyCollectorOwnership } from "./ownership-8O073GIC.mjs";
5
+ import { c as parkedMaxSetting, n as teardownOwnedIosSim, t as teardownOwnedAvd, x as resolveEasCliBin } from "./teardown-ClFp1Aax.mjs";
6
+ import { l as releaseWorkspaceLeases } from "./device-lease-BJYybF-2.mjs";
7
+ import { i as endRecordedSession } from "./device-remote-AkYCKS8U.mjs";
8
+ import { r as stopTunnel, t as resolveSupervisorTarget } from "./ownership-BpCKiRlQ.mjs";
9
9
  import { existsSync, rmSync } from "node:fs";
10
10
  //#region src/reclaim.ts
11
11
  async function reapCollectors(root, { verify = verifyCollectorOwnership, collectors }) {
@@ -25,7 +25,7 @@ async function reapCollectors(root, { verify = verifyCollectorOwnership, collect
25
25
  if (ownership.status === "unverified") {
26
26
  const name = `${platform} log collector (pid ${pid})`;
27
27
  const entry = {
28
- platform: platform === "android" ? "android" : "ios",
28
+ platform: platform.split(":")[0] === "android" ? "android" : "ios",
29
29
  name,
30
30
  reason: `${ownership.reason}; keeping the record without signalling the process`
31
31
  };
@@ -40,7 +40,7 @@ async function reapCollectors(root, { verify = verifyCollectorOwnership, collect
40
40
  if (error.code === "ESRCH") continue;
41
41
  }
42
42
  const entry = {
43
- platform: platform === "android" ? "android" : "ios",
43
+ platform: platform.split(":")[0] === "android" ? "android" : "ios",
44
44
  name: `${platform} log collector (pid ${pid})`,
45
45
  reason: "Collector exit could not be confirmed; keeping its ownership record."
46
46
  };
@@ -139,11 +139,13 @@ async function reclaimMetroTunnel(root, { stopMetroTunnel = defaultStopMetroTunn
139
139
  }
140
140
  function describeDereferenced(project) {
141
141
  const devices = [];
142
- const ios = project?.platforms?.ios;
143
- if (ios?.deviceUdid) devices.push(`ios sim ${ios.deviceUdid}`);
144
- const android = project?.platforms?.android;
145
- if (android?.avdName) devices.push(`android avd ${android.avdName}`);
146
- else if (android?.serial) devices.push(`android device ${android.serial}`);
142
+ for (const { platforms } of projectDeviceSlots(project)) {
143
+ const ios = platforms.ios;
144
+ if (ios?.deviceUdid) devices.push(`ios sim ${ios.deviceUdid}`);
145
+ const android = platforms.android;
146
+ if (android?.avdName) devices.push(`android avd ${android.avdName}`);
147
+ else if (android?.serial) devices.push(`android device ${android.serial}`);
148
+ }
147
149
  return devices;
148
150
  }
149
151
  function parkedIosCacheKey(lastBuild) {
@@ -151,7 +153,7 @@ function parkedIosCacheKey(lastBuild) {
151
153
  const build = lastBuild;
152
154
  return build.platform === "ios" && typeof build.cacheKey === "string" ? build.cacheKey : null;
153
155
  }
154
- function parkRequest(project, projectPath) {
156
+ function parkRequest(project, projectPath, slot, simslimManaged) {
155
157
  const { max, error } = parkedMaxSetting("ios");
156
158
  if (error || max <= 0) return void 0;
157
159
  const cacheKey = parkedIosCacheKey(readWorkspaceState(projectPath)?.lastBuild);
@@ -160,7 +162,8 @@ function parkRequest(project, projectPath) {
160
162
  max,
161
163
  bundleId: typeof project?.bundleId === "string" ? project.bundleId : null,
162
164
  cacheKey,
163
- simslimManaged: Boolean(project?.platforms?.ios?.simslimManaged)
165
+ simslimManaged,
166
+ slot
164
167
  };
165
168
  }
166
169
  function reclaimOwnedDevices(project, projectPath, { park = false } = {}) {
@@ -170,68 +173,72 @@ function reclaimOwnedDevices(project, projectPath, { park = false } = {}) {
170
173
  const poolNotes = [];
171
174
  const skippedDevices = [];
172
175
  const failedDevices = [];
173
- const ios = project?.platforms?.ios;
174
- if (ios?.owned && ios.deviceUdid) {
175
- const udid = ios.deviceUdid;
176
- const label = ios.deviceName || udid;
177
- const r = teardownOwnedIosSim(udid, {
178
- del: true,
179
- label,
180
- ...park ? { park: parkRequest(project, projectPath) } : {}
181
- });
182
- if (r.parkFallback) poolNotes.push(`could not park ${label}: ${r.parkFallback} -- deleted it instead`);
183
- for (const failure of r.evictionFailures ?? []) poolNotes.push(failure);
184
- if (r.parked) {
185
- parkedDevices.push(r.parked);
186
- evictedDevices.push(...r.evicted ?? []);
187
- } else if (r.status === "torn-down") deletedDevices.push(r.label);
188
- if (r.status === "skipped") skippedDevices.push({
189
- platform: "ios",
190
- name: label,
191
- udid,
192
- reason: `${r.reason} -- not touched`
193
- });
194
- else if (r.status === "failed") {
195
- const entry = {
176
+ for (const { slot, platforms } of projectDeviceSlots(project)) {
177
+ const ios = platforms.ios;
178
+ if (ios?.owned && ios.deviceUdid) {
179
+ const udid = ios.deviceUdid;
180
+ const label = ios.deviceName || udid;
181
+ const r = teardownOwnedIosSim(udid, {
182
+ del: true,
183
+ label,
184
+ ...park ? { park: parkRequest(project, projectPath, slot, Boolean(ios.simslimManaged)) } : {}
185
+ });
186
+ if (r.parkFallback) poolNotes.push(`could not park ${label}: ${r.parkFallback} -- deleted it instead`);
187
+ for (const failure of r.evictionFailures ?? []) poolNotes.push(failure);
188
+ if (r.parked) {
189
+ parkedDevices.push(r.parked);
190
+ evictedDevices.push(...r.evicted ?? []);
191
+ } else if (r.status === "torn-down") deletedDevices.push(r.label);
192
+ if (r.status === "skipped") skippedDevices.push({
196
193
  platform: "ios",
197
194
  name: label,
198
195
  udid,
199
- reason: `teardown failed: ${r.reason}`
200
- };
201
- skippedDevices.push(entry);
202
- failedDevices.push(entry);
196
+ reason: `${r.reason} -- not touched`
197
+ });
198
+ else if (r.status === "failed") {
199
+ const entry = {
200
+ platform: "ios",
201
+ name: label,
202
+ udid,
203
+ reason: `teardown failed: ${r.reason}`
204
+ };
205
+ skippedDevices.push(entry);
206
+ failedDevices.push(entry);
207
+ }
203
208
  }
204
- }
205
- const android = project?.platforms?.android;
206
- if (android?.owned && android.avdName) {
207
- const bound = parkedMaxSetting("android");
208
- const r = teardownOwnedAvd(android.avdName, {
209
- del: true,
210
- ...park && !bound.error && bound.max > 0 ? { park: {
211
- projectPath,
212
- max: bound.max,
213
- configuration: typeof android.poolConfiguration === "string" ? android.poolConfiguration : void 0
214
- } } : {}
215
- });
216
- if (r.parkFallback) poolNotes.push(`could not park ${android.avdName}: ${r.parkFallback} -- deleted it instead`);
217
- for (const failure of r.evictionFailures ?? []) poolNotes.push(failure);
218
- if (r.parked) {
219
- parkedDevices.push(r.parked);
220
- evictedDevices.push(...r.evicted ?? []);
221
- } else if (r.status === "torn-down") deletedDevices.push(android.avdName);
222
- else if (r.status === "skipped") skippedDevices.push({
223
- platform: "android",
224
- name: android.avdName,
225
- reason: `${r.reason} -- not touched`
226
- });
227
- else if (r.status === "failed") {
228
- const entry = {
209
+ const android = platforms.android;
210
+ if (android?.owned && android.avdName) {
211
+ const bound = parkedMaxSetting("android");
212
+ const r = teardownOwnedAvd(android.avdName, {
213
+ del: true,
214
+ owner: { projectPath },
215
+ ...park && !bound.error && bound.max > 0 ? { park: {
216
+ projectPath,
217
+ slot,
218
+ max: bound.max,
219
+ configuration: typeof android.poolConfiguration === "string" ? android.poolConfiguration : void 0
220
+ } } : {}
221
+ });
222
+ if (r.parkFallback) poolNotes.push(`could not park ${android.avdName}: ${r.parkFallback} -- deleted it instead`);
223
+ for (const failure of r.evictionFailures ?? []) poolNotes.push(failure);
224
+ if (r.parked) {
225
+ parkedDevices.push(r.parked);
226
+ evictedDevices.push(...r.evicted ?? []);
227
+ } else if (r.status === "torn-down") deletedDevices.push(android.avdName);
228
+ else if (r.status === "skipped") skippedDevices.push({
229
229
  platform: "android",
230
230
  name: android.avdName,
231
- reason: `teardown failed: ${r.reason}`
232
- };
233
- skippedDevices.push(entry);
234
- failedDevices.push(entry);
231
+ reason: `${r.reason} -- not touched`
232
+ });
233
+ else if (r.status === "failed") {
234
+ const entry = {
235
+ platform: "android",
236
+ name: android.avdName,
237
+ reason: `teardown failed: ${r.reason}`
238
+ };
239
+ skippedDevices.push(entry);
240
+ failedDevices.push(entry);
241
+ }
235
242
  }
236
243
  }
237
244
  return {
@@ -1,11 +1,12 @@
1
- import { f as getProject } from "./config-D7uu8Gbq.mjs";
2
- import { f as readWorkspaceLaunches } from "./state-IYEY-zi8.mjs";
3
- import { a as findProjectRoot } from "./project-COj6nzzh.mjs";
4
- import { i as resolveProjectMetro } from "./metro-B01H3EuD.mjs";
1
+ import { L as deviceSlotPlatforms, R as parseDeviceSlotKey, f as getProject } from "./config-CcefMY2Q.mjs";
2
+ import { f as readWorkspaceLaunches } from "./state-B3XA2wp2.mjs";
3
+ import { a as findProjectRoot } from "./project-fKmuIaMI.mjs";
4
+ import { i as resolveProjectMetro } from "./metro-CORqXyTL.mjs";
5
5
  import { l as phaseLine } from "./command-output-8wHMEde0.mjs";
6
- import { u as androidAppProcess, y as iosAppProcess } from "./app-install-oZIQRbdD.mjs";
7
- import { b as resolveOwnedIosSim } from "./ios-BZxKPuqP.mjs";
8
- import { A as resolveOwnedAvdSerial } from "./android-xmyGBNy5.mjs";
6
+ import { N as resolveOwnedAvdSerial } from "./android-BqVIJoG7.mjs";
7
+ import { u as androidAppProcess, y as iosAppProcess } from "./app-install-DIp9riNx.mjs";
8
+ import { x as resolveOwnedIosSim } from "./ios-Deh5js5V.mjs";
9
+ import { n as nativeRunCommand } from "./slot-launch-cci0xvmC.mjs";
9
10
  import chalk from "chalk";
10
11
  import { WebSocket } from "ws";
11
12
  //#region src/engine/reload.ts
@@ -134,21 +135,22 @@ function failure(code, message, remedy) {
134
135
  function describe(error) {
135
136
  return error instanceof Error ? error.message : String(error);
136
137
  }
137
- function processFailure(platform, record, d) {
138
+ function processFailure(platform, record, d, slot = "default") {
138
139
  const process = platform === "ios" ? d.iosProcess(record.deviceId, record.appId) : d.androidProcess(record.deviceId, record.appId);
139
140
  if (process === void 0) {
140
141
  const probe = platform === "ios" ? `xcrun simctl spawn ${record.deviceId} launchctl list` : `adb -s ${record.deviceId} shell pidof ${record.appId}`;
141
142
  return failure("STIM_RELOAD_PROBE_FAILED", `Stim could not determine whether ${record.appId} is running on ${record.deviceId}.`, `Run \`${probe}\` and retry when the device tool responds.`);
142
143
  }
143
- if (process === null) return failure("STIM_RELOAD_STOPPED", `${record.appId} is not running on ${record.deviceId}.`, `Run \`stim ${platform}\` to launch it.`);
144
+ if (process === null) return failure("STIM_RELOAD_STOPPED", `${record.appId} is not running on ${record.deviceId}.`, `Run \`${nativeRunCommand(platform, slot)}\` to launch it.`);
144
145
  return null;
145
146
  }
146
- function inspectTarget(platform, record, project, d) {
147
+ function inspectTarget(platform, record, project, d, slot = "default") {
148
+ const runCommand = nativeRunCommand(platform, slot);
147
149
  if (platform === "ios") {
148
150
  const configured = project.platforms?.ios;
149
151
  if (!configured?.owned || configured.deviceUdid !== record.deviceId) return {
150
152
  platform,
151
- error: failure("STIM_RELOAD_UNOWNED", `The recorded iOS launch on ${record.deviceId} is not this workspace's current owned simulator.`, "Run `stim ios` to launch the app on this workspace's owned simulator.")
153
+ error: failure("STIM_RELOAD_UNOWNED", `The recorded iOS launch on ${record.deviceId} is not this workspace's current owned simulator.`, `Run \`${runCommand}\` to launch the app on this workspace's owned simulator.`)
152
154
  };
153
155
  let resolved;
154
156
  try {
@@ -161,15 +163,16 @@ function inspectTarget(platform, record, project, d) {
161
163
  }
162
164
  if (!resolved.sim || resolved.sim.state !== "Booted") return {
163
165
  platform,
164
- error: failure("STIM_RELOAD_STOPPED", `The recorded iOS app is not running on a booted owned simulator.`, "Run `stim ios` to boot, install, and launch it.")
166
+ error: failure("STIM_RELOAD_STOPPED", `The recorded iOS app is not running on a booted owned simulator.`, `Run \`${runCommand}\` to boot, install, and launch it.`)
165
167
  };
166
- const stopped = processFailure(platform, record, d);
168
+ const stopped = processFailure(platform, record, d, slot);
167
169
  if (stopped) return {
168
170
  platform,
169
171
  error: stopped
170
172
  };
171
173
  return {
172
174
  platform,
175
+ slot,
173
176
  record,
174
177
  deviceName: resolved.sim.name
175
178
  };
@@ -177,7 +180,7 @@ function inspectTarget(platform, record, project, d) {
177
180
  const configured = project.platforms?.android;
178
181
  if (!configured?.owned || !configured.avdName) return {
179
182
  platform,
180
- error: failure("STIM_RELOAD_UNOWNED", `The recorded Android launch on ${record.deviceId} is not this workspace's current owned emulator.`, "Run `stim android` to launch the app on this workspace's owned emulator.")
183
+ error: failure("STIM_RELOAD_UNOWNED", `The recorded Android launch on ${record.deviceId} is not this workspace's current owned emulator.`, `Run \`${runCommand}\` to launch the app on this workspace's owned emulator.`)
181
184
  };
182
185
  let resolved;
183
186
  try {
@@ -190,15 +193,16 @@ function inspectTarget(platform, record, project, d) {
190
193
  }
191
194
  if (!resolved.serial || resolved.serial !== record.deviceId) return {
192
195
  platform,
193
- error: failure("STIM_RELOAD_STOPPED", `The recorded Android app is not running on this workspace's owned emulator.`, "Run `stim android` to boot, install, and launch it.")
196
+ error: failure("STIM_RELOAD_STOPPED", `The recorded Android app is not running on this workspace's owned emulator.`, `Run \`${runCommand}\` to boot, install, and launch it.`)
194
197
  };
195
- const stopped = processFailure(platform, record, d);
198
+ const stopped = processFailure(platform, record, d, slot);
196
199
  if (stopped) return {
197
200
  platform,
198
201
  error: stopped
199
202
  };
200
203
  return {
201
204
  platform,
205
+ slot,
202
206
  record,
203
207
  deviceName: configured.avdName
204
208
  };
@@ -217,12 +221,16 @@ async function runReload({ root, platform = null, deps = {} }) {
217
221
  error: failure("STIM_NO_PROJECT", `No Stim environment is registered for ${root}.`, "Run `stim start` first.")
218
222
  };
219
223
  const launches = d.readLaunches(root);
220
- const inspected = (platform ? [platform] : ["ios", "android"]).flatMap((candidate) => {
221
- const record = launches[candidate];
222
- return record ? [inspectTarget(candidate, record, project, d)] : [];
224
+ const inspected = Object.entries(launches).flatMap(([key, record]) => {
225
+ const parsed = parseDeviceSlotKey(key);
226
+ if (!parsed || platform && parsed.platform !== platform) return [];
227
+ return [inspectTarget(parsed.platform, record, {
228
+ ...project,
229
+ platforms: deviceSlotPlatforms(project, parsed.slot)
230
+ }, d, parsed.slot)];
223
231
  });
224
232
  const live = inspected.filter((target) => !isTargetFailure(target));
225
- if (live.length > 1) return {
233
+ if (new Set(live.map((target) => target.platform)).size > 1) return {
226
234
  ok: false,
227
235
  error: failure("STIM_RELOAD_AMBIGUOUS", "Both the iOS and Android apps are running.", "Choose one with `stim reload ios` or `stim reload android`.")
228
236
  };
@@ -230,10 +238,10 @@ async function runReload({ root, platform = null, deps = {} }) {
230
238
  ok: false,
231
239
  error: inspected.find(isTargetFailure)?.error ?? failure("STIM_RELOAD_STOPPED", platform ? `No ${platform} app launch is recorded for this workspace.` : "No live app launch is recorded.", platform ? `Run \`stim ${platform}\` first.` : "Run `stim ios` or `stim android` first.")
232
240
  };
233
- const target = live[0];
241
+ const target = live.find((candidate) => !candidate.record.release && candidate.record.metroPort === project.metroPort) ?? live[0];
234
242
  if (target.record.release) return {
235
243
  ok: false,
236
- error: failure("STIM_RELOAD_RELEASE", `${target.record.appId} was launched with an embedded release bundle.`, `Run \`stim ${target.platform}\` with a Debug configuration or variant before reloading JavaScript.`)
244
+ error: failure("STIM_RELOAD_RELEASE", `${target.record.appId} was launched with an embedded release bundle.`, `Run \`${nativeRunCommand(target.platform, target.slot)}\` with a Debug configuration or variant before reloading JavaScript.`)
237
245
  };
238
246
  const port = target.record.metroPort;
239
247
  if (!port || project.metroPort !== port) return {
@@ -244,7 +252,7 @@ async function runReload({ root, platform = null, deps = {} }) {
244
252
  ok: false,
245
253
  error: failure("STIM_NO_METRO", `Port ${port} is not serving this workspace's Metro.`, "Run `stim start`, then retry.")
246
254
  };
247
- const stopped = processFailure(target.platform, target.record, d);
255
+ const stopped = processFailure(target.platform, target.record, d, target.slot);
248
256
  if (stopped) return {
249
257
  ok: false,
250
258
  error: stopped
@@ -254,7 +262,7 @@ async function runReload({ root, platform = null, deps = {} }) {
254
262
  appId: target.record.appId
255
263
  });
256
264
  if (!reloaded.ok) {
257
- const stoppedAfterMetro = processFailure(target.platform, target.record, d);
265
+ const stoppedAfterMetro = processFailure(target.platform, target.record, d, target.slot);
258
266
  if (stoppedAfterMetro) return {
259
267
  ok: false,
260
268
  error: stoppedAfterMetro
@@ -1,6 +1,6 @@
1
- import { p as readWorkspaceState } from "./state-IYEY-zi8.mjs";
1
+ import { p as readWorkspaceState } from "./state-B3XA2wp2.mjs";
2
2
  import { n as supervisorError } from "./errors-B6W7FkxQ.mjs";
3
- import { S as projectMetroSharedCache } from "./settings-BSwrY4FZ.mjs";
3
+ import { C as projectMetroSharedCache } from "./settings-DEJf6_kJ.mjs";
4
4
  import { a as metroStoreRoot, o as registerMetroStore, t as appendCacheStore } from "./metro-store-CDDZHtPl.mjs";
5
5
  import { createRequire } from "node:module";
6
6
  import { dirname, isAbsolute, join, relative, sep } from "node:path";
@@ -13,6 +13,11 @@ var require_bundle_response = /* @__PURE__ */ __commonJSMin(((exports, module) =
13
13
  const { randomUUID } = __require("node:crypto");
14
14
  function bundleResponseMiddleware(write) {
15
15
  return (req, res, next) => {
16
+ if (req.method === "GET" && req.url === "/_stim/metro-warmup") {
17
+ res.end("ready");
18
+ return;
19
+ }
20
+ if (req.headers["x-stim-metro-warmup"] === "1") return next();
16
21
  let url;
17
22
  try {
18
23
  url = new URL(req.url, "http://localhost");
@@ -0,0 +1,2 @@
1
+ import { p as startExpoServer } from "./server-expo-DizT4OMk.mjs";
2
+ export { startExpoServer };
@@ -1,10 +1,10 @@
1
- import { t as getExecutor } from "./exec-bsN9MJXb.mjs";
2
- import { r as createNdjsonWriter } from "./ndjson-DcAtEx_K.mjs";
3
- import { p as readWorkspaceState } from "./state-IYEY-zi8.mjs";
4
- import { l as resolvePackageJson } from "./project-COj6nzzh.mjs";
1
+ import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
+ import { r as createNdjsonWriter } from "./ndjson-BZwI1NBv.mjs";
3
+ import { p as readWorkspaceState } from "./state-B3XA2wp2.mjs";
4
+ import { l as resolvePackageJson } from "./project-fKmuIaMI.mjs";
5
5
  import { n as supervisorError } from "./errors-B6W7FkxQ.mjs";
6
6
  import { n as stripAnsi, t as createLineReader } from "./process-output-C7IlIzd7.mjs";
7
- import { S as projectMetroSharedCache } from "./settings-BSwrY4FZ.mjs";
7
+ import { C as projectMetroSharedCache } from "./settings-DEJf6_kJ.mjs";
8
8
  import { a as metroStoreRoot, i as metroStoreConfirmedRoot, n as expoMetroConfigPath, o as registerMetroStore, r as expoMetroStoreEnv } from "./metro-store-CDDZHtPl.mjs";
9
9
  import { accessSync, constants, existsSync, readFileSync } from "node:fs";
10
10
  import { dirname, join, parse } from "node:path";
@@ -1,5 +1,5 @@
1
- import { d as getConfigPath, g as loadConfig, m as getRepoSettings, p as getProjectSettings } from "./config-D7uu8Gbq.mjs";
2
- import { g as repoRoot, s as gitCommonDir } from "./worktree-Dx3ZC5lS.mjs";
1
+ import { d as getConfigPath, g as loadConfig, m as getRepoSettings, p as getProjectSettings } from "./config-CcefMY2Q.mjs";
2
+ import { g as repoRoot, s as gitCommonDir } from "./worktree-Eq6TLH8N.mjs";
3
3
  import { createHash } from "node:crypto";
4
4
  import { isAbsolute } from "node:path";
5
5
  import { existsSync, readFileSync, realpathSync, statSync } from "fs";
@@ -11,6 +11,7 @@ const OPTIMIZATION_SHAPES = {
11
11
  "optimizations.remoteBuildCache": "boolean",
12
12
  "optimizations.releaseBundleSwap": "boolean",
13
13
  "optimizations.metroSharedCache": "boolean",
14
+ "optimizations.metroWarmup": "boolean",
14
15
  "optimizations.ios": "object",
15
16
  "optimizations.ios.compilationCache": "boolean",
16
17
  "optimizations.ios.swiftCompilationCache": "boolean",
@@ -78,6 +79,7 @@ function resolveOptimizations(settings = {}, env = process.env) {
78
79
  remoteBuildCache: optimizationBoolean(settings, "remoteBuildCache"),
79
80
  releaseBundleSwap: optimizationBoolean(settings, "releaseBundleSwap"),
80
81
  metroSharedCache: optimizationBoolean(settings, "metroSharedCache"),
82
+ metroWarmup: optimizationBoolean(settings, "metroWarmup"),
81
83
  ios: {
82
84
  compilationCache: optimizationBoolean(settings, "ios.compilationCache"),
83
85
  swiftCompilationCache: optimizationBoolean(settings, "ios.swiftCompilationCache", false),
@@ -209,6 +211,18 @@ const SETTING_SHAPE_RULES = {
209
211
  object: {
210
212
  expected: "an object",
211
213
  accepts: isPlainObject
214
+ },
215
+ "bundle-url": {
216
+ expected: "an HTTP(S) URL or /path ending in .bundle with a matching platform query and no fragment",
217
+ accepts: (value, key) => {
218
+ if (typeof value !== "string" || !/^(https?:\/\/|\/(?!\/))/.test(value) || /[\s\\#]/.test(value)) return false;
219
+ try {
220
+ const url = new URL(value, "http://localhost");
221
+ return /\.bundle\/*$/.test(url.pathname) && url.searchParams.get("platform") === key.split(".").at(-1);
222
+ } catch {
223
+ return false;
224
+ }
225
+ }
212
226
  }
213
227
  };
214
228
  const SETTING_SHAPES = {
@@ -232,6 +246,9 @@ const SETTING_SHAPES = {
232
246
  "metro.tunnel": "string",
233
247
  "metro.ngrokUrl": "string",
234
248
  "metro.publicUrl": "string",
249
+ "metro.warmupUrl": "object",
250
+ "metro.warmupUrl.ios": "bundle-url",
251
+ "metro.warmupUrl.android": "bundle-url",
235
252
  "worktree.exclude": "strings",
236
253
  "worktree.defaultBranch": "string",
237
254
  "cache.provider": "string",
@@ -256,7 +273,7 @@ function settingShapeErrors(settings) {
256
273
  const value = settingValueAt(settings, path);
257
274
  if (value === void 0) continue;
258
275
  const rule = SETTING_SHAPE_RULES[shape];
259
- if (rule.accepts(value)) continue;
276
+ if (rule.accepts(value, path)) continue;
260
277
  errors.push(`Invalid ${path} setting ${JSON.stringify(value)}. Expected ${rule.expected}.`);
261
278
  }
262
279
  return errors;
@@ -506,7 +523,7 @@ function unknownSettingKeys(settings, prefix = "") {
506
523
  const unknown = [];
507
524
  for (const [key, value] of Object.entries(settings)) {
508
525
  const path = prefix ? `${prefix}.${key}` : key;
509
- if (KNOWN_SETTINGS.has(path) && ![...KNOWN_SETTINGS].some((k) => k.startsWith(`${path}.`))) continue;
526
+ if (KNOWN_SETTINGS.has(path) && (!isPlainObject(value) || ![...KNOWN_SETTINGS].some((k) => k.startsWith(`${path}.`)))) continue;
510
527
  if (isPlainObject(value) && [...KNOWN_SETTINGS].some((k) => k.startsWith(`${path}.`))) {
511
528
  unknown.push(...unknownSettingKeys(value, path));
512
529
  continue;
@@ -669,6 +686,10 @@ function publicUrlSetting(settings) {
669
686
  const url = block.publicUrl;
670
687
  return typeof url === "string" && url.trim() ? url : null;
671
688
  }
689
+ function metroWarmupUrlSetting(settings, platform) {
690
+ const value = settingValueAt(settings, `metro.warmupUrl.${platform}`);
691
+ return typeof value === "string" ? value : null;
692
+ }
672
693
  function ngrokUrlSetting(settings) {
673
694
  const block = settings.metro;
674
695
  if (typeof block !== "object" || block === null) return null;
@@ -684,4 +705,4 @@ function normalizedHttpsUrl(raw) {
684
705
  }
685
706
  }
686
707
  //#endregion
687
- export { settingFile as A, compilerCacheFallbackMessage as B, projectOptimizations as C, remoteIosSetting as D, remoteDeviceSettingError as E, unknownSettingKeys as F, resolveOptimizations as H, PUBLIC_METRO_ENV as I, detectProviders as L, settingShapeErrors as M, settingsLayers as N, resolveCacheProviderConfig as O, tunnelModeSetting as P, planMetroReach as R, projectMetroSharedCache as S, remoteAndroidSetting as T, optimizationBuildProfile as V, iosSigningIdentitySha1SettingError as _, SETTING_SHAPE_REMEDY as a, metroTunnelSettingError as b, androidDataPartitionSizeBytes as c, cacheProviderSettingError as d, iosLanHostSetting as f, iosSigningIdentitySha1Setting as g, iosSigningIdentitySettingError as h, SETTINGS_WITH_RESOLVE_TIME_FALLBACK as i, settingOrigin as j, resolveSettings as k, androidDataPartitionSizeGbSetting as l, iosSigningIdentitySetting as m, PATH_SETTINGS as n, androidAvdConfigSetting as o, iosLanHostSettingError as p, REMOTE_DEVICE_BACKENDS as r, androidAvdConfigSettingError as s, ANDROID_AVD_CONFIG_HELP as t, androidDataPartitionSizeGbSettingError as u, iosSimSlimProfileSetting as v, publicUrlSetting as w, ngrokUrlSetting as x, mergeSettingsLayers as y, artifactCachePolicy as z };
708
+ export { resolveSettings as A, artifactCachePolicy as B, projectMetroSharedCache as C, remoteDeviceSettingError as D, remoteAndroidSetting as E, tunnelModeSetting as F, optimizationBuildProfile as H, unknownSettingKeys as I, PUBLIC_METRO_ENV as L, settingOrigin as M, settingShapeErrors as N, remoteIosSetting as O, settingsLayers as P, detectProviders as R, ngrokUrlSetting as S, publicUrlSetting as T, resolveOptimizations as U, compilerCacheFallbackMessage as V, iosSigningIdentitySha1SettingError as _, SETTING_SHAPE_REMEDY as a, metroTunnelSettingError as b, androidDataPartitionSizeBytes as c, cacheProviderSettingError as d, iosLanHostSetting as f, iosSigningIdentitySha1Setting as g, iosSigningIdentitySettingError as h, SETTINGS_WITH_RESOLVE_TIME_FALLBACK as i, settingFile as j, resolveCacheProviderConfig as k, androidDataPartitionSizeGbSetting as l, iosSigningIdentitySetting as m, PATH_SETTINGS as n, androidAvdConfigSetting as o, iosLanHostSettingError as p, REMOTE_DEVICE_BACKENDS as r, androidAvdConfigSettingError as s, ANDROID_AVD_CONFIG_HELP as t, androidDataPartitionSizeGbSettingError as u, iosSimSlimProfileSetting as v, projectOptimizations as w, metroWarmupUrlSetting as x, mergeSettingsLayers as y, planMetroReach as z };
@@ -0,0 +1,19 @@
1
+ import { f as getProject } from "./config-CcefMY2Q.mjs";
2
+ import { p as readWorkspaceState } from "./state-B3XA2wp2.mjs";
3
+ import { h as deviceShellArg } from "./app-install-DIp9riNx.mjs";
4
+ import { n as fileLeaseIo } from "./device-lease-BJYybF-2.mjs";
5
+ //#region src/engine/slot-launch.ts
6
+ function launchSlotScope(root, slot = "default") {
7
+ if (slot !== "default") return slot;
8
+ if (Object.keys(getProject(root)?.deviceSlots ?? {}).length) return slot;
9
+ const state = readWorkspaceState(root);
10
+ if (Object.keys({
11
+ ...state?.collectors,
12
+ ...fileLeaseIo.readHolder(root)
13
+ }).some((key) => key.includes(":"))) return slot;
14
+ }
15
+ function nativeRunCommand(platform, slot = "default", { physical, deviceId } = {}) {
16
+ return `stim ${platform}${slot === "default" ? "" : ` --slot ${slot}`}${physical && deviceId ? ` --device ${deviceShellArg(deviceId)}` : ""}`;
17
+ }
18
+ //#endregion
19
+ export { nativeRunCommand as n, launchSlotScope as t };
@@ -1,23 +1,23 @@
1
- import { t as getExecutor } from "./exec-bsN9MJXb.mjs";
2
- import { E as inspectProcessIdentity, O as waitForProcessExit, S as upsertProject, T as captureProcessToken, a as clearSupervisor, f as getProject } from "./config-D7uu8Gbq.mjs";
3
- import { C as supervisorLogFile, D as workspaceLogsDir, _ as writeWorkspaceState, c as readMetroTunnel, i as clearManagedMetroTunnel, p as readWorkspaceState, s as clearWorkspaceSupervisor, y as ensureWorkspaceStorage } from "./state-IYEY-zi8.mjs";
4
- import { g as repoRoot, s as gitCommonDir } from "./worktree-Dx3ZC5lS.mjs";
5
- import { a as findProjectRoot, i as detectIsExpo, n as detectAndroidPackage, r as detectBundleId, t as appProjectProblem } from "./project-COj6nzzh.mjs";
1
+ import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
+ import { E as inspectProcessIdentity, O as waitForProcessExit, S as upsertProject, T as captureProcessToken, a as clearSupervisor, f as getProject } from "./config-CcefMY2Q.mjs";
3
+ import { C as supervisorLogFile, D as workspaceLogsDir, _ as writeWorkspaceState, c as readMetroTunnel, i as clearManagedMetroTunnel, p as readWorkspaceState, s as clearWorkspaceSupervisor, y as ensureWorkspaceStorage } from "./state-B3XA2wp2.mjs";
4
+ import { g as repoRoot, s as gitCommonDir } from "./worktree-Eq6TLH8N.mjs";
5
+ import { a as findProjectRoot, i as detectIsExpo, n as detectAndroidPackage, r as detectBundleId, t as appProjectProblem } from "./project-fKmuIaMI.mjs";
6
6
  import { n as supervisorError } from "./errors-B6W7FkxQ.mjs";
7
- import { i as resolveProjectMetro, n as killMetroTree, o as reserveMetroPort, r as pidExists } from "./metro-B01H3EuD.mjs";
8
- import { D as remoteIosSetting, E as remoteDeviceSettingError, F as unknownSettingKeys, I as PUBLIC_METRO_ENV, L as detectProviders, M as settingShapeErrors, O as resolveCacheProviderConfig, P as tunnelModeSetting, R as planMetroReach, T as remoteAndroidSetting, a as SETTING_SHAPE_REMEDY, b as metroTunnelSettingError, d as cacheProviderSettingError, k as resolveSettings, w as publicUrlSetting, x as ngrokUrlSetting } from "./settings-BSwrY4FZ.mjs";
7
+ import { i as resolveProjectMetro, n as killMetroTree, o as reserveMetroPort, r as pidExists } from "./metro-CORqXyTL.mjs";
9
8
  import { g as stepTimer, l as phaseLine } from "./command-output-8wHMEde0.mjs";
10
- import { n as workspaceProcessLockError, t as withWorkspaceProcessLock } from "./workspace-process-lock-CxOO9-pf.mjs";
9
+ import { A as resolveSettings, D as remoteDeviceSettingError, E as remoteAndroidSetting, F as tunnelModeSetting, I as unknownSettingKeys, L as PUBLIC_METRO_ENV, N as settingShapeErrors, O as remoteIosSetting, R as detectProviders, S as ngrokUrlSetting, T as publicUrlSetting, a as SETTING_SHAPE_REMEDY, b as metroTunnelSettingError, d as cacheProviderSettingError, k as resolveCacheProviderConfig, z as planMetroReach } from "./settings-DEJf6_kJ.mjs";
11
10
  import { n as expoMetroConfigPath } from "./metro-store-CDDZHtPl.mjs";
12
- import { o as expoSdkMajor } from "./server-expo-BEMr_m6p.mjs";
11
+ import { o as expoSdkMajor } from "./server-expo-DizT4OMk.mjs";
12
+ import { n as workspaceProcessLockError, t as withWorkspaceProcessLock } from "./workspace-process-lock-3NcQnZ0X.mjs";
13
13
  import { t as spawnEntry } from "./spawn-entry-nCE6m770.mjs";
14
- import { s as queryLogs } from "./logs-query-oqn47Ffx.mjs";
15
- import { a as withManagedRemoteWorktreeLock, i as terminateChild, n as startTunnelSequence, r as stopTunnel, s as withManagedTunnelLock, t as resolveSupervisorTarget } from "./ownership-B9QIdQOw.mjs";
14
+ import { s as queryLogs } from "./logs-query-R-Cqc6Qv.mjs";
15
+ import { a as withManagedRemoteWorktreeLock, i as terminateChild, n as startTunnelSequence, r as stopTunnel, s as withManagedTunnelLock, t as resolveSupervisorTarget } from "./ownership-BpCKiRlQ.mjs";
16
16
  import { mkdirSync, openSync, readFileSync } from "node:fs";
17
17
  import { randomUUID } from "node:crypto";
18
18
  import { dirname } from "node:path";
19
- import chalk from "chalk";
20
19
  import { CACHE_PROVIDER_ENV, cacheProviderEnv } from "@stim-cli/cache";
20
+ import chalk from "chalk";
21
21
  //#region src/supervisor/cache-reset.ts
22
22
  async function resetMetroCache(root) {
23
23
  if (detectIsExpo(root) && ((expoSdkMajor(root) ?? 0) < 54 || !expoMetroConfigPath())) throw supervisorError("STIM_BAD_ARG", "An isolated Metro cache reset requires Expo SDK 54 or newer and the Stim Metro config adapter.", "Upgrade Expo or repair the Stim installation before retrying. The running server was not changed.");
@@ -1,4 +1,4 @@
1
- import { D as sameProcessRecord, k as withDirLock, u as getConfigDir } from "./config-D7uu8Gbq.mjs";
1
+ import { D as sameProcessRecord, I as deviceSlotKey, R as parseDeviceSlotKey, k as withDirLock, u as getConfigDir } from "./config-CcefMY2Q.mjs";
2
2
  import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { dirname } from "node:path";
4
4
  import { join as join$1, resolve as resolve$1 } from "path";
@@ -122,14 +122,14 @@ function parseWorkspaceLaunchRecord(value) {
122
122
  function readWorkspaceLaunches(root) {
123
123
  const launches = readWorkspaceState(root)?.launches;
124
124
  if (!launches || typeof launches !== "object" || Array.isArray(launches)) return {};
125
- const ios = parseWorkspaceLaunchRecord(launches.ios);
126
- const android = parseWorkspaceLaunchRecord(launches.android);
127
- return {
128
- ...ios ? { ios } : {},
129
- ...android ? { android } : {}
130
- };
125
+ const records = {};
126
+ for (const [key, value] of Object.entries(launches)) {
127
+ const record = parseWorkspaceLaunchRecord(value);
128
+ if (parseDeviceSlotKey(key) && record) records[key] = record;
129
+ }
130
+ return records;
131
131
  }
132
- function writeWorkspaceLaunch(root, platform, record) {
132
+ function writeWorkspaceLaunch(root, platform, record, slot = "default") {
133
133
  withWorkspaceStateLock(root, () => {
134
134
  const state = readWorkspaceState(root) ?? {};
135
135
  const launches = state.launches && typeof state.launches === "object" ? state.launches : {};
@@ -137,7 +137,7 @@ function writeWorkspaceLaunch(root, platform, record) {
137
137
  ...state,
138
138
  launches: {
139
139
  ...launches,
140
- [platform]: record
140
+ [deviceSlotKey(platform, slot)]: record
141
141
  }
142
142
  });
143
143
  });
@@ -1,4 +1,4 @@
1
- import { C as withConfigLock, u as getConfigDir } from "./config-D7uu8Gbq.mjs";
1
+ import { C as withConfigLock, u as getConfigDir } from "./config-CcefMY2Q.mjs";
2
2
  import { readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { basename, dirname, join, relative, resolve } from "node:path";
4
4
  const PLATFORMS = ["ios", "android"];
@@ -1,7 +1,7 @@
1
- import { g as repoRoot, s as gitCommonDir } from "./worktree-Dx3ZC5lS.mjs";
2
- import { a as findProjectRoot } from "./project-COj6nzzh.mjs";
1
+ import { g as repoRoot, s as gitCommonDir } from "./worktree-Eq6TLH8N.mjs";
2
+ import { a as findProjectRoot } from "./project-fKmuIaMI.mjs";
3
3
  import { o as formatLongDuration } from "./command-output-8wHMEde0.mjs";
4
- import { a as statsProjectKey, r as readStats } from "./stats-wqxPm7Tv.mjs";
4
+ import { a as statsProjectKey, r as readStats } from "./stats-BR-kwj0B.mjs";
5
5
  import chalk from "chalk";
6
6
  //#region src/commands/stats.ts
7
7
  const PLATFORMS = ["ios", "android"];