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,34 +1,35 @@
1
1
  import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { C as withConfigLock, S as upsertProject, b as setDevice, f as getProject, g as loadConfig, l as getConcurrencyLimits, y as saveConfig } from "./config-CMtIaTk9.mjs";
3
- import { r as createNdjsonWriter } from "./ndjson-DcAtEx_K.mjs";
4
- import { D as workspaceLogsDir, T as workspaceDir, _ as writeWorkspaceState, g as writeWorkspaceLaunch, n as MODE_EXPO, p as readWorkspaceState, t as MODE_BARE, v as emulatorLogFile } from "./state-CZ-ER-8d.mjs";
5
- import { C as removeTemporaryEntry, S as makeTemporaryDirectory, g as repoRoot, s as gitCommonDir } from "./worktree-C79xx_kM.mjs";
6
- import { a as findProjectRoot, c as projectShortcut, i as detectIsExpo, n as detectAndroidPackage, r as detectBundleId, t as appProjectProblem } from "./project-BBTVABUz.mjs";
2
+ import { C as withConfigLock, I as deviceSlotKey, L as deviceSlotPlatforms, S as upsertProject, V as validateDeviceSlot, b as setDevice, f as getProject, g as loadConfig, l as getConcurrencyLimits, y as saveConfig } from "./config-CcefMY2Q.mjs";
3
+ import { r as createNdjsonWriter } from "./ndjson-BZwI1NBv.mjs";
4
+ import { D as workspaceLogsDir, T as workspaceDir, _ as writeWorkspaceState, g as writeWorkspaceLaunch, n as MODE_EXPO, p as readWorkspaceState, t as MODE_BARE, v as emulatorLogFile } from "./state-B3XA2wp2.mjs";
5
+ import { C as removeTemporaryEntry, S as makeTemporaryDirectory, g as repoRoot, s as gitCommonDir } from "./worktree-Eq6TLH8N.mjs";
6
+ import { a as findProjectRoot, c as projectShortcut, i as detectIsExpo, n as detectAndroidPackage, r as detectBundleId, t as appProjectProblem } from "./project-fKmuIaMI.mjs";
7
7
  import { r as waitForChild, t as createLineReader } from "./process-output-C7IlIzd7.mjs";
8
- import { i as resolveProjectMetro, r as pidExists } from "./metro-COslRK_F.mjs";
9
- import { n as verifyCollectorOwnership, r as readCollectors } from "./ownership-CGPart6T.mjs";
10
- import { a as easAuthNote, c as loadProjectProvider, d as resolveEasCliBin, f as resolveRemote, i as checkEasAuth, n as UPLOAD_TIMEOUT_MS, o as exitAfterFlush, p as uploadRemote, r as cacheLevel, s as isEasAuthFailureText, t as RESOLVE_TIMEOUT_MS, x as parkedMaxSetting } from "./remote-cache-DDmJ1Roq.mjs";
11
- import { A as resolveSettings, B as artifactCachePolicy, D as remoteDeviceSettingError, E as remoteAndroidSetting, F as tunnelModeSetting, H as optimizationBuildProfile, I as unknownSettingKeys, N as settingShapeErrors, R as detectProviders, T as publicUrlSetting, U as resolveOptimizations, V as compilerCacheFallbackMessage, a as SETTING_SHAPE_REMEDY, d as cacheProviderSettingError, j as settingFile, k as resolveCacheProviderConfig, r as REMOTE_DEVICE_BACKENDS, s as androidAvdConfigSettingError, u as androidDataPartitionSizeGbSettingError, x as metroWarmupUrlSetting } from "./settings-DwU1_U_q.mjs";
8
+ import { i as resolveProjectMetro, r as pidExists } from "./metro-CORqXyTL.mjs";
9
+ import { n as verifyCollectorOwnership, r as readCollectors } from "./ownership-8O073GIC.mjs";
12
10
  import { _ as launchErrorPreview, c as launchErrorReport, g as stepTimer, h as stepClock, i as formatDuration, l as phaseLine, n as appReadinessMessage, p as shortHash } from "./command-output-8wHMEde0.mjs";
13
- import { D as verifyAndroidReleaseLaunch, E as unverifiedLaunchLines, O as verifyLaunch, S as launchAndroidReleaseApp, _ as installConflictKind, c as RELEASE_VERIFY_WAIT_MS, d as androidDevClientUrl, g as installAndroidApp, h as deviceShellArg, i as LAUNCH_BUNDLING, l as VERIFY_TIMEOUT_MS, n as DEFAULT_METRO_PORT, o as LAUNCH_FATAL, s as LAUNCH_UNVERIFIED, t as ADB_INSTALL_TIMEOUT_MS, u as androidAppProcess, x as launchAndroidApp } from "./app-install-NFxUahnH.mjs";
14
- import { C as untrackedNativeFiles, D as readAssetManifest, E as compareAssetManifests, S as untrackedMissLine, T as captureAssetManifest, _ as providerUploadOutcome, b as storeBuild, c as buildCacheKey, d as filesystemBuildCapability, f as fingerprintDiffRecord, g as providerDownloadPath, h as prepareProviderDownloadDir, i as releaseBuildLock, l as describeFingerprintMiss, m as fingerprintProject, n as acquireBuildLock, o as takeoverLine, p as fingerprintDiffSuffix, s as waitForBuild, t as WAIT_CEILING_MS, v as refingerprintAfterMutation, w as assetDiffReason, x as storedAssetManifest, y as resolveBuild } from "./build-lock-BsVoLDaq.mjs";
15
- import { r as claimFailure } from "./ownership-claim-7zRxxTfZ.mjs";
16
- import { a as lostLine, c as releaseLeaseOnSignal, i as leaseExpiryText, l as runLease, n as DEBUG_VERIFY_STEP_MS, o as lostRefusal, r as acquireRunLease, s as parseDeviceWait, t as selectFromPool, u as waitFlagConflict } from "./device-pool-B5pEVa3t.mjs";
17
- import { r as releaseBuildSlot, t as acquireBuildSlot } from "./build-slots-D0b6sJyo.mjs";
18
- import { c as ensureWorkspaceStorageSafely, d as noMetroMessage, f as noMetroRemedy, h as warmMetro, i as detectEntryFile, n as pickDevClientScheme, o as needsPrebuild, p as resolveMetroWithRetry, s as runPrebuild, t as devClientScheme, u as launchOutcomeRecord } from "./dev-client-RpvXxCXV.mjs";
19
- import { a as statsProjectKey, i as recordRunStats, n as readRunEstimates, t as createRunRecorder } from "./stats-BCWEVLrM.mjs";
20
- import { C as memoizeEmulatorProbe, L as waitForBoot, M as resetAdoptedAvd, N as resolveOwnedAvdSerial, P as resolvePhysicalDevice, _ as findBuildTool, a as androidSystemImageAbi, g as extractEmulatorFailure, h as emulatorFailureRemedy, i as androidPoolNoCandidatesRefusal, j as probeEmulatorSerial, k as physicalDeviceModel, m as emulatorDiskSpaceRemedy, n as androidHome, r as androidPoolCandidates, t as androidDeviceAbi, x as listInstalledSystemImages, y as listAdbDevices } from "./android-K63_0UfF.mjs";
21
- import { C as unknownAndroidSystemImageRefusal, _ as AvdRecoveryError, b as ensureBooted, f as startBuildHeartbeat, p as tailLines, r as HEARTBEAT_INTERVAL_MS, v as checkDeviceCapacity, x as ensureOwnedDevice } from "./xcode-DeKCribl.mjs";
22
- import { a as ensureMetroReachable, b as ownedSessionName, l as resolveRemoteContext, n as binOnPath, o as ensureRemoteBootOwned, s as remoteAndroidDeps } from "./device-remote-E81KAdXf.mjs";
23
- import { t as cleanLine } from "./server-expo-C3ktAwUG.mjs";
24
- import { a as buildAndroid, c as CCACHE_NOT_RUN, f as resolveCcache, l as CCACHE_UNAVAILABLE, n as resolveAndroidCas, o as productFlavorRefusal, p as formatDiagnostic, r as resolveAndroidCompilerCache, s as readProductFlavors, u as ccacheActivityLine } from "./android-cas-D6HPZ6UQ.mjs";
11
+ import { r as claimFailure } from "./ownership-claim-Bf2CAfw3.mjs";
12
+ import { $ as providerDownloadPath, C as memoizeEmulatorProbe, G as buildCacheKey, J as filesystemBuildCapability, K as describeFingerprintMiss, L as waitForBoot, M as resetAdoptedAvd, N as resolveOwnedAvdSerial, P as resolvePhysicalDevice, Q as prepareProviderDownloadDir, R as WAIT_CEILING_MS, U as takeoverLine, V as releaseBuildLock, W as waitForBuild, X as fingerprintDiffSuffix, Y as fingerprintDiffRecord, Z as fingerprintProject, _ as findBuildTool, a as androidSystemImageAbi, at as untrackedMissLine, ct as captureAssetManifest, et as providerUploadOutcome, g as extractEmulatorFailure, h as emulatorFailureRemedy, i as androidPoolNoCandidatesRefusal, it as storedAssetManifest, j as probeEmulatorSerial, k as physicalDeviceModel, lt as compareAssetManifests, m as emulatorDiskSpaceRemedy, n as androidHome, nt as resolveBuild, ot as untrackedNativeFiles, r as androidPoolCandidates, rt as storeBuild, st as assetDiffReason, t as androidDeviceAbi, tt as refingerprintAfterMutation, ut as readAssetManifest, x as listInstalledSystemImages, y as listAdbDevices, z as acquireBuildLock } from "./android-BqVIJoG7.mjs";
13
+ import { A as resolveSettings, B as artifactCachePolicy, D as remoteDeviceSettingError, E as remoteAndroidSetting, F as tunnelModeSetting, H as optimizationBuildProfile, I as unknownSettingKeys, N as settingShapeErrors, R as detectProviders, T as publicUrlSetting, U as resolveOptimizations, V as compilerCacheFallbackMessage, a as SETTING_SHAPE_REMEDY, d as cacheProviderSettingError, j as settingFile, k as resolveCacheProviderConfig, r as REMOTE_DEVICE_BACKENDS, s as androidAvdConfigSettingError, u as androidDataPartitionSizeGbSettingError, x as metroWarmupUrlSetting } from "./settings-DEJf6_kJ.mjs";
14
+ import { t as cleanLine } from "./server-expo-DizT4OMk.mjs";
15
+ import { C as uploadRemote, S as resolveRemote, _ as isEasAuthFailureText, c as parkedMaxSetting, d as RESOLVE_TIMEOUT_MS, f as UPLOAD_TIMEOUT_MS, g as exitAfterFlush, h as easAuthNote, m as checkEasAuth, p as cacheLevel, v as loadProjectProvider, x as resolveEasCliBin } from "./teardown-ClFp1Aax.mjs";
16
+ import { c as ensureWorkspaceStorageSafely, d as launchOutcomeRecord, f as noMetroMessage, g as warmMetro, i as detectEntryFile, m as resolveMetroWithRetry, n as pickDevClientScheme, o as needsPrebuild, p as noMetroRemedy, s as runPrebuild, t as devClientScheme, u as isPhysicalDeviceRequest, v as isEasBuildFailure, y as resolveEasDevelopmentBuild } from "./dev-client-BjisHDB5.mjs";
17
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-3NcQnZ0X.mjs";
18
+ import { D as verifyAndroidReleaseLaunch, E as unverifiedLaunchLines, O as verifyLaunch, S as launchAndroidReleaseApp, _ as installConflictKind, c as RELEASE_VERIFY_WAIT_MS, d as androidDevClientUrl, g as installAndroidApp, h as deviceShellArg, i as LAUNCH_BUNDLING, l as VERIFY_TIMEOUT_MS, n as DEFAULT_METRO_PORT, o as LAUNCH_FATAL, s as LAUNCH_UNVERIFIED, t as ADB_INSTALL_TIMEOUT_MS, u as androidAppProcess, x as launchAndroidApp } from "./app-install-DIp9riNx.mjs";
19
+ import { a as lostLine, c as releaseLeaseOnSignal, i as leaseExpiryText, l as runLease, n as DEBUG_VERIFY_STEP_MS, o as lostRefusal, r as acquireRunLease, s as parseDeviceWait, t as selectFromPool, u as waitFlagConflict } from "./device-pool-CPbLGfrB.mjs";
20
+ import { r as releaseBuildSlot, t as acquireBuildSlot } from "./build-slots-C3-R4XQr.mjs";
21
+ import { a as statsProjectKey, i as recordRunStats, n as readRunEstimates, t as createRunRecorder } from "./stats-BR-kwj0B.mjs";
22
+ import { C as unknownAndroidSystemImageRefusal, _ as AvdRecoveryError, b as ensureBooted, f as startBuildHeartbeat, p as tailLines, r as HEARTBEAT_INTERVAL_MS, v as checkDeviceCapacity, x as ensureOwnedDevice } from "./xcode-D7sAp-Zy.mjs";
23
+ import { a as ensureMetroReachable, b as ownedSessionName, l as resolveRemoteContext, n as binOnPath, o as ensureRemoteBootOwned, s as remoteAndroidDeps } from "./device-remote-AkYCKS8U.mjs";
24
+ import { a as buildAndroid, c as CCACHE_NOT_RUN, f as resolveCcache, l as CCACHE_UNAVAILABLE, n as resolveAndroidCas, o as productFlavorRefusal, p as formatDiagnostic, r as resolveAndroidCompilerCache, s as readProductFlavors, u as ccacheActivityLine } from "./android-cas-BR5YJBYv.mjs";
25
+ import { n as nativeRunCommand, t as launchSlotScope } from "./slot-launch-cci0xvmC.mjs";
25
26
  import { t as spawnEntry } from "./spawn-entry-nCE6m770.mjs";
26
- import { a as printNativeCrashReport, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-Dw8Gn3yw.mjs";
27
+ import { a as printNativeCrashReport, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-DHC31rXA.mjs";
27
28
  import { existsSync, mkdirSync, openSync, readFileSync, readdirSync, rmSync } from "node:fs";
28
29
  import { InvalidArgumentError } from "commander";
29
30
  import { basename, dirname, join, relative } from "node:path";
30
- import chalk from "chalk";
31
31
  import { createWarnOnce, loadCacheProvider, resolveTieredBuild, storeTieredBuild } from "@stim-cli/cache";
32
+ import chalk from "chalk";
32
33
  //#region src/engine/apk-swap.ts
33
34
  const ANDROID_BUNDLE_NAME = "index.android.bundle";
34
35
  const ANDROID_BUNDLE_ENTRY = `assets/${ANDROID_BUNDLE_NAME}`;
@@ -399,7 +400,12 @@ function androidSystemImageSetting(settings) {
399
400
  function resolveSystemImage(flag, settings) {
400
401
  return (typeof flag === "string" && flag.trim() !== "" ? flag.trim() : null) || androidSystemImageSetting(settings);
401
402
  }
402
- function systemImageRefusal({ flag, resolved, physical, remoteBackend, listImages }) {
403
+ function systemImageRefusal({ slot, flag, resolved, physical, remoteBackend, listImages }) {
404
+ if (slot && slot !== "default" && remoteBackend) return {
405
+ code: "STIM_BAD_ARG",
406
+ message: "Named slots currently support local simulators and physical devices.",
407
+ remedy: "Use the default slot for a remote session."
408
+ };
403
409
  if (typeof flag === "string" && flag.trim() === "") return {
404
410
  code: "STIM_BAD_ARG",
405
411
  message: "--system-image was given an empty id.",
@@ -624,9 +630,10 @@ function androidBuildOptions({ release, physical, device, variant, deviceAbi, co
624
630
  }
625
631
  //#endregion
626
632
  //#region src/commands/android/result.ts
627
- function androidFacts({ serial, avdName = null, deviceName = null, systemImage = null, fingerprint, cacheKey = null, variant = null, metroPort = null, cacheHit, cacheSkipped = false, waitedForBuild = null, appPath, bundleId, installSkipped = false, launched, ccache = CCACHE_UNAVAILABLE, logs, debugHttpHost = null, debugHttpHostNote = null, devClientUrl = null, durationMs, lease }) {
633
+ function androidFacts({ slot, serial, avdName = null, deviceName = null, systemImage = null, fingerprint, cacheKey = null, variant = null, metroPort = null, cacheHit, cacheSkipped = false, waitedForBuild = null, appPath, bundleId, installSkipped = false, launched, ccache = CCACHE_UNAVAILABLE, logs, debugHttpHost = null, debugHttpHostNote = null, devClientUrl = null, durationMs, lease }) {
628
634
  return {
629
635
  platform: PLATFORM,
636
+ ...slot && slot !== "default" ? { slot } : {},
630
637
  serial: serial ?? null,
631
638
  avdName: avdName ?? null,
632
639
  deviceName: deviceName ?? avdName ?? null,
@@ -689,7 +696,7 @@ async function finishAndroidUpload(uploadPending, remote, phase) {
689
696
  }
690
697
  return false;
691
698
  }
692
- function reportAndroidResult({ json, useBuildCache, variant, release, metroCheck, metroPort, logsDir, serial, apkPath, androidPackage, installSkipped, record, waitedForBuild, remote, providerName, launchState, launchWarning, launched, ccache, durationMs, writer, emit, lease, recordRun }) {
699
+ function reportAndroidResult({ slot, json, useBuildCache, variant, release, metroCheck, metroPort, logsDir, serial, apkPath, androidPackage, installSkipped, record, waitedForBuild, remote, providerName, launchState, launchWarning, launched, ccache, durationMs, writer, emit, lease, recordRun }) {
693
700
  recordRun({
694
701
  failed: false,
695
702
  cacheHit: cacheLevel(record.cacheHit),
@@ -697,6 +704,7 @@ function reportAndroidResult({ json, useBuildCache, variant, release, metroCheck
697
704
  durationMs
698
705
  });
699
706
  const facts = androidFacts({
707
+ slot,
700
708
  serial,
701
709
  avdName: record.avdName,
702
710
  deviceName: record.deviceName,
@@ -765,18 +773,18 @@ function persistLastBuild({ writeState, root, record, startedAt, durationMs, sta
765
773
  function collectorEntry() {
766
774
  return spawnEntry("collector-run");
767
775
  }
768
- function collectorLogFile(root) {
769
- return join(workspaceLogsDir(root), `collector-${PLATFORM}.log`);
776
+ function collectorLogFile(root, slot = "default") {
777
+ return join(workspaceLogsDir(root), `collector-${deviceSlotKey(PLATFORM, slot)}.log`);
770
778
  }
771
779
  const COLLECTOR_EXIT_WAIT_MS = 2e3;
772
780
  const COLLECTOR_POLL_MS = 25;
773
- function killPreviousCollector(root, { platform = PLATFORM, kill = (pid, signal) => process.kill(pid, signal), collectors = null, verify = verifyCollectorOwnership, isAlive = pidExists, note = (_line) => {} } = {}) {
774
- const record = (collectors ?? readCollectors(root))?.[platform];
781
+ function killPreviousCollector(root, { platform = PLATFORM, slot = "default", kill = (pid, signal) => process.kill(pid, signal), collectors = null, verify = verifyCollectorOwnership, isAlive = pidExists, note = (_line) => {} } = {}) {
782
+ const record = (collectors ?? readCollectors(root))?.[deviceSlotKey(platform, slot)];
775
783
  const pid = Number(record?.pid);
776
784
  if (!Number.isFinite(pid) || pid <= 0 || pid === process.pid) return null;
777
785
  const ownership = verify({
778
786
  pid,
779
- platform,
787
+ platform: deviceSlotKey(platform, slot),
780
788
  root,
781
789
  isAlive,
782
790
  expected: record
@@ -793,8 +801,9 @@ function killPreviousCollector(root, { platform = PLATFORM, kill = (pid, signal)
793
801
  return null;
794
802
  }
795
803
  }
796
- async function startCollector({ root, serial, packageName, spawn, kill, alive = pidExists, verify = verifyCollectorOwnership, waitMs = COLLECTOR_EXIT_WAIT_MS, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), out }) {
804
+ async function startCollector({ root, slot = "default", serial, packageName, spawn, kill, alive = pidExists, verify = verifyCollectorOwnership, waitMs = COLLECTOR_EXIT_WAIT_MS, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), out }) {
797
805
  const previousPid = killPreviousCollector(root, {
806
+ slot,
798
807
  kill,
799
808
  isAlive: alive,
800
809
  verify,
@@ -807,7 +816,7 @@ async function startCollector({ root, serial, packageName, spawn, kill, alive =
807
816
  let stdio = "ignore";
808
817
  try {
809
818
  mkdirSync(workspaceLogsDir(root), { recursive: true });
810
- const fd = openSync(collectorLogFile(root), "a");
819
+ const fd = openSync(collectorLogFile(root, slot), "a");
811
820
  stdio = [
812
821
  "ignore",
813
822
  fd,
@@ -821,6 +830,7 @@ async function startCollector({ root, serial, packageName, spawn, kill, alive =
821
830
  PLATFORM,
822
831
  "--root",
823
832
  root,
833
+ ...slot === "default" ? [] : ["--slot", slot],
824
834
  "--serial",
825
835
  serial,
826
836
  "--package",
@@ -840,9 +850,14 @@ async function startCollector({ root, serial, packageName, spawn, kill, alive =
840
850
  }
841
851
  //#endregion
842
852
  //#region src/commands/android/launch.ts
843
- async function verifyAndroidRun({ root, release, remoteRelease, remoteDevice, verifyReleaseLaunched, verifyLaunched, serial, androidPackage, variant, metroCheck, logsDir, launchedAt, metroPort, isExpo, physical, device, scheme, component = null, phase }) {
853
+ async function verifyAndroidRun({ root, slot, release, remoteRelease, remoteDevice, verifyReleaseLaunched, verifyLaunched, serial, androidPackage, variant, metroCheck, logsDir, launchedAt, metroPort, isExpo, physical, device, scheme, component = null, phase }) {
854
+ const runCommand = nativeRunCommand("android", slot, {
855
+ physical,
856
+ deviceId: serial
857
+ });
844
858
  const readNativeCrashes = () => remoteDevice ? [] : captureNativeCrashes({
845
859
  root,
860
+ slot,
846
861
  platform: "android",
847
862
  deviceId: serial,
848
863
  appId: androidPackage,
@@ -878,6 +893,7 @@ async function verifyAndroidRun({ root, release, remoteRelease, remoteDevice, ve
878
893
  const verification = metroCheck ? await verifyLaunched({
879
894
  timeoutMs,
880
895
  requireBundleResponse: true,
896
+ slot: launchSlotScope(root, slot),
881
897
  onReadinessPending: () => phase("readiness", "waiting for app readiness (up to 30s after bundle load)"),
882
898
  logsDir,
883
899
  since: launchedAt,
@@ -909,10 +925,10 @@ async function verifyAndroidRun({ root, release, remoteRelease, remoteDevice, ve
909
925
  phase("verify", chalk.red(`FATAL after ${formatDuration(verification.waitedMs ?? 0)}: ${reason}`));
910
926
  for (const line of launchErrorPreview(verification.errors ?? [], root)) phase("", chalk.red(line));
911
927
  if (verification.processAlive === false && !verification.errors?.some((record) => record.event === "native_crash")) phase("logs", "No attributable native crash report captured yet. Run `stim logs --errors` again for delayed reports, or `stim logs --source device` for available output.");
912
- if (nativeFatal || verification.processAlive === false) phase("remedy", chalk.yellow(`Fix the crash, then restart the app: \`adb -s ${deviceShellArg(serial)} shell am force-stop ${deviceShellArg(androidPackage)}\`, then run \`stim android\` again. A crashed Android process can remain alive behind the system crash dialog; Metro reload cannot recover it.`));
928
+ if (nativeFatal || verification.processAlive === false) phase("remedy", chalk.yellow(`Fix the crash, then restart the app: \`adb -s ${deviceShellArg(serial)} shell am force-stop ${deviceShellArg(androidPackage)}\`, then run \`${runCommand}\` again. A crashed Android process can remain alive behind the system crash dialog; Metro reload cannot recover it.`));
913
929
  else if (verification.processAlive === true && metroPort !== null) {
914
930
  const reloadRemedy = physical ? `run \`agent-device metro reload --metro-port ${metroPort}\`.` : "run `stim reload android`.";
915
- phase("remedy", chalk.yellow(`The native app is still running. ${deliveryFailed ? "Check the Metro logs and device connection, then" : "Fix the JavaScript or TypeScript error, then"} ${reloadRemedy} Do not run \`stim android\` unless native inputs changed or the app process exits.`));
931
+ phase("remedy", chalk.yellow(`The native app is still running. ${deliveryFailed ? "Check the Metro logs and device connection, then" : "Fix the JavaScript or TypeScript error, then"} ${reloadRemedy} Do not run \`${runCommand}\` unless native inputs changed or the app process exits.`));
916
932
  }
917
933
  return { state: LAUNCH_FATAL };
918
934
  }
@@ -923,7 +939,7 @@ async function verifyAndroidRun({ root, release, remoteRelease, remoteDevice, ve
923
939
  for (const line of report.lines) phase("launch", chalk.yellow(line));
924
940
  if (report.lines.length > 0 && verification.processAlive === true && metroPort !== null) {
925
941
  const reloadRemedy = physical ? `run \`agent-device metro reload --metro-port ${metroPort}\`.` : "run `stim reload android`.";
926
- phase("remedy", chalk.yellow(`The native app is still running. Fix the JavaScript or TypeScript error; Fast Refresh should apply the edit. If the error screen remains, ${reloadRemedy} Do not run \`stim android\` unless native inputs changed or the app process exits.`));
942
+ phase("remedy", chalk.yellow(`The native app is still running. Fix the JavaScript or TypeScript error; Fast Refresh should apply the edit. If the error screen remains, ${reloadRemedy} Do not run \`${runCommand}\` unless native inputs changed or the app process exits.`));
927
943
  }
928
944
  return {
929
945
  state: true,
@@ -952,7 +968,7 @@ async function verifyAndroidRun({ root, release, remoteRelease, remoteDevice, ve
952
968
  })) phase("", chalk.yellow(line));
953
969
  return { state: LAUNCH_UNVERIFIED };
954
970
  }
955
- async function resolveInstallSerial({ root, device, physical, remoteDevice, serial, resolveAvdSerial, waitForDeviceBoot, phase }) {
971
+ async function resolveInstallSerial({ root, slot, device, physical, remoteDevice, serial, resolveAvdSerial, waitForDeviceBoot, phase }) {
956
972
  if (physical || remoteDevice || !device.owned || !device.avdName) return serial;
957
973
  const resolved = resolveAvdSerial(device.avdName, { timeoutMs: 5e3 });
958
974
  if (!resolved.serial) throw new Error(`Could not verify a running owned AVD ${device.avdName} before installation.`);
@@ -965,16 +981,16 @@ async function resolveInstallSerial({ root, device, physical, remoteDevice, seri
965
981
  }
966
982
  const consolePort = Number(resolved.serial.replace(/^emulator-/, ""));
967
983
  withConfigLock(() => {
968
- const current = loadConfig()?.projects?.[root]?.platforms?.android;
984
+ const current = deviceSlotPlatforms(loadConfig()?.projects?.[root], slot)?.android;
969
985
  if (!current?.owned || current.avdName !== device.avdName) throw new Error("The owned emulator assignment changed before installation.");
970
986
  if (current.consolePort !== consolePort) setDevice(root, PLATFORM, {
971
987
  ...current,
972
988
  consolePort
973
- });
989
+ }, slot);
974
990
  });
975
991
  return resolved.serial;
976
992
  }
977
- async function finishAndroidRun({ lease, releaseLease, root, json, metroCheck, useBuildCache, variant, release, isExpo, metroPort, logsDir, emuLog, device, physical, remoteDevice, bootPromise, resolveAvdSerial, waitForDeviceBoot, bootDuration, apkPath, androidPackage: initialPackage, swapDir, record, waitedForBuild, ccache, uploadPending, providerUpload, providerName, remote, abandonedRemote: remoteWasAbandoned, started, startedAt, writer, phase, fail, readApkPackage, install, launch, launchRelease, resolveDevClientScheme, verifyLaunched, verifyReleaseLaunched, spawn, kill, pidAlive, verifyCollector, writeLaunch, writeState, now, out, emit, recordRun }) {
993
+ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCheck, useBuildCache, variant, release, isExpo, metroPort, logsDir, emuLog, device, physical, remoteDevice, bootPromise, resolveAvdSerial, waitForDeviceBoot, bootDuration, apkPath, androidPackage: initialPackage, swapDir, record, waitedForBuild, ccache, uploadPending, providerUpload, providerName, remote, abandonedRemote: remoteWasAbandoned, started, startedAt, writer, phase, fail, readApkPackage, install, launch, launchRelease, resolveDevClientScheme, verifyLaunched, verifyReleaseLaunched, spawn, kill, pidAlive, verifyCollector, writeLaunch, writeState, now, out, emit, recordRun }) {
978
994
  let androidPackage = initialPackage;
979
995
  let leaseWarned = false;
980
996
  const raiseLeaseFor = (boundMs, beforeInstall) => {
@@ -991,11 +1007,15 @@ async function finishAndroidRun({ lease, releaseLease, root, json, metroCheck, u
991
1007
  return null;
992
1008
  };
993
1009
  const booted = await bootPromise;
1010
+ const runCommand = nativeRunCommand("android", slot, {
1011
+ physical,
1012
+ deviceId: booted.serial
1013
+ });
994
1014
  if (booted.failed) {
995
1015
  const diag = noDeviceDiagnostic({
996
1016
  reason: booted.reason ?? "The emulator did not boot.",
997
1017
  logFile: emuLog,
998
- remedy: "Run `stim status` to see what Stim thinks it owns; re-running `stim android` creates a fresh owned AVD.",
1018
+ remedy: `Run \`stim status\` to see what Stim thinks it owns; re-running \`${runCommand}\` creates a fresh owned AVD.`,
999
1019
  localEmulator: !physical
1000
1020
  });
1001
1021
  return fail(NO_DEVICE, diag.message, diag.remedy, {
@@ -1007,6 +1027,7 @@ async function finishAndroidRun({ lease, releaseLease, root, json, metroCheck, u
1007
1027
  try {
1008
1028
  serial = await resolveInstallSerial({
1009
1029
  root,
1030
+ slot,
1010
1031
  device,
1011
1032
  physical,
1012
1033
  remoteDevice,
@@ -1016,7 +1037,7 @@ async function finishAndroidRun({ lease, releaseLease, root, json, metroCheck, u
1016
1037
  phase
1017
1038
  });
1018
1039
  } catch (error) {
1019
- return fail(NO_DEVICE, error instanceof Error ? error.message : String(error), "Run `stim status` to inspect the owned AVD, then retry `stim android`; the APK was not installed.");
1040
+ return fail(NO_DEVICE, error instanceof Error ? error.message : String(error), `Run \`stim status\` to inspect the owned AVD, then retry \`${runCommand}\`; the APK was not installed.`);
1020
1041
  }
1021
1042
  phase("device", physical ? `${device.deviceName || serial} (${serial}) connected, not owned by Stim` : `${device.avdName || serial} (${serial}) ${device.adopted || device.adoptionPending ? "adopted" : "booted"} ${bootDuration()}`);
1022
1043
  const packageFromApk = readApkPackage(apkPath);
@@ -1049,7 +1070,7 @@ async function finishAndroidRun({ lease, releaseLease, root, json, metroCheck, u
1049
1070
  if (adopting) try {
1050
1071
  withConfigLock(() => {
1051
1072
  const config = loadConfig();
1052
- const current = config?.projects?.[root]?.platforms?.android;
1073
+ const current = deviceSlotPlatforms(config?.projects?.[root], slot)?.android;
1053
1074
  if (!config || !current || current.avdName !== device.avdName) throw new Error("The adopted emulator assignment changed during installation.");
1054
1075
  delete current.adoptionPending;
1055
1076
  saveConfig(config);
@@ -1111,6 +1132,7 @@ async function finishAndroidRun({ lease, releaseLease, root, json, metroCheck, u
1111
1132
  if (launched.failed) {
1112
1133
  printNativeCrashReport({
1113
1134
  root,
1135
+ slot,
1114
1136
  platform: "android",
1115
1137
  deviceId: serial,
1116
1138
  appId: androidPackage,
@@ -1173,6 +1195,7 @@ async function finishAndroidRun({ lease, releaseLease, root, json, metroCheck, u
1173
1195
  if (physical) raiseLeaseFor(0, false);
1174
1196
  const collectorPid = await startCollector({
1175
1197
  root,
1198
+ slot,
1176
1199
  serial,
1177
1200
  packageName: androidPackage,
1178
1201
  spawn,
@@ -1186,6 +1209,7 @@ async function finishAndroidRun({ lease, releaseLease, root, json, metroCheck, u
1186
1209
  if (physical) raiseLeaseFor(release ? RELEASE_VERIFY_WAIT_MS : DEBUG_VERIFY_STEP_MS, false);
1187
1210
  const { state: launchState, warning: launchWarning } = await verifyAndroidRun({
1188
1211
  root,
1212
+ slot,
1189
1213
  release,
1190
1214
  remoteRelease,
1191
1215
  remoteDevice: Boolean(remoteDevice),
@@ -1219,6 +1243,7 @@ async function finishAndroidRun({ lease, releaseLease, root, json, metroCheck, u
1219
1243
  const leaseFacts = lease?.facts() ?? null;
1220
1244
  releaseLease();
1221
1245
  const facts = reportAndroidResult({
1246
+ slot,
1222
1247
  json,
1223
1248
  useBuildCache,
1224
1249
  variant,
@@ -1281,7 +1306,7 @@ function androidCommand(program) {
1281
1306
  registerAndroid(program);
1282
1307
  }
1283
1308
  function registerAndroid(program) {
1284
- program.command("android").description("Build (or install from the shared cache), install and launch this workspace's Android app on its owned emulator, wired to the reserved Metro port. Never starts the bundler -- run `stim start` first.").option("--json", "Emit the facts as a single JSON line on stdout; every other line goes to stderr").option("--no-metro-check", "Skip the reserved-port Metro health check (the app will load no bundle unless something else serves it)").option("--no-build-cache", "Build fresh, ignoring cached artifacts (local and the project's build-cache provider); the fresh build still replaces the cache entry").option("--variant <name>", "Gradle variant to assemble and install (e.g. productionDebug on a flavored project); overrides the android.variant setting. A variant ending in Release embeds the JS bundle and skips Metro entirely. Default: debug").option("--system-image <id>", "Android system image to create this workspace's owned AVD from, as the sdkmanager package id (e.g. \"system-images;android-36;google_apis;arm64-v8a\"). Overrides the android.systemImage setting for this invocation. An unknown id refuses with STIM_BAD_ARG and prints the installed images.").option("--device [serial]", "Install and launch on a connected physical device instead of this workspace's owned emulator. With no serial, the first connected device this workspace can lease is used. Stim never creates, boots, or deletes a physical device.").option("--remote <backend>", "Install and launch on a remote device with proxy or EAS. The build still happens here.", (value) => {
1309
+ program.command("android").description("Build (or install from the shared cache), install and launch this workspace's Android app on its owned emulator, wired to the reserved Metro port. Never starts the bundler -- run `stim start` first.").option("--eas-profile <name>", "Download a matching EAS development build; on a miss, print the build command without running it").option("--slot <name>", "Reusable device slot within this workspace (default: default)", validateDeviceSlot).option("--json", "Emit the facts as a single JSON line on stdout; every other line goes to stderr").option("--no-metro-check", "Skip the reserved-port Metro health check (the app will load no bundle unless something else serves it)").option("--no-build-cache", "Build fresh, ignoring cached artifacts (local and the project's build-cache provider); the fresh build still replaces the cache entry").option("--variant <name>", "Gradle variant to assemble and install (e.g. productionDebug on a flavored project); overrides the android.variant setting. A variant ending in Release embeds the JS bundle and skips Metro entirely. Default: debug").option("--system-image <id>", "Android system image to create this workspace's owned AVD from, as the sdkmanager package id (e.g. \"system-images;android-36;google_apis;arm64-v8a\"). Overrides the android.systemImage setting for this invocation. An unknown id refuses with STIM_BAD_ARG and prints the installed images.").option("--device [serial]", "Install and launch on a connected physical device instead of this workspace's owned emulator. With no serial, the first connected device this workspace can lease is used. Stim never creates, boots, or deletes a physical device.").option("--remote <backend>", "Install and launch on a remote device with proxy or EAS. Builds are local unless --eas-profile selects an existing EAS build.", (value) => {
1285
1310
  if (REMOTE_DEVICE_BACKENDS.includes(value)) return value;
1286
1311
  throw new InvalidArgumentError(`expected one of: ${REMOTE_DEVICE_BACKENDS.join(", ")}`);
1287
1312
  }).option("--wait <seconds>", "How long to wait for another workspace to release the device it leases, before refusing with STIM_DEVICE_BUSY (default 60, 0 refuses at once). Only with --device.").option("--no-wait", "Install on a device another workspace leases instead of waiting: this run takes no lease and, when both workspaces build the same app id, the install terminates the holder's running app. Only with --device.").action(async (opts) => {
@@ -1291,8 +1316,10 @@ function registerAndroid(program) {
1291
1316
  process.exit(1);
1292
1317
  return;
1293
1318
  }
1294
- if (!(await runAndroid({
1319
+ if (!(await withWorkspaceProcessLock(workspaceDir(root), "native-run", () => runAndroid({
1295
1320
  root,
1321
+ slot: opts.slot,
1322
+ easProfile: opts.easProfile,
1296
1323
  json: Boolean(opts.json),
1297
1324
  metroCheck: opts.metroCheck !== false,
1298
1325
  useBuildCache: opts.buildCache !== false,
@@ -1302,10 +1329,13 @@ function registerAndroid(program) {
1302
1329
  device: opts.device ?? null,
1303
1330
  wait: opts.wait,
1304
1331
  waitConflict: waitFlagConflict(process.argv)
1332
+ }), {
1333
+ external: true,
1334
+ waitMs: 18e5
1305
1335
  })).ok) process.exit(1);
1306
1336
  });
1307
1337
  }
1308
- function resolveRunAndroidOptions({ root, json = false, remoteDevice: commandRemoteBackend = null, resolveSettingsFor = resolveSettings, resolveRemoteDeviceContext = resolveRemoteContext, remoteDeviceDeps: makeRemoteDeviceDeps = remoteAndroidDeps, resolveEasBin = resolveEasCliBin, ensureMetroReachable: ensureRemoteMetro = ensureMetroReachable, ensureRemoteBootOwned: ensureRemoteOwned = ensureRemoteBootOwned, detectRemoteProviders = detectProviders, metroCheck = true, useBuildCache = true, variant: variantFlag = null, systemImage: systemImageFlag = null, device: deviceFlag = null, wait: waitFlag = void 0, waitConflict = false, acquireLease = acquireRunLease, makeRunLease = runLease, selectPool = selectFromPool, onLeaseSignal = releaseLeaseOnSignal, listDevices = listAdbDevices, deviceModel = physicalDeviceModel, deviceAbi = androidDeviceAbi, isEmulatorDevice = probeEmulatorSerial, readApkPackage = (apkPath) => apkPackage(dumpApkManifest(apkPath)), getLimits = getConcurrencyLimits, checkCapacity = checkDeviceCapacity, acquireSlot = acquireBuildSlot, releaseSlot = releaseBuildSlot, ensureDevice = ensureOwnedDevice, listSystemImages = listInstalledSystemImages, ensureDeviceBooted = ensureBooted, resolveAvdSerial = resolveOwnedAvdSerial, waitForDeviceBoot = waitForBoot, resolveMetro = resolveProjectMetro, warmMetro: prewarmMetro = warmMetro, resolveMetroRetrying = resolveMetroWithRetry, readState = readWorkspaceState, pidAlive = pidExists, verifyCollector = verifyCollectorOwnership, verifyLaunched = verifyLaunch, ensureStorage = ensureWorkspaceStorageSafely, fingerprint = fingerprintProject, untracked = untrackedNativeFiles, resolveCached = resolveBuild, storeCached = storeBuild, storedAssets = storedAssetManifest, captureAssets = captureAssetManifest, acquireLock = acquireBuildLock, releaseLock = releaseBuildLock, waitForBuild: waitForBuild$1 = waitForBuild, loadProvider = loadProjectProvider, easAuth = checkEasAuth, resolveRemoteBuild = resolveRemote, uploadRemoteBuild = uploadRemote, resolveCacheProvider = resolveCacheProviderConfig, loadCacheProviderModule = loadCacheProvider, needsPrebuildFor = needsPrebuild, prebuild = runPrebuild, build = buildAndroid, ccacheFor = resolveCcache, install = installAndroidApp, launch = launchAndroidApp, launchRelease = launchAndroidReleaseApp, verifyReleaseLaunched = verifyAndroidReleaseLaunch, swapApk = swapApkBundle, resolveDevClientScheme = androidDevClientScheme, spawn = (cmd, args, opts) => getExecutor().spawn(cmd, args, opts), kill = (pid, signal) => process.kill(pid, signal), createWriter = createNdjsonWriter, writeLaunch = writeWorkspaceLaunch, writeState = writeWorkspaceState, recordStats = recordRunStats, readEstimates = readRunEstimates, now = Date.now, out = (line) => console.error(line), emit = (line) => console.log(line) } = {}) {
1338
+ function resolveRunAndroidOptions({ root, json = false, remoteDevice: commandRemoteBackend = null, resolveSettingsFor = resolveSettings, resolveRemoteDeviceContext = resolveRemoteContext, remoteDeviceDeps: makeRemoteDeviceDeps = remoteAndroidDeps, resolveEasBin = resolveEasCliBin, ensureMetroReachable: ensureRemoteMetro = ensureMetroReachable, ensureRemoteBootOwned: ensureRemoteOwned = ensureRemoteBootOwned, detectRemoteProviders = detectProviders, metroCheck = true, useBuildCache = true, easProfile, resolveEasDevelopmentBuild: resolveEasBuild = resolveEasDevelopmentBuild, variant: variantFlag = null, systemImage: systemImageFlag = null, device: deviceFlag = null, wait: waitFlag = void 0, waitConflict = false, acquireLease = acquireRunLease, makeRunLease = runLease, selectPool = selectFromPool, onLeaseSignal = releaseLeaseOnSignal, listDevices = listAdbDevices, deviceModel = physicalDeviceModel, deviceAbi = androidDeviceAbi, isEmulatorDevice = probeEmulatorSerial, readApkPackage = (apkPath) => apkPackage(dumpApkManifest(apkPath)), getLimits = getConcurrencyLimits, checkCapacity = checkDeviceCapacity, acquireSlot = acquireBuildSlot, releaseSlot = releaseBuildSlot, ensureDevice = ensureOwnedDevice, listSystemImages = listInstalledSystemImages, ensureDeviceBooted = ensureBooted, resolveAvdSerial = resolveOwnedAvdSerial, waitForDeviceBoot = waitForBoot, resolveMetro = resolveProjectMetro, warmMetro: prewarmMetro = warmMetro, resolveMetroRetrying = resolveMetroWithRetry, readState = readWorkspaceState, pidAlive = pidExists, verifyCollector = verifyCollectorOwnership, verifyLaunched = verifyLaunch, ensureStorage = ensureWorkspaceStorageSafely, fingerprint = fingerprintProject, untracked = untrackedNativeFiles, resolveCached = resolveBuild, storeCached = storeBuild, storedAssets = storedAssetManifest, captureAssets = captureAssetManifest, acquireLock = acquireBuildLock, releaseLock = releaseBuildLock, waitForBuild: waitForBuild$1 = waitForBuild, loadProvider = loadProjectProvider, easAuth = checkEasAuth, resolveRemoteBuild = resolveRemote, uploadRemoteBuild = uploadRemote, resolveCacheProvider = resolveCacheProviderConfig, loadCacheProviderModule = loadCacheProvider, needsPrebuildFor = needsPrebuild, prebuild = runPrebuild, build = buildAndroid, ccacheFor = resolveCcache, install = installAndroidApp, launch = launchAndroidApp, launchRelease = launchAndroidReleaseApp, verifyReleaseLaunched = verifyAndroidReleaseLaunch, swapApk = swapApkBundle, resolveDevClientScheme = androidDevClientScheme, spawn = (cmd, args, opts) => getExecutor().spawn(cmd, args, opts), kill = (pid, signal) => process.kill(pid, signal), createWriter = createNdjsonWriter, writeLaunch = writeWorkspaceLaunch, writeState = writeWorkspaceState, recordStats = recordRunStats, readEstimates = readRunEstimates, now = Date.now, out = (line) => console.error(line), emit = (line) => console.log(line) } = {}) {
1309
1339
  return {
1310
1340
  root,
1311
1341
  json,
@@ -1319,6 +1349,8 @@ function resolveRunAndroidOptions({ root, json = false, remoteDevice: commandRem
1319
1349
  detectRemoteProviders,
1320
1350
  metroCheck,
1321
1351
  useBuildCache,
1352
+ easProfile,
1353
+ resolveEasBuild,
1322
1354
  variantFlag,
1323
1355
  systemImageFlag,
1324
1356
  deviceFlag,
@@ -1387,8 +1419,38 @@ function resolveRunAndroidOptions({ root, json = false, remoteDevice: commandRem
1387
1419
  emit
1388
1420
  };
1389
1421
  }
1422
+ function androidSlotOptions(options) {
1423
+ const base = resolveRunAndroidOptions(options);
1424
+ const slot = validateDeviceSlot(options.slot);
1425
+ if (slot === "default") return base;
1426
+ return {
1427
+ ...base,
1428
+ ensureDevice: (args) => base.ensureDevice({
1429
+ ...args,
1430
+ slot
1431
+ }),
1432
+ checkCapacity: (args) => base.checkCapacity({
1433
+ ...args,
1434
+ slot
1435
+ }),
1436
+ selectPool: (args) => base.selectPool({
1437
+ ...args,
1438
+ slot
1439
+ }),
1440
+ acquireLease: (args) => base.acquireLease({
1441
+ ...args,
1442
+ slot
1443
+ }),
1444
+ makeRunLease: (args) => base.makeRunLease({
1445
+ ...args,
1446
+ slot
1447
+ }),
1448
+ writeLaunch: ((projectRoot, platform, record) => base.writeLaunch(projectRoot, platform, record, slot))
1449
+ };
1450
+ }
1390
1451
  async function runAndroid(options = {}) {
1391
- let { root, json, commandRemoteBackend, resolveSettingsFor, resolveRemoteDeviceContext, makeRemoteDeviceDeps, resolveEasBin, ensureRemoteMetro, ensureRemoteOwned, detectRemoteProviders, metroCheck, useBuildCache: requestedBuildCache, variantFlag, systemImageFlag, deviceFlag, waitFlag, waitConflict, acquireLease, makeRunLease, selectPool, onLeaseSignal, listDevices, deviceModel, deviceAbi, isEmulatorDevice, readApkPackage, getLimits, checkCapacity, acquireSlot, releaseSlot, ensureDevice, listSystemImages, ensureDeviceBooted, resolveAvdSerial, waitForDeviceBoot, resolveMetro, prewarmMetro, resolveMetroRetrying, readState, pidAlive, verifyCollector, verifyLaunched, ensureStorage, fingerprint, untracked, resolveCached, storeCached, storedAssets, captureAssets, acquireLock, releaseLock, waitForBuild, loadProvider, easAuth, resolveRemoteBuild, uploadRemoteBuild, resolveCacheProvider, loadCacheProviderModule, needsPrebuildFor, prebuild, build, ccacheFor, install, launch, launchRelease, verifyReleaseLaunched, swapApk, resolveDevClientScheme, spawn, kill, createWriter, writeLaunch, writeState, recordStats, readEstimates, now, out, emit } = resolveRunAndroidOptions(options);
1452
+ let { root, json, commandRemoteBackend, resolveSettingsFor, resolveRemoteDeviceContext, makeRemoteDeviceDeps, resolveEasBin, ensureRemoteMetro, ensureRemoteOwned, detectRemoteProviders, metroCheck, useBuildCache: requestedBuildCache, easProfile, resolveEasBuild, variantFlag, systemImageFlag, deviceFlag, waitFlag, waitConflict, acquireLease, makeRunLease, selectPool, onLeaseSignal, listDevices, deviceModel, deviceAbi, isEmulatorDevice, readApkPackage, getLimits, checkCapacity, acquireSlot, releaseSlot, ensureDevice, listSystemImages, ensureDeviceBooted, resolveAvdSerial, waitForDeviceBoot, resolveMetro, prewarmMetro, resolveMetroRetrying, readState, pidAlive, verifyCollector, verifyLaunched, ensureStorage, fingerprint, untracked, resolveCached, storeCached, storedAssets, captureAssets, acquireLock, releaseLock, waitForBuild, loadProvider, easAuth, resolveRemoteBuild, uploadRemoteBuild, resolveCacheProvider, loadCacheProviderModule, needsPrebuildFor, prebuild, build, ccacheFor, install, launch, launchRelease, verifyReleaseLaunched, swapApk, resolveDevClientScheme, spawn, kill, createWriter, writeLaunch, writeState, recordStats, readEstimates, now, out, emit } = androidSlotOptions(options);
1453
+ const slot = validateDeviceSlot(options.slot);
1392
1454
  const started = now();
1393
1455
  const startedAt = new Date(started).toISOString();
1394
1456
  const projectProblem = appProjectProblem(root);
@@ -1433,8 +1495,11 @@ async function runAndroid(options = {}) {
1433
1495
  };
1434
1496
  }
1435
1497
  const logsDir = workspaceLogsDir(root);
1436
- const buildLog = join(logsDir, "build-android.ndjson");
1437
- const writer = createWriter(buildLog, { truncate: true });
1498
+ const buildLog = join(logsDir, `build-${deviceSlotKey("android", slot)}.ndjson`);
1499
+ const writer = createWriter(buildLog, {
1500
+ truncate: true,
1501
+ fields: { slot }
1502
+ });
1438
1503
  const record = {
1439
1504
  fingerprint: null,
1440
1505
  cacheKey: null,
@@ -1559,7 +1624,7 @@ async function runAndroid(options = {}) {
1559
1624
  const remoteSettingError = remoteDeviceSettingError(settings);
1560
1625
  if (remoteSettingError) return fail("STIM_BAD_ARG", remoteSettingError, `Set ios.remote and android.remote to one of: ${REMOTE_DEVICE_BACKENDS.join(", ")}.`);
1561
1626
  const systemImage = resolveSystemImage(systemImageFlag, settings);
1562
- const variant = resolveVariant(variantFlag, settings);
1627
+ const variant = easProfile !== void 0 ? "debug" : resolveVariant(variantFlag, settings);
1563
1628
  const flavorRefusal = productFlavorRefusal({
1564
1629
  flavors: readProductFlavors(root),
1565
1630
  variant
@@ -1569,7 +1634,7 @@ async function runAndroid(options = {}) {
1569
1634
  const cachePolicy = artifactCachePolicy(optimizations, requestedBuildCache, release);
1570
1635
  const useBuildCache = cachePolicy.read;
1571
1636
  const isExpo = detectIsExpo(root);
1572
- const physical = deviceFlag !== null && deviceFlag !== void 0 && deviceFlag !== false;
1637
+ const physical = isPhysicalDeviceRequest(deviceFlag);
1573
1638
  if (physical && deviceFlag === "") return fail("STIM_BAD_ARG", "--device was given an empty serial.", "Pass `--device` on its own to take the first connected device this workspace can lease, or `--device <serial>` to name one.");
1574
1639
  if (physical && commandRemoteBackend) return fail("STIM_BAD_ARG", "--device installs on a device connected to this machine, and --remote installs on a remote one.", "Pass only one of --device and --remote.");
1575
1640
  const noWait = waitFlag === false;
@@ -1581,6 +1646,7 @@ async function runAndroid(options = {}) {
1581
1646
  const waitSeconds = waitParsed.seconds;
1582
1647
  const remoteBackend = physical ? null : commandRemoteBackend ?? remoteAndroidSetting(settings);
1583
1648
  const imageRefusal = systemImageRefusal({
1649
+ slot,
1584
1650
  flag: systemImageFlag,
1585
1651
  resolved: systemImage,
1586
1652
  physical,
@@ -1588,6 +1654,17 @@ async function runAndroid(options = {}) {
1588
1654
  listImages: listSystemImages
1589
1655
  });
1590
1656
  if (imageRefusal) return fail(imageRefusal.code, imageRefusal.message, imageRefusal.remedy);
1657
+ const easBuild = await resolveEasBuild({
1658
+ root,
1659
+ platform: PLATFORM,
1660
+ profile: easProfile,
1661
+ note: out,
1662
+ isExpo,
1663
+ physical,
1664
+ selectors: [variantFlag],
1665
+ buildCache: requestedBuildCache
1666
+ });
1667
+ if (isEasBuildFailure(easBuild)) return fail(easBuild.code, easBuild.message, easBuild.remedy);
1591
1668
  const requestedSerial = typeof deviceFlag === "string" ? deviceFlag : null;
1592
1669
  let androidPackage = detectAndroidPackage(root);
1593
1670
  record.bundleId = androidPackage;
@@ -1799,6 +1876,18 @@ async function runAndroid(options = {}) {
1799
1876
  let storeSources = [];
1800
1877
  let apkPath = null;
1801
1878
  async function resolveInitialFingerprint() {
1879
+ if (easBuild?.ok) {
1880
+ hash = storeHash = easBuild.fingerprint;
1881
+ cacheKey = storeKey = easBuild.cacheKey;
1882
+ apkPath = easBuild.path;
1883
+ record.fingerprint = hash;
1884
+ record.cacheKey = cacheKey;
1885
+ record.cacheHit = easBuild.cacheHit;
1886
+ record.cacheSkipped = !useBuildCache;
1887
+ providerName = "eas";
1888
+ stats.setCacheKey(cacheKey);
1889
+ return true;
1890
+ }
1802
1891
  const fingerprintTimer = stepTimer(now);
1803
1892
  try {
1804
1893
  const computed = await fingerprint(root, { platform: PLATFORM });
@@ -1930,7 +2019,7 @@ async function runAndroid(options = {}) {
1930
2019
  } else phase("cache", `remote miss (${remote.name}) ${remoteTimer()}`);
1931
2020
  }
1932
2021
  }
1933
- await resolveRemoteArtifact();
2022
+ if (!easBuild) await resolveRemoteArtifact();
1934
2023
  let waitedForBuild = null;
1935
2024
  let releasedWait = null;
1936
2025
  async function waitForSharedBuild() {
@@ -2014,7 +2103,7 @@ async function runAndroid(options = {}) {
2014
2103
  }
2015
2104
  return true;
2016
2105
  }
2017
- if (!await waitForSharedBuild()) return phaseFailure;
2106
+ if (!easBuild && !await waitForSharedBuild()) return phaseFailure;
2018
2107
  let swapDir = null;
2019
2108
  let swapFellBack = false;
2020
2109
  const installableCachedApk = async (key, cachedPath) => {
@@ -2214,8 +2303,10 @@ async function runAndroid(options = {}) {
2214
2303
  }
2215
2304
  return true;
2216
2305
  }
2217
- await prepareCachedArtifact();
2218
- if (!await buildArtifact()) return phaseFailure;
2306
+ if (!easBuild) {
2307
+ await prepareCachedArtifact();
2308
+ if (!await buildArtifact()) return phaseFailure;
2309
+ }
2219
2310
  record.appPath = apkPath;
2220
2311
  let leaseHandle = null;
2221
2312
  let stopLeaseSignals = null;
@@ -2260,6 +2351,7 @@ async function runAndroid(options = {}) {
2260
2351
  }
2261
2352
  try {
2262
2353
  return await finishAndroidRun({
2354
+ slot,
2263
2355
  lease: leaseHandle,
2264
2356
  releaseLease,
2265
2357
  root,
@@ -1,11 +1,11 @@
1
1
  import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { k as withDirLock, u as getConfigDir } from "./config-CMtIaTk9.mjs";
3
- import { r as register } from "./cache-manifest-Cu4_Znw9.mjs";
4
- import { D as workspaceLogsDir, x as sharedCcache, y as ensureWorkspaceStorage } from "./state-CZ-ER-8d.mjs";
2
+ import { k as withDirLock, u as getConfigDir } from "./config-CcefMY2Q.mjs";
3
+ import { r as register } from "./cache-manifest-CXR-Y8pH.mjs";
4
+ import { D as workspaceLogsDir, x as sharedCcache, y as ensureWorkspaceStorage } from "./state-B3XA2wp2.mjs";
5
5
  import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-C7IlIzd7.mjs";
6
6
  import { l as phaseLine } from "./command-output-8wHMEde0.mjs";
7
- import { n as androidHome } from "./android-K63_0UfF.mjs";
8
- import { f as startBuildHeartbeat, r as HEARTBEAT_INTERVAL_MS } from "./xcode-DeKCribl.mjs";
7
+ import { n as androidHome } from "./android-BqVIJoG7.mjs";
8
+ import { f as startBuildHeartbeat, r as HEARTBEAT_INTERVAL_MS } from "./xcode-D7sAp-Zy.mjs";
9
9
  import { accessSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
10
10
  import { createHash, randomUUID } from "node:crypto";
11
11
  import { dirname, isAbsolute, join, relative } from "node:path";
@@ -1,8 +1,8 @@
1
1
  import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { o as parseNdjsonText } from "./ndjson-DcAtEx_K.mjs";
2
+ import { o as parseNdjsonText } from "./ndjson-BZwI1NBv.mjs";
3
3
  import { t as DEV_MENU_LAUNCH_ARGS } from "./ios-device-D3pzTKQE.mjs";
4
4
  import { s as isAppLaunchError } from "./command-output-8wHMEde0.mjs";
5
- import { l as iosSimulatorFailureAdvice, p as listUserApps, w as uninstallIosApp } from "./ios-D1h1Di39.mjs";
5
+ import { l as iosSimulatorFailureAdvice, p as listUserApps, w as uninstallIosApp } from "./ios-Deh5js5V.mjs";
6
6
  import { closeSync, lstatSync, openSync, readFileSync, readSync, readdirSync } from "node:fs";
7
7
  import { createHash } from "node:crypto";
8
8
  import { join } from "node:path";
@@ -900,7 +900,7 @@ function nativeLaunchFailure({ readCrashes, since, platform, processAlive, mode,
900
900
  waitedMs
901
901
  } : null;
902
902
  }
903
- async function verifyLaunch({ logsDir, since, metroPort = null, platform = null, mode = null, requireBundleResponse, timeoutMs = VERIFY_TIMEOUT_MS, stabilityMs = STABILITY_WINDOW_MS, pollMs = VERIFY_POLL_MS, readRecords = null, readDeviceRecords = null, readClientRecords = null, readNativeCrashes = null, processAlive = null, onReadinessPending = null, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)) } = {}) {
903
+ async function verifyLaunch({ slot, logsDir, since, metroPort = null, platform = null, mode = null, requireBundleResponse, timeoutMs = VERIFY_TIMEOUT_MS, stabilityMs = STABILITY_WINDOW_MS, pollMs = VERIFY_POLL_MS, readRecords = null, readDeviceRecords = null, readClientRecords = null, readNativeCrashes = null, processAlive = null, onReadinessPending = null, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)) } = {}) {
904
904
  const read = readRecords || (() => readMetroRecords(logsDir));
905
905
  const readDevice = readDeviceRecords || (() => readNdjson(logsDir, "device.ndjson"));
906
906
  const readClient = readClientRecords || (() => readNdjson(logsDir, "client.ndjson"));
@@ -915,10 +915,10 @@ async function verifyLaunch({ logsDir, since, metroPort = null, platform = null,
915
915
  let runtimeStartedAt = null;
916
916
  let nextCrashCheck = startedAt + 1e3;
917
917
  while (true) {
918
- const metroRecords = read().filter((record) => after(record, since));
918
+ const metroRecords = read().filter((record) => after(record, since) && (slot === void 0 || record.slot === slot));
919
919
  const bundleRecords = metroRecords.filter((record) => !requireBundleResponse || String(record.event).startsWith("bundle_response_"));
920
- const deviceRecords = readDevice().filter((record) => after(record, since));
921
- const clientRecords = readClient().filter((record) => after(record, since));
920
+ const deviceRecords = readDevice().filter((record) => after(record, since) && (slot === void 0 || (record.slot ?? "default") === slot));
921
+ const clientRecords = readClient().filter((record) => after(record, since) && (slot === void 0 || record.slot === slot));
922
922
  if (now() >= nextCrashCheck) {
923
923
  nextCrashCheck = now() + 2e3;
924
924
  const crash = nativeLaunchFailure({
@@ -1,6 +1,6 @@
1
- import { u as getConfigDir } from "./config-CMtIaTk9.mjs";
1
+ import { u as getConfigDir } from "./config-CcefMY2Q.mjs";
2
2
  import { a as formatElapsed } from "./command-output-8wHMEde0.mjs";
3
- import { d as readClaimSet, f as releaseClaim, h as tryAcquireClaim, s as isClaimRefusal } from "./ownership-claim-7zRxxTfZ.mjs";
3
+ import { d as readClaimSet, f as releaseClaim, h as tryAcquireClaim, s as isClaimRefusal } from "./ownership-claim-Bf2CAfw3.mjs";
4
4
  import { readdirSync, statSync } from "node:fs";
5
5
  import { join } from "node:path";
6
6
  //#region src/engine/build-slots.ts
@@ -1,4 +1,4 @@
1
- import { u as getConfigDir } from "./config-CMtIaTk9.mjs";
1
+ import { u as getConfigDir } from "./config-CcefMY2Q.mjs";
2
2
  import { existsSync } from "fs";
3
3
  import { homedir } from "os";
4
4
  import { join, resolve } from "path";
@@ -1,2 +1,2 @@
1
- import { a as unregister, i as registeredCaches, n as readManifest, r as register, t as manifestPath } from "./cache-manifest-Cu4_Znw9.mjs";
1
+ import { a as unregister, i as registeredCaches, n as readManifest, r as register, t as manifestPath } from "./cache-manifest-CXR-Y8pH.mjs";
2
2
  export { manifestPath, readManifest, register, registeredCaches, unregister };