stim 1.2.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 (59) hide show
  1. package/README.md +30 -0
  2. package/dist/android-BqVIJoG7.mjs +1368 -0
  3. package/dist/{android-Bcmev2s2.mjs → android-CqcW1hrP.mjs} +148 -56
  4. package/dist/{android-cas-D6HPZ6UQ.mjs → android-cas-BR5YJBYv.mjs} +5 -5
  5. package/dist/{app-install-NFxUahnH.mjs → app-install-DIp9riNx.mjs} +6 -6
  6. package/dist/{build-slots-D0b6sJyo.mjs → build-slots-C3-R4XQr.mjs} +2 -2
  7. package/dist/{cache-manifest-Cu4_Znw9.mjs → cache-manifest-CXR-Y8pH.mjs} +1 -1
  8. package/dist/cache-manifest.mjs +1 -1
  9. package/dist/cli.mjs +13 -13
  10. package/dist/collector-run.d.mts +3 -1
  11. package/dist/collector-run.mjs +33 -8
  12. package/dist/{config-CMtIaTk9.mjs → config-CcefMY2Q.mjs} +93 -12
  13. package/dist/{deps-BLBsTNDS.mjs → deps-Spz8ozgo.mjs} +1 -1
  14. package/dist/{dev-client-RpvXxCXV.mjs → dev-client-BjisHDB5.mjs} +186 -9
  15. package/dist/{device-CoCPmazS.mjs → device-BkiW-gkk.mjs} +19 -11
  16. package/dist/{device-lease-Dpik41RY.mjs → device-lease-BJYybF-2.mjs} +107 -83
  17. package/dist/{device-pool-B5pEVa3t.mjs → device-pool-CPbLGfrB.mjs} +18 -12
  18. package/dist/{device-remote-E81KAdXf.mjs → device-remote-AkYCKS8U.mjs} +7 -7
  19. package/dist/{doctor-CqPeMsSm.mjs → doctor-Bmv86UUW.mjs} +11 -12
  20. package/dist/{doctor-D_CX7ib5.mjs → doctor-fAMXC4vl.mjs} +4 -4
  21. package/dist/{error-diagnostics-Dw8Gn3yw.mjs → error-diagnostics-DHC31rXA.mjs} +62 -52
  22. package/dist/{gc-B7KkDinF.mjs → gc-GGu-8ULN.mjs} +66 -57
  23. package/dist/{guide-BS2Xc24o.mjs → guide-92CPA21s.mjs} +196 -13
  24. package/dist/{ios-B7KLgIp5.mjs → ios-BSXFeO5H.mjs} +183 -54
  25. package/dist/{ios-D1h1Di39.mjs → ios-Deh5js5V.mjs} +78 -40
  26. package/dist/{ios-device-xNt0Lg6v.mjs → ios-device-CWOM_S44.mjs} +1 -1
  27. package/dist/{logs-DcX9AvRF.mjs → logs-DPNF4C9s.mjs} +9 -6
  28. package/dist/{logs-query-oqn47Ffx.mjs → logs-query-R-Cqc6Qv.mjs} +22 -5
  29. package/dist/{metro-COslRK_F.mjs → metro-CORqXyTL.mjs} +2 -2
  30. package/dist/{ndjson-DcAtEx_K.mjs → ndjson-BZwI1NBv.mjs} +5 -2
  31. package/dist/{ownership-CGPart6T.mjs → ownership-8O073GIC.mjs} +3 -3
  32. package/dist/{ownership-CYOyqxUo.mjs → ownership-BpCKiRlQ.mjs} +3 -3
  33. package/dist/{ownership-claim-7zRxxTfZ.mjs → ownership-claim-Bf2CAfw3.mjs} +4 -3
  34. package/dist/{project-BBTVABUz.mjs → project-fKmuIaMI.mjs} +2 -2
  35. package/dist/{reclaim-C5Jmuahx.mjs → reclaim-iW9Cad7k.mjs} +80 -74
  36. package/dist/{reload-Z8nKifAk.mjs → reload-DazHDSFA.mjs} +32 -24
  37. package/dist/{server-bare-DEk37Mz2.mjs → server-bare-DEzPZLvf.mjs} +2 -2
  38. package/dist/server-expo-44em5Pmg.mjs +2 -0
  39. package/dist/{server-expo-C3ktAwUG.mjs → server-expo-DizT4OMk.mjs} +4 -4
  40. package/dist/{settings-DwU1_U_q.mjs → settings-DEJf6_kJ.mjs} +2 -2
  41. package/dist/slot-launch-cci0xvmC.mjs +19 -0
  42. package/dist/{start-BrY4EuIg.mjs → start-mObjQH2W.mjs} +11 -11
  43. package/dist/{state-CZ-ER-8d.mjs → state-B3XA2wp2.mjs} +9 -9
  44. package/dist/{stats-BCWEVLrM.mjs → stats-BR-kwj0B.mjs} +1 -1
  45. package/dist/{stats-BsF15Bti.mjs → stats-CaaQr4yN.mjs} +3 -3
  46. package/dist/{status-DFI1Hfpg.mjs → status-BxQNqu9t.mjs} +55 -24
  47. package/dist/{stop-B7DR4awK.mjs → stop-CrHGcjrV.mjs} +2 -2
  48. package/dist/{stop-Btg770vV.mjs → stop-DimGXEKY.mjs} +93 -42
  49. package/dist/supervisor-run.d.mts +1 -2
  50. package/dist/supervisor-run.mjs +5 -5
  51. package/dist/{remote-cache-DDmJ1Roq.mjs → teardown-ClFp1Aax.mjs} +887 -888
  52. package/dist/{workspace-process-lock-BKfdys1q.mjs → workspace-process-lock-3NcQnZ0X.mjs} +1 -1
  53. package/dist/{worktree-BPTbI-LN.mjs → worktree-DxTgN1CJ.mjs} +12 -12
  54. package/dist/{worktree-C79xx_kM.mjs → worktree-Eq6TLH8N.mjs} +1 -1
  55. package/dist/{xcode-DeKCribl.mjs → xcode-D7sAp-Zy.mjs} +82 -56
  56. package/package.json +5 -5
  57. package/dist/android-K63_0UfF.mjs +0 -727
  58. package/dist/build-lock-BsVoLDaq.mjs +0 -645
  59. package/dist/server-expo-Drd7ksHu.mjs +0 -2
@@ -1,4 +1,4 @@
1
- import { f as releaseClaim, h as tryAcquireClaim } from "./ownership-claim-7zRxxTfZ.mjs";
1
+ import { f as releaseClaim, h as tryAcquireClaim } from "./ownership-claim-Bf2CAfw3.mjs";
2
2
  import { join } from "node:path";
3
3
  //#region src/engine/workspace-process-lock.ts
4
4
  const DEFAULT_WAIT_MS = 6e4;
@@ -1,17 +1,17 @@
1
1
  import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { S as upsertProject, f as getProject, g as loadConfig, h as isPathPrefix, u as getConfigDir, v as removeProject, w as captureProcessIdentity } from "./config-CMtIaTk9.mjs";
3
- import { c as readMetroTunnel, u as readRemoteSession } from "./state-CZ-ER-8d.mjs";
4
- import { _ as resolveFullRef, b as warmWorktreePaths, c as hasRemote, d as isPodInstallChurn, f as listWorktrees, h as removeWorktree, i as depsOutOfSync, l as hasUncommittedWork, m as readWorktreeExclude, n as cloneIgnoredEntries, o as dirtyPaths, p as podsOutOfSync, r as deleteBranch, s as gitCommonDir, t as branchExists, u as isMainWorkingTree, v as restoreFile, y as unpushedCommits } from "./worktree-C79xx_kM.mjs";
5
- import { a as findProjectRoot } from "./project-BBTVABUz.mjs";
6
- import { v as POOL_SETTING_REMEDY, x as parkedMaxSetting } from "./remote-cache-DDmJ1Roq.mjs";
7
- import { A as resolveSettings, I as unknownSettingKeys, N as settingShapeErrors, a as SETTING_SHAPE_REMEDY } from "./settings-DwU1_U_q.mjs";
2
+ import { S as upsertProject, f as getProject, g as loadConfig, h as isPathPrefix, u as getConfigDir, v as removeProject, w as captureProcessIdentity } from "./config-CcefMY2Q.mjs";
3
+ import { c as readMetroTunnel, u as readRemoteSession } from "./state-B3XA2wp2.mjs";
4
+ import { _ as resolveFullRef, b as warmWorktreePaths, c as hasRemote, d as isPodInstallChurn, f as listWorktrees, h as removeWorktree, i as depsOutOfSync, l as hasUncommittedWork, m as readWorktreeExclude, n as cloneIgnoredEntries, o as dirtyPaths, p as podsOutOfSync, r as deleteBranch, s as gitCommonDir, t as branchExists, u as isMainWorkingTree, v as restoreFile, y as unpushedCommits } from "./worktree-Eq6TLH8N.mjs";
5
+ import { a as findProjectRoot } from "./project-fKmuIaMI.mjs";
8
6
  import { a as formatElapsed, f as releasedLeaseFact, i as formatDuration, l as phaseLine, m as shortUdid, u as plural } from "./command-output-8wHMEde0.mjs";
9
- import { a as clearClaimChild, c as isClaimUnavailable, d as readClaimSet, f as releaseClaim, h as tryAcquireClaim, i as claimRemoveCommand, l as markClaimChildPending, m as settleClaim, p as setClaimChild, r as claimFailure, s as isClaimRefusal, u as processGroupAlive } from "./ownership-claim-7zRxxTfZ.mjs";
10
- import { g as podInstallCommand, r as HEARTBEAT_INTERVAL_MS } from "./xcode-DeKCribl.mjs";
11
- import { a as installedNpmTreeIsValid, i as hasInstalledDependencies, o as locallyKnownUpstream, t as dependencyState } from "./doctor-CqPeMsSm.mjs";
12
- import { c as withManagedTunnelRemovalLock, o as withManagedRemoteWorktreeRemovalLock } from "./ownership-CYOyqxUo.mjs";
13
- import { n as reclaimProject } from "./reclaim-C5Jmuahx.mjs";
14
- import { a as runPodInstall, i as runCaptured, n as podsAreStale, r as readPodState, t as DEPS_ERROR } from "./deps-BLBsTNDS.mjs";
7
+ import { a as clearClaimChild, c as isClaimUnavailable, d as readClaimSet, f as releaseClaim, h as tryAcquireClaim, i as claimRemoveCommand, l as markClaimChildPending, m as settleClaim, p as setClaimChild, r as claimFailure, s as isClaimRefusal, u as processGroupAlive } from "./ownership-claim-Bf2CAfw3.mjs";
8
+ import { A as resolveSettings, I as unknownSettingKeys, N as settingShapeErrors, a as SETTING_SHAPE_REMEDY } from "./settings-DEJf6_kJ.mjs";
9
+ import { a as POOL_SETTING_REMEDY, c as parkedMaxSetting } from "./teardown-ClFp1Aax.mjs";
10
+ import { g as podInstallCommand, r as HEARTBEAT_INTERVAL_MS } from "./xcode-D7sAp-Zy.mjs";
11
+ import { a as installedNpmTreeIsValid, i as hasInstalledDependencies, o as locallyKnownUpstream, t as dependencyState } from "./doctor-Bmv86UUW.mjs";
12
+ import { c as withManagedTunnelRemovalLock, o as withManagedRemoteWorktreeRemovalLock } from "./ownership-BpCKiRlQ.mjs";
13
+ import { n as reclaimProject } from "./reclaim-iW9Cad7k.mjs";
14
+ import { a as runPodInstall, i as runCaptured, n as podsAreStale, r as readPodState, t as DEPS_ERROR } from "./deps-Spz8ozgo.mjs";
15
15
  import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
16
16
  import { createHash, randomUUID } from "node:crypto";
17
17
  import { join, relative } from "node:path";
@@ -1,5 +1,5 @@
1
1
  import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { g as loadConfig, u as getConfigDir } from "./config-CMtIaTk9.mjs";
2
+ import { g as loadConfig, u as getConfigDir } from "./config-CcefMY2Q.mjs";
3
3
  import { accessSync, chmodSync, constants, lstatSync, mkdirSync, mkdtempSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
4
4
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
5
  import { chmodSync as chmodSync$1, existsSync as existsSync$1, lstatSync as lstatSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, realpathSync as realpathSync$1, utimesSync } from "fs";
@@ -1,18 +1,18 @@
1
1
  import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { C as withConfigLock, E as inspectProcessIdentity, _ as releaseAndroidConsolePort, b as setDevice, g as loadConfig, i as clearDevice, t as allConsolePortsAndSerials, u as getConfigDir, w as captureProcessIdentity, y as saveConfig } from "./config-CMtIaTk9.mjs";
3
- import { r as register } from "./cache-manifest-Cu4_Znw9.mjs";
4
- import { E as workspaceId, S as sharedCompilationCache, w as workspaceDerivedData } from "./state-CZ-ER-8d.mjs";
5
- import { s as ownedDeviceLabel } from "./project-BBTVABUz.mjs";
2
+ import { C as withConfigLock, E as inspectProcessIdentity, L as deviceSlotPlatforms, _ as releaseAndroidConsolePort, b as setDevice, g as loadConfig, i as clearDevice, t as allConsolePortsAndSerials, u as getConfigDir, w as captureProcessIdentity, y as saveConfig, z as projectDeviceSlots } from "./config-CcefMY2Q.mjs";
3
+ import { r as register } from "./cache-manifest-CXR-Y8pH.mjs";
4
+ import { E as workspaceId, S as sharedCompilationCache, w as workspaceDerivedData } from "./state-B3XA2wp2.mjs";
5
+ import { s as ownedDeviceLabel } from "./project-fKmuIaMI.mjs";
6
6
  import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-C7IlIzd7.mjs";
7
- import { r as pidExists } from "./metro-COslRK_F.mjs";
8
- import { C as selectParked, S as readParked, _ as teardownParkedIosSim, b as dropParked, g as teardownParkedAvd, m as teardownOwnedAvd, x as parkedMaxSetting, y as adoptParked } from "./remote-cache-DDmJ1Roq.mjs";
9
- import { U as resolveOptimizations, l as androidDataPartitionSizeGbSetting, o as androidAvdConfigSetting, v as iosSimSlimProfileSetting } from "./settings-DwU1_U_q.mjs";
7
+ import { r as pidExists } from "./metro-CORqXyTL.mjs";
10
8
  import { a as formatElapsed, l as phaseLine } from "./command-output-8wHMEde0.mjs";
11
- import { E as readHostMemoryPressure, T as hostMemoryPressureAdvice, _ as renameIosSim, b as resolveIosCreation, c as iosRuntimeMatches, d as listIosDeviceTypes, g as parseRuntimeVersion, i as createOwnedIosSim, l as iosSimulatorFailureAdvice, m as ownedSimName, n as bootIosSim, u as listAllIosSims, v as resetIosKeychain, x as resolveOwnedIosSim, y as resetIosPrivacy } from "./ios-D1h1Di39.mjs";
12
- import { f as releaseClaim, h as tryAcquireClaim, i as claimRemoveCommand, l as markClaimChildPending, n as ClaimRefusedError, p as setClaimChild, u as processGroupAlive } from "./ownership-claim-7zRxxTfZ.mjs";
13
- import { A as pickDefaultSystemImage, D as ownedAvdName, E as ownedAvdMatchesConfiguration, L as waitForBoot, N as resolveOwnedAvdSerial, O as ownedAvdSystemImage, b as listAvds, c as avdPoolConfiguration, d as configureNewOwnedAvd, f as createOwnedAvd, o as assertOwnedAvdStopped, u as bootAndroidEmulator, v as getAvdNameForSerial, w as nextConsolePort, x as listInstalledSystemImages, y as listAdbDevices } from "./android-K63_0UfF.mjs";
14
- import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BKfdys1q.mjs";
15
- import { t as cleanLine } from "./server-expo-C3ktAwUG.mjs";
9
+ import { f as releaseClaim, h as tryAcquireClaim, i as claimRemoveCommand, l as markClaimChildPending, n as ClaimRefusedError, p as setClaimChild, u as processGroupAlive } from "./ownership-claim-Bf2CAfw3.mjs";
10
+ import { A as pickDefaultSystemImage, D as ownedAvdName, E as ownedAvdMatchesConfiguration, L as waitForBoot, N as resolveOwnedAvdSerial, O as ownedAvdSystemImage, b as listAvds, c as avdPoolConfiguration, d as configureNewOwnedAvd, f as createOwnedAvd, o as assertOwnedAvdStopped, u as bootAndroidEmulator, v as getAvdNameForSerial, w as nextConsolePort, x as listInstalledSystemImages, y as listAdbDevices } from "./android-BqVIJoG7.mjs";
11
+ import { U as resolveOptimizations, l as androidDataPartitionSizeGbSetting, o as androidAvdConfigSetting, v as iosSimSlimProfileSetting } from "./settings-DEJf6_kJ.mjs";
12
+ import { t as cleanLine } from "./server-expo-DizT4OMk.mjs";
13
+ import { c as parkedMaxSetting, i as teardownParkedIosSim, l as readParked, o as adoptParked, r as teardownParkedAvd, s as dropParked, t as teardownOwnedAvd, u as selectParked } from "./teardown-ClFp1Aax.mjs";
14
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-3NcQnZ0X.mjs";
15
+ import { E as readHostMemoryPressure, T as hostMemoryPressureAdvice, _ as renameIosSim, b as resolveIosCreation, c as iosRuntimeMatches, d as listIosDeviceTypes, g as parseRuntimeVersion, i as createOwnedIosSim, l as iosSimulatorFailureAdvice, m as ownedSimName, n as bootIosSim, u as listAllIosSims, v as resetIosKeychain, x as resolveOwnedIosSim, y as resetIosPrivacy } from "./ios-Deh5js5V.mjs";
16
16
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
17
17
  import { createHash, randomUUID } from "node:crypto";
18
18
  import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
@@ -161,9 +161,12 @@ function simslimIsOnPath() {
161
161
  }
162
162
  //#endregion
163
163
  //#region src/engine/device.ts
164
- function startIosBoot(udid, configure) {
164
+ function startIosBoot(udid, configure, label, out) {
165
165
  const done = (async () => {
166
- await bootIosSim(udid);
166
+ await bootIosSim(udid, {
167
+ label,
168
+ out
169
+ });
167
170
  await configure();
168
171
  })();
169
172
  done.catch(() => {});
@@ -172,11 +175,13 @@ function startIosBoot(udid, configure) {
172
175
  done
173
176
  };
174
177
  }
175
- async function ensureOwnedDevice({ platform, project, projectPath, settingsRoot = projectPath, label = ownedDeviceLabel(projectPath), settings, flags = {}, note = () => {}, out = () => {}, logFile = null, alive = pidExists, configureAvd = configureNewOwnedAvd, teardownAvd = teardownOwnedAvd, reconcileIosSimulator = reconcileSimSlim }) {
176
- const record = project?.platforms?.[platform] ?? null;
178
+ async function ensureOwnedDevice({ platform, project, projectPath, slot, settingsRoot = projectPath, label = ownedDeviceLabel(projectPath), settings, flags = {}, note = () => {}, out = () => {}, logFile = null, alive = pidExists, configureAvd = configureNewOwnedAvd, teardownAvd = teardownOwnedAvd, reconcileIosSimulator = reconcileSimSlim }) {
179
+ if (slot && slot !== "default") label = `${label}-${slot}`;
180
+ const record = deviceSlotPlatforms(project, slot)?.[platform] ?? null;
177
181
  if (platform === "ios") return ensureOwnedIosDevice({
178
182
  record,
179
183
  projectPath,
184
+ slot,
180
185
  settingsRoot,
181
186
  label,
182
187
  settings,
@@ -188,6 +193,7 @@ async function ensureOwnedDevice({ platform, project, projectPath, settingsRoot
188
193
  return ensureOwnedAndroidDevice({
189
194
  record,
190
195
  projectPath,
196
+ slot,
191
197
  settingsRoot,
192
198
  label,
193
199
  settings,
@@ -200,7 +206,7 @@ async function ensureOwnedDevice({ platform, project, projectPath, settingsRoot
200
206
  teardownAvd
201
207
  });
202
208
  }
203
- async function ensureOwnedIosDevice({ record, projectPath, settingsRoot, label, settings, flags, note, out, reconcileIosSimulator }) {
209
+ async function ensureOwnedIosDevice({ record, projectPath, slot, settingsRoot, label, settings, flags, note, out, reconcileIosSimulator }) {
204
210
  const memoryAdvice = hostMemoryPressureAdvice(readHostMemoryPressure());
205
211
  if (memoryAdvice) out(chalk.yellow(phaseLine("memory", memoryAdvice)));
206
212
  const simslimProfile = iosSimSlimProfileSetting(settings, settingsRoot);
@@ -233,6 +239,7 @@ async function ensureOwnedIosDevice({ record, projectPath, settingsRoot, label,
233
239
  return configureOwnedIosSim({
234
240
  record: updated,
235
241
  projectPath,
242
+ slot,
236
243
  profile: simslimProfile,
237
244
  out,
238
245
  reconcileIosSimulator
@@ -246,7 +253,7 @@ async function ensureOwnedIosDevice({ record, projectPath, settingsRoot, label,
246
253
  out(chalk.dim(phaseLine("device", `booting ${name} (${sim.udid})`)));
247
254
  return {
248
255
  ...updated,
249
- booting: startIosBoot(sim.udid, configure),
256
+ booting: startIosBoot(sim.udid, configure, name, out),
250
257
  ...facts
251
258
  };
252
259
  }
@@ -265,6 +272,12 @@ async function ensureOwnedIosDevice({ record, projectPath, settingsRoot, label,
265
272
  return record;
266
273
  }
267
274
  }
275
+ withConfigLock(() => {
276
+ const current = deviceSlotPlatforms(loadConfig()?.projects?.[projectPath], slot)?.ios;
277
+ if (!current) return;
278
+ if (current.deviceUdid !== record.deviceUdid || current.owned !== record.owned) throw new Error("The simulator assignment changed during recovery. Run `stim ios` again.");
279
+ clearDevice(projectPath, "ios", slot);
280
+ });
268
281
  }
269
282
  const choice = resolveIosCreation({
270
283
  deviceType: flags.deviceType || settings.ios?.deviceType,
@@ -272,6 +285,7 @@ async function ensureOwnedIosDevice({ record, projectPath, settingsRoot, label,
272
285
  });
273
286
  const adopted = parkedMaxSetting("ios").max > 0 ? await withIosDeviceNameLock(() => takeParkedIosSim({
274
287
  projectPath,
288
+ slot,
275
289
  label,
276
290
  choice,
277
291
  out
@@ -283,11 +297,12 @@ async function ensureOwnedIosDevice({ record, projectPath, settingsRoot, label,
283
297
  await configureOwnedIosSim({
284
298
  record: adopted,
285
299
  projectPath,
300
+ slot,
286
301
  profile: simslimProfile,
287
302
  out,
288
303
  reconcileIosSimulator
289
304
  });
290
- });
305
+ }, adopted.deviceName, out);
291
306
  return {
292
307
  ...adopted,
293
308
  booting,
@@ -305,7 +320,7 @@ async function ensureOwnedIosDevice({ record, projectPath, settingsRoot, label,
305
320
  deviceUdid: result.udid,
306
321
  owned: true,
307
322
  deviceName: result.name
308
- });
323
+ }, slot);
309
324
  return result;
310
325
  });
311
326
  const newRecord = {
@@ -316,10 +331,11 @@ async function ensureOwnedIosDevice({ record, projectPath, settingsRoot, label,
316
331
  const booting = startIosBoot(created.udid, () => configureOwnedIosSim({
317
332
  record: newRecord,
318
333
  projectPath,
334
+ slot,
319
335
  profile: simslimProfile,
320
336
  out,
321
337
  reconcileIosSimulator
322
- }));
338
+ }), created.name, out);
323
339
  return {
324
340
  ...newRecord,
325
341
  created: true,
@@ -333,8 +349,8 @@ function withIosDeviceNameLock(fn) {
333
349
  }
334
350
  function ownedIosNameSuffix(label, model, projectPath, udid) {
335
351
  const names = new Set(listAllIosSims({ includeUnavailable: true }).filter((sim) => sim.udid !== udid).map((sim) => sim.name));
336
- for (const project of Object.values(loadConfig()?.projects ?? {})) {
337
- const record = project.platforms?.ios;
352
+ for (const project of Object.values(loadConfig()?.projects ?? {})) for (const { platforms } of projectDeviceSlots(project)) {
353
+ const record = platforms.ios;
338
354
  if (record?.deviceUdid !== udid && record?.deviceName) names.add(record.deviceName);
339
355
  }
340
356
  for (const parked of readParked("ios")) if (parked.udid !== udid) names.add(parked.name);
@@ -357,7 +373,7 @@ function resetAdoptedSim(udid, out) {
357
373
  resetIosPrivacy(udid);
358
374
  resetIosKeychain(udid);
359
375
  }
360
- function takeParkedIosSim({ projectPath, label, choice, out }) {
376
+ function takeParkedIosSim({ projectPath, slot, label, choice, out }) {
361
377
  const candidates = selectParked(readParked("ios"), {
362
378
  deviceTypeIdentifier: choice.deviceTypeId,
363
379
  runtimeIdentifier: choice.runtimeId
@@ -393,6 +409,7 @@ function takeParkedIosSim({ projectPath, label, choice, out }) {
393
409
  if (!adoptParked({
394
410
  platform: "ios",
395
411
  projectPath,
412
+ slot,
396
413
  udid: parked.udid,
397
414
  device
398
415
  })) continue;
@@ -403,10 +420,10 @@ function takeParkedIosSim({ projectPath, label, choice, out }) {
403
420
  }
404
421
  return null;
405
422
  }
406
- function clearIosAdoptionPending(projectPath) {
423
+ function clearIosAdoptionPending(projectPath, slot = "default") {
407
424
  withConfigLock(() => {
408
425
  const cfg = loadConfig();
409
- const ios = cfg?.projects?.[projectPath]?.platforms?.ios;
426
+ const ios = deviceSlotPlatforms(cfg?.projects?.[projectPath], slot)?.ios;
410
427
  if (!cfg || !ios?.adoptionPending) return;
411
428
  delete ios.adopted;
412
429
  delete ios.adoptionPending;
@@ -414,7 +431,7 @@ function clearIosAdoptionPending(projectPath) {
414
431
  saveConfig(cfg);
415
432
  });
416
433
  }
417
- async function configureOwnedIosSim({ record, projectPath, profile, out, reconcileIosSimulator }) {
434
+ async function configureOwnedIosSim({ record, projectPath, slot, profile, out, reconcileIosSimulator }) {
418
435
  if (profile) out(chalk.dim(phaseLine("device", `applying the SimSlim profile to ${record.deviceUdid}`)));
419
436
  else if (record.simslimManaged) out(chalk.dim(phaseLine("device", `restoring stock simulator services on ${record.deviceUdid}`)));
420
437
  const previouslyManaged = Boolean(record.simslimManaged);
@@ -423,7 +440,7 @@ async function configureOwnedIosSim({ record, projectPath, profile, out, reconci
423
440
  ...record,
424
441
  simslimManaged: true
425
442
  };
426
- setDevice(projectPath, "ios", pending);
443
+ setDevice(projectPath, "ios", pending, slot);
427
444
  record = pending;
428
445
  }
429
446
  const result = await reconcileIosSimulator({
@@ -435,15 +452,12 @@ async function configureOwnedIosSim({ record, projectPath, profile, out, reconci
435
452
  const updated = { ...record };
436
453
  if (result.managed) updated.simslimManaged = true;
437
454
  else delete updated.simslimManaged;
438
- setDevice(projectPath, "ios", updated);
455
+ setDevice(projectPath, "ios", updated, slot);
439
456
  return updated;
440
457
  }
441
- function findOtherProjectOwningAvd(avdName, projectPath) {
458
+ function findOtherProjectOwningAvd(avdName, projectPath, selectedSlot = "default") {
442
459
  const cfg = loadConfig();
443
- for (const [path, proj] of Object.entries(cfg?.projects || {})) {
444
- if (path === projectPath) continue;
445
- if (proj?.platforms?.android?.avdName === avdName) return path;
446
- }
460
+ for (const [path, proj] of Object.entries(cfg?.projects || {})) if (projectDeviceSlots(proj).some(({ slot, platforms }) => (path !== projectPath || slot !== selectedSlot) && platforms.android?.avdName?.toLowerCase() === avdName.toLowerCase())) return path;
447
461
  return null;
448
462
  }
449
463
  var AvdRecoveryError = class extends Error {
@@ -453,7 +467,7 @@ var AvdRecoveryError = class extends Error {
453
467
  this.remedy = remedy;
454
468
  }
455
469
  };
456
- async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, label, settings, flags, note, out, logFile, alive, configureAvd, teardownAvd }) {
470
+ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoot, label, settings, flags, note, out, logFile, alive, configureAvd, teardownAvd }) {
457
471
  const avdConfig = androidAvdConfigSetting(settings, settingsRoot);
458
472
  const configuration = avdPoolConfiguration(androidDataPartitionSizeGbSetting(settings), avdConfig);
459
473
  if (record?.setupIncomplete && record.avdName) {
@@ -462,7 +476,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
462
476
  owner: { projectPath }
463
477
  });
464
478
  if (cleanup.status === "failed" || cleanup.status === "skipped") throw new Error(`Owned AVD ${record.avdName} has incomplete setup and could not be deleted (${cleanup.reason || cleanup.status}). Fix the cause, then retry; Stim kept the device record for cleanup.`);
465
- clearDevice(projectPath, "android");
479
+ clearDevice(projectPath, "android", slot);
466
480
  record = null;
467
481
  }
468
482
  if (record?.avdName) {
@@ -479,7 +493,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
479
493
  owned: true,
480
494
  deviceName: record.deviceName ?? record.avdName
481
495
  };
482
- setDevice(projectPath, "android", updated);
496
+ setDevice(projectPath, "android", updated, slot);
483
497
  return {
484
498
  ...updated,
485
499
  systemImage: ownedAvdSystemImage(record.avdName)
@@ -491,6 +505,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
491
505
  avdName: record.avdName,
492
506
  metadata: record,
493
507
  projectPath,
508
+ slot,
494
509
  deviceName: record.deviceName,
495
510
  out,
496
511
  logFile,
@@ -543,6 +558,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
543
558
  if (!adoptParked({
544
559
  platform: "android",
545
560
  projectPath,
561
+ slot,
546
562
  udid: parked.udid,
547
563
  device: adopted
548
564
  })) continue;
@@ -550,6 +566,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
550
566
  ...await bootOwnedAvdOnFreshPort({
551
567
  avdName: parked.name,
552
568
  projectPath,
569
+ slot,
553
570
  metadata: adopted,
554
571
  out,
555
572
  logFile,
@@ -564,9 +581,9 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
564
581
  let fresh = false;
565
582
  try {
566
583
  created = withConfigLock(() => {
567
- const current = loadConfig()?.projects?.[projectPath]?.platforms?.android;
584
+ const current = deviceSlotPlatforms(loadConfig()?.projects?.[projectPath], slot)?.android;
568
585
  if (current?.avdName && current.avdName !== record?.avdName) throw new Error(`Another Stim run assigned AVD ${current.avdName} to this workspace. Retry to use it.`);
569
- if (readParked("android").some((entry) => entry.name === ownedAvdName(label)) || findOtherProjectOwningAvd(ownedAvdName(label), projectPath)) label = `${label}-${randomUUID().slice(0, 8)}`;
586
+ if (readParked("android").some((entry) => entry.name === ownedAvdName(label)) || findOtherProjectOwningAvd(ownedAvdName(label), projectPath, slot)) label = `${label}-${randomUUID().slice(0, 8)}`;
570
587
  const result = createOwnedAvd(label, { systemImage: flags.systemImage || settings.android?.systemImage });
571
588
  setDevice(projectPath, "android", {
572
589
  avdName: result.avdName,
@@ -574,7 +591,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
574
591
  deviceName: result.avdName,
575
592
  setupIncomplete: true,
576
593
  poolConfiguration: configuration
577
- });
594
+ }, slot);
578
595
  return result;
579
596
  });
580
597
  fresh = true;
@@ -586,9 +603,9 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
586
603
  if (!listAvds().includes(avdName)) throw new AvdRecoveryError(`AVD ${avdName} already exists on disk but is not listed by the emulator. ${message}`, "Run `npx stim gc` to inspect orphaned owned AVDs, then `npx stim gc --delete` to reclaim those safe to delete. Retry `stim android` after cleanup; keep any AVD that GC cannot verify.", e);
587
604
  created = withConfigLock(() => {
588
605
  if (readParked("android").some((entry) => entry.name === avdName)) throw new Error(`AVD ${avdName} was parked by another Stim run. Retry to adopt it safely.`, { cause: e });
589
- const owner = findOtherProjectOwningAvd(avdName, projectPath);
606
+ const owner = findOtherProjectOwningAvd(avdName, projectPath, slot);
590
607
  if (owner) throw new Error(`AVD ${avdName} already exists and is owned by another project (${owner}). Retry to allocate a distinct owned emulator.`, { cause: e });
591
- const current = loadConfig()?.projects?.[projectPath]?.platforms?.android;
608
+ const current = deviceSlotPlatforms(loadConfig()?.projects?.[projectPath], slot)?.android;
592
609
  if (current?.avdName) {
593
610
  const state = current.setupIncomplete ? "has incomplete setup" : "was registered";
594
611
  throw new Error(`AVD ${current.avdName} ${state} by another concurrent Stim run. Retry after that run finishes so the recorded device is resolved safely.`, { cause: e });
@@ -602,7 +619,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
602
619
  deviceName: avdName,
603
620
  ...resolved.serial ? { consolePort: Number(resolved.serial.replace(/^emulator-/, "")) } : {}
604
621
  };
605
- setDevice(projectPath, "android", recovered);
622
+ setDevice(projectPath, "android", recovered, slot);
606
623
  return {
607
624
  ...recovered,
608
625
  systemImage: ownedAvdSystemImage(avdName),
@@ -633,7 +650,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
633
650
  owner: { projectPath }
634
651
  });
635
652
  const kept = cleanup.status === "failed" || cleanup.status === "skipped";
636
- if (!kept) clearDevice(projectPath, "android");
653
+ if (!kept) clearDevice(projectPath, "android", slot);
637
654
  const orphan = kept ? ` The owned AVD remains tracked for cleanup (${cleanup.reason || cleanup.status}); fix the cause, then retry or run \`stim gc --delete\`.` : "";
638
655
  throw new Error(`Created owned AVD ${created.avdName}, but could not configure its AVD settings: ${String(error?.message || error)}${orphan}`, { cause: error });
639
656
  }
@@ -642,6 +659,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
642
659
  avdName: created.avdName,
643
660
  metadata: fresh ? { poolConfiguration: configuration } : void 0,
644
661
  projectPath,
662
+ slot,
645
663
  deviceName: created.avdName,
646
664
  out,
647
665
  logFile,
@@ -651,7 +669,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
651
669
  systemImage: created.systemImage
652
670
  };
653
671
  }
654
- function claimAndroidConsolePort({ projectPath, avdName, deviceName, livePorts = [], metadata }, { lock = withConfigLock, recordedPorts = () => allConsolePortsAndSerials().androidConsolePorts, record = setDevice } = {}) {
672
+ function claimAndroidConsolePort({ projectPath, slot, avdName, deviceName, livePorts = [], metadata }, { lock = withConfigLock, recordedPorts = () => allConsolePortsAndSerials().androidConsolePorts, record = setDevice } = {}) {
655
673
  return lock(() => {
656
674
  const consolePort = nextConsolePort([...recordedPorts(), ...livePorts]);
657
675
  const claim = {
@@ -662,7 +680,7 @@ function claimAndroidConsolePort({ projectPath, avdName, deviceName, livePorts =
662
680
  owned: true,
663
681
  deviceName: deviceName ?? avdName
664
682
  };
665
- record(projectPath, "android", claim);
683
+ record(projectPath, "android", claim, slot);
666
684
  return claim;
667
685
  });
668
686
  }
@@ -670,9 +688,10 @@ function liveAndroidConsolePorts() {
670
688
  const adbLive = listAdbDevices();
671
689
  return [...adbLive.emulators.map((e) => e.consolePort), ...adbLive.unhealthy.map((u) => u.consolePort).filter((p) => p != null)];
672
690
  }
673
- async function bootOwnedAvdOnFreshPort({ avdName, metadata, projectPath, deviceName, out, logFile = null, alive = pidExists }) {
691
+ async function bootOwnedAvdOnFreshPort({ avdName, metadata, projectPath, slot, deviceName, out, logFile = null, alive = pidExists }) {
674
692
  const claim = claimAndroidConsolePort({
675
693
  projectPath,
694
+ slot,
676
695
  avdName,
677
696
  deviceName,
678
697
  livePorts: liveAndroidConsolePorts(),
@@ -691,7 +710,7 @@ async function bootOwnedAvdOnFreshPort({ avdName, metadata, projectPath, deviceN
691
710
  serial
692
711
  };
693
712
  } catch (error) {
694
- releaseAndroidConsolePort(projectPath, claim.consolePort);
713
+ releaseAndroidConsolePort(projectPath, claim.consolePort, slot);
695
714
  throw error;
696
715
  }
697
716
  }
@@ -706,25 +725,26 @@ function liveOwnedDeviceCount({ sims = [], adbEmulators = [], config = null } =
706
725
  let count = 0;
707
726
  for (const sim of sims) if (sim?.state === "Booted" && sim.name?.startsWith("stim-")) count++;
708
727
  const livePorts = new Set(adbEmulators.map((e) => e.consolePort));
709
- for (const proj of Object.values(config?.projects || {})) {
710
- const android = proj?.platforms?.android;
728
+ for (const proj of Object.values(config?.projects || {})) for (const { platforms } of projectDeviceSlots(proj)) {
729
+ const android = platforms.android;
711
730
  if (android?.owned && android.avdName && typeof android.consolePort === "number" && livePorts.has(android.consolePort)) count++;
712
731
  }
713
732
  return count;
714
733
  }
715
- function workspaceHasLiveDevice({ platform, project, sims = [], adbEmulators = [] } = {}) {
734
+ function workspaceHasLiveDevice({ platform, project, slot = "default", sims = [], adbEmulators = [] } = {}) {
716
735
  if (!platform) return false;
717
- const record = project?.platforms?.[platform];
736
+ const record = deviceSlotPlatforms(project, slot)?.[platform];
718
737
  if (!record) return false;
719
738
  if (platform === "ios") return sims.some((s) => s.udid === record.deviceUdid && s.state === "Booted");
720
739
  return typeof record.consolePort === "number" && adbEmulators.some((e) => e.consolePort === record.consolePort);
721
740
  }
722
- function deviceCapacityRefusal({ platform, project, max, sims = [], adb = null, config = null } = {}) {
741
+ function deviceCapacityRefusal({ platform, project, slot = "default", max, sims = [], adb = null, config = null } = {}) {
723
742
  if (!max || max <= 0) return null;
724
743
  const adbEmulators = adb?.emulators || [];
725
744
  if (workspaceHasLiveDevice({
726
745
  platform,
727
746
  project,
747
+ slot,
728
748
  sims,
729
749
  adbEmulators
730
750
  })) return null;
@@ -740,7 +760,7 @@ function deviceCapacityRefusal({ platform, project, max, sims = [], adb = null,
740
760
  remedy: "stop an environment (stim stop) or raise concurrency.maxDevices"
741
761
  };
742
762
  }
743
- function checkDeviceCapacity({ platform, project, max, sims = listAllIosSims, adb = listAdbDevices, config = loadConfig } = {}) {
763
+ function checkDeviceCapacity({ platform, project, slot = "default", max, sims = listAllIosSims, adb = listAdbDevices, config = loadConfig } = {}) {
744
764
  if (!max || max <= 0) return null;
745
765
  let simList = [];
746
766
  let adbRes = {
@@ -761,6 +781,7 @@ function checkDeviceCapacity({ platform, project, max, sims = listAllIosSims, ad
761
781
  return deviceCapacityRefusal({
762
782
  platform,
763
783
  project,
784
+ slot,
764
785
  max,
765
786
  sims: simList,
766
787
  adb: adbRes,
@@ -839,7 +860,8 @@ async function ensureIosBooted({ device, timeoutMs, pollMs, out }) {
839
860
  "simctl",
840
861
  "spawn",
841
862
  udid,
842
- "/usr/bin/true"
863
+ "launchctl",
864
+ "list"
843
865
  ], { timeoutMs: 3e4 });
844
866
  return {
845
867
  ok: true,
@@ -886,7 +908,11 @@ async function ensureIosBooted({ device, timeoutMs, pollMs, out }) {
886
908
  out(chalk.dim(phaseLine("device", `booting ${sim.name} (${udid})`)));
887
909
  const bootDeadline = Date.now() + timeoutMs;
888
910
  try {
889
- await bootIosSim(udid, { timeoutMs });
911
+ await bootIosSim(udid, {
912
+ timeoutMs,
913
+ label: sim.name,
914
+ out
915
+ });
890
916
  } catch (e) {
891
917
  return {
892
918
  failed: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stim",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Stim: fast, isolated React Native environments for coding agents",
5
5
  "keywords": [
6
6
  "agent",
@@ -42,16 +42,16 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "@expo/fingerprint": "^0.20.10",
45
- "@stim-cli/cache": "^1.2.0",
46
- "@stim-cli/core": "^1.2.0",
47
- "@stim-cli/metro": "^1.2.0",
45
+ "@stim-cli/cache": "^1.3.0",
46
+ "@stim-cli/core": "^1.3.0",
47
+ "@stim-cli/metro": "^1.3.0",
48
48
  "chalk": "^5.4.1",
49
49
  "commander": "^13.1.0",
50
50
  "unique-pid": "0.1.1",
51
51
  "ws": "^8.21.3"
52
52
  },
53
53
  "devDependencies": {
54
- "@stim-cli/expo-build-cache": "1.2.0",
54
+ "@stim-cli/expo-build-cache": "1.3.0",
55
55
  "@types/ws": "^8.18.1"
56
56
  },
57
57
  "engines": {