stim 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +37 -0
  2. package/dist/{android-CC5bUer7.mjs → android-BW8i_YxU.mjs} +1 -1
  3. package/dist/android-BqVIJoG7.mjs +1368 -0
  4. package/dist/{android-DnZlT3aU.mjs → android-CqcW1hrP.mjs} +205 -58
  5. package/dist/{android-cas-DGlTStWu.mjs → android-cas-BR5YJBYv.mjs} +6 -6
  6. package/dist/android-cas-compiler.mjs +1 -1
  7. package/dist/{app-install-oZIQRbdD.mjs → app-install-DIp9riNx.mjs} +29 -21
  8. package/dist/{build-slots-CgacA9zQ.mjs → build-slots-C3-R4XQr.mjs} +3 -3
  9. package/dist/{cache-manifest-oeH18ERr.mjs → cache-manifest-CXR-Y8pH.mjs} +1 -1
  10. package/dist/cache-manifest.mjs +1 -1
  11. package/dist/cli.mjs +13 -13
  12. package/dist/collector-run.d.mts +4 -1
  13. package/dist/collector-run.mjs +36 -11
  14. package/dist/{config-D7uu8Gbq.mjs → config-CcefMY2Q.mjs} +94 -13
  15. package/dist/{deps-64wVnpZc.mjs → deps-Spz8ozgo.mjs} +2 -2
  16. package/dist/{dev-client-Bvky1hya.mjs → dev-client-BjisHDB5.mjs} +250 -10
  17. package/dist/{device-DJKv5Qej.mjs → device-BkiW-gkk.mjs} +19 -11
  18. package/dist/{device-lease-D-G3SgVe.mjs → device-lease-BJYybF-2.mjs} +107 -83
  19. package/dist/{device-pool-BlHXnYYs.mjs → device-pool-CPbLGfrB.mjs} +19 -13
  20. package/dist/{device-remote-CCgkG82J.mjs → device-remote-AkYCKS8U.mjs} +8 -8
  21. package/dist/{doctor-DlVxTtMq.mjs → doctor-Bmv86UUW.mjs} +18 -26
  22. package/dist/{doctor-BRF9aAJ3.mjs → doctor-fAMXC4vl.mjs} +10 -10
  23. package/dist/{error-diagnostics-B5IJwv3-.mjs → error-diagnostics-DHC31rXA.mjs} +65 -55
  24. package/dist/{exec-bsN9MJXb.mjs → exec-CyylIdq9.mjs} +4 -2
  25. package/dist/{gc-DhXdgSdt.mjs → gc-GGu-8ULN.mjs} +90 -67
  26. package/dist/{guide-DrhrDBo5.mjs → guide-92CPA21s.mjs} +414 -47
  27. package/dist/{ios-CK1Vrk6Q.mjs → ios-BSXFeO5H.mjs} +200 -59
  28. package/dist/{ios-BZxKPuqP.mjs → ios-Deh5js5V.mjs} +147 -40
  29. package/dist/{ios-device-CdSm0GDw.mjs → ios-device-CWOM_S44.mjs} +2 -2
  30. package/dist/{ios-device-CYbuRNXR.mjs → ios-device-D3pzTKQE.mjs} +1 -1
  31. package/dist/{logs-Dxoy16Qv.mjs → logs-DPNF4C9s.mjs} +9 -6
  32. package/dist/{logs-query-oqn47Ffx.mjs → logs-query-R-Cqc6Qv.mjs} +22 -5
  33. package/dist/{metro-B01H3EuD.mjs → metro-CORqXyTL.mjs} +3 -3
  34. package/dist/{ndjson-DcAtEx_K.mjs → ndjson-BZwI1NBv.mjs} +5 -2
  35. package/dist/{ownership-D8HmSv2E.mjs → ownership-8O073GIC.mjs} +3 -3
  36. package/dist/{ownership-B9QIdQOw.mjs → ownership-BpCKiRlQ.mjs} +4 -4
  37. package/dist/{ownership-claim-CeJLEpjD.mjs → ownership-claim-Bf2CAfw3.mjs} +4 -3
  38. package/dist/{project-COj6nzzh.mjs → project-fKmuIaMI.mjs} +2 -2
  39. package/dist/{reclaim-BPvN2HlP.mjs → reclaim-iW9Cad7k.mjs} +80 -73
  40. package/dist/{reload-DYZEBbZy.mjs → reload-DazHDSFA.mjs} +32 -24
  41. package/dist/{server-bare-BYHqEeyw.mjs → server-bare-DEzPZLvf.mjs} +7 -2
  42. package/dist/server-expo-44em5Pmg.mjs +2 -0
  43. package/dist/{server-expo-BEMr_m6p.mjs → server-expo-DizT4OMk.mjs} +5 -5
  44. package/dist/{settings-BSwrY4FZ.mjs → settings-DEJf6_kJ.mjs} +26 -5
  45. package/dist/slot-launch-cci0xvmC.mjs +19 -0
  46. package/dist/{start-ByCylYtB.mjs → start-mObjQH2W.mjs} +12 -12
  47. package/dist/{state-IYEY-zi8.mjs → state-B3XA2wp2.mjs} +9 -9
  48. package/dist/{stats-wqxPm7Tv.mjs → stats-BR-kwj0B.mjs} +1 -1
  49. package/dist/{stats-DZ4Q5J-7.mjs → stats-CaaQr4yN.mjs} +3 -3
  50. package/dist/{status-Dia0XTqG.mjs → status-BxQNqu9t.mjs} +86 -25
  51. package/dist/{stop-D6o5zAdm.mjs → stop-CrHGcjrV.mjs} +2 -2
  52. package/dist/{stop-BApyaF21.mjs → stop-DimGXEKY.mjs} +93 -42
  53. package/dist/supervisor-run.d.mts +1 -2
  54. package/dist/supervisor-run.mjs +5 -5
  55. package/dist/{remote-cache-DOi5-zLj.mjs → teardown-ClFp1Aax.mjs} +889 -830
  56. package/dist/{workspace-process-lock-CxOO9-pf.mjs → workspace-process-lock-3NcQnZ0X.mjs} +1 -1
  57. package/dist/{worktree-DxEmUI_n.mjs → worktree-DxTgN1CJ.mjs} +16 -16
  58. package/dist/{worktree-Dx3ZC5lS.mjs → worktree-Eq6TLH8N.mjs} +7 -3
  59. package/dist/{xcode-CDB6fytw.mjs → xcode-D7sAp-Zy.mjs} +256 -103
  60. package/package.json +5 -5
  61. package/shim/bundle-response.cjs +5 -0
  62. package/dist/android-xmyGBNy5.mjs +0 -665
  63. package/dist/build-lock-vE45ZzJZ.mjs +0 -645
  64. package/dist/server-expo-rb0k-0w7.mjs +0 -2
@@ -1,34 +1,35 @@
1
- import { t as getExecutor } from "./exec-bsN9MJXb.mjs";
2
- import { C as withConfigLock, S as upsertProject, f as getProject, g as loadConfig, l as getConcurrencyLimits, y as saveConfig } from "./config-D7uu8Gbq.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-IYEY-zi8.mjs";
5
- import { C as removeTemporaryEntry, S as makeTemporaryDirectory, g as repoRoot, s as gitCommonDir } from "./worktree-Dx3ZC5lS.mjs";
6
- import { a as findProjectRoot, c as projectShortcut, i as detectIsExpo, n as detectAndroidPackage, r as detectBundleId, t as appProjectProblem } from "./project-COj6nzzh.mjs";
1
+ import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
+ import { 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-B01H3EuD.mjs";
9
- import { n as verifyCollectorOwnership, r as readCollectors } from "./ownership-D8HmSv2E.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-DOi5-zLj.mjs";
11
- import { A as settingFile, B as compilerCacheFallbackMessage, E as remoteDeviceSettingError, F as unknownSettingKeys, H as resolveOptimizations, L as detectProviders, M as settingShapeErrors, O as resolveCacheProviderConfig, P as tunnelModeSetting, T as remoteAndroidSetting, V as optimizationBuildProfile, a as SETTING_SHAPE_REMEDY, d as cacheProviderSettingError, k as resolveSettings, r as REMOTE_DEVICE_BACKENDS, s as androidAvdConfigSettingError, u as androidDataPartitionSizeGbSettingError, w as publicUrlSetting, z as artifactCachePolicy } from "./settings-BSwrY4FZ.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, 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-oZIQRbdD.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-vE45ZzJZ.mjs";
15
- import { r as claimFailure } from "./ownership-claim-CeJLEpjD.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-BlHXnYYs.mjs";
17
- import { r as releaseBuildSlot, t as acquireBuildSlot } from "./build-slots-CgacA9zQ.mjs";
18
- import { c as ensureWorkspaceStorageSafely, d as noMetroMessage, f as noMetroRemedy, i as detectEntryFile, n as pickDevClientScheme, o as needsPrebuild, p as resolveMetroWithRetry, s as runPrebuild, t as devClientScheme, u as launchOutcomeRecord } from "./dev-client-Bvky1hya.mjs";
19
- import { a as statsProjectKey, i as recordRunStats, n as readRunEstimates, t as createRunRecorder } from "./stats-wqxPm7Tv.mjs";
20
- import { E as physicalDeviceModel, O as probeEmulatorSerial, _ as listAdbDevices, a as androidSystemImageAbi, b as memoizeEmulatorProbe, f as emulatorDiskSpaceRemedy, h as findBuildTool, i as androidPoolNoCandidatesRefusal, j as resolvePhysicalDevice, k as resetAdoptedAvd, m as extractEmulatorFailure, n as androidHome, p as emulatorFailureRemedy, r as androidPoolCandidates, t as androidDeviceAbi, y as listInstalledSystemImages } from "./android-xmyGBNy5.mjs";
21
- import { S as unknownAndroidSystemImageRefusal, _ as checkDeviceCapacity, b as ensureOwnedDevice, f as startBuildHeartbeat, p as tailLines, r as HEARTBEAT_INTERVAL_MS, y as ensureBooted } from "./xcode-CDB6fytw.mjs";
22
- import { a as ensureMetroReachable, b as ownedSessionName, l as resolveRemoteContext, n as binOnPath, o as ensureRemoteBootOwned, s as remoteAndroidDeps } from "./device-remote-CCgkG82J.mjs";
23
- import { t as cleanLine } from "./server-expo-BEMr_m6p.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-DGlTStWu.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-B5IJwv3-.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, 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,
@@ -872,7 +887,13 @@ async function verifyAndroidRun({ root, release, remoteRelease, remoteDevice, ve
872
887
  phase("", chalk.yellow("A release process exited before readiness. Run `stim logs --errors` for captured crash reports, or `stim logs --source device` for the full device output."));
873
888
  return { state: LAUNCH_FATAL };
874
889
  }
890
+ const newEmulator = Boolean(device.created && device.owned && !remoteDevice);
891
+ const timeoutMs = newEmulator ? 6e4 : VERIFY_TIMEOUT_MS;
892
+ if (metroCheck && newEmulator) phase("verify", "waiting up to 60s for bundle load (new emulator)");
875
893
  const verification = metroCheck ? await verifyLaunched({
894
+ timeoutMs,
895
+ requireBundleResponse: true,
896
+ slot: launchSlotScope(root, slot),
876
897
  onReadinessPending: () => phase("readiness", "waiting for app readiness (up to 30s after bundle load)"),
877
898
  logsDir,
878
899
  since: launchedAt,
@@ -904,10 +925,10 @@ async function verifyAndroidRun({ root, release, remoteRelease, remoteDevice, ve
904
925
  phase("verify", chalk.red(`FATAL after ${formatDuration(verification.waitedMs ?? 0)}: ${reason}`));
905
926
  for (const line of launchErrorPreview(verification.errors ?? [], root)) phase("", chalk.red(line));
906
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.");
907
- 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.`));
908
929
  else if (verification.processAlive === true && metroPort !== null) {
909
930
  const reloadRemedy = physical ? `run \`agent-device metro reload --metro-port ${metroPort}\`.` : "run `stim reload android`.";
910
- 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.`));
911
932
  }
912
933
  return { state: LAUNCH_FATAL };
913
934
  }
@@ -918,7 +939,7 @@ async function verifyAndroidRun({ root, release, remoteRelease, remoteDevice, ve
918
939
  for (const line of report.lines) phase("launch", chalk.yellow(line));
919
940
  if (report.lines.length > 0 && verification.processAlive === true && metroPort !== null) {
920
941
  const reloadRemedy = physical ? `run \`agent-device metro reload --metro-port ${metroPort}\`.` : "run `stim reload android`.";
921
- 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.`));
922
943
  }
923
944
  return {
924
945
  state: true,
@@ -947,7 +968,29 @@ async function verifyAndroidRun({ root, release, remoteRelease, remoteDevice, ve
947
968
  })) phase("", chalk.yellow(line));
948
969
  return { state: LAUNCH_UNVERIFIED };
949
970
  }
950
- async function finishAndroidRun({ lease, releaseLease, root, json, metroCheck, useBuildCache, variant, release, isExpo, metroPort, logsDir, emuLog, device, physical, remoteDevice, bootPromise, 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 }) {
971
+ async function resolveInstallSerial({ root, slot, device, physical, remoteDevice, serial, resolveAvdSerial, waitForDeviceBoot, phase }) {
972
+ if (physical || remoteDevice || !device.owned || !device.avdName) return serial;
973
+ const resolved = resolveAvdSerial(device.avdName, { timeoutMs: 5e3 });
974
+ if (!resolved.serial) throw new Error(`Could not verify a running owned AVD ${device.avdName} before installation.`);
975
+ if (resolved.serial !== serial) {
976
+ phase("device", `${device.avdName} changed serial (${serial} -> ${resolved.serial}); checking readiness before installation`);
977
+ const ready = await waitForDeviceBoot(resolved.serial, 6e4, { commandTimeoutMs: 5e3 });
978
+ if (!ready.ok) throw new Error(`Emulator ${resolved.serial} never reported boot completion. Diagnostic: ${JSON.stringify(ready.diagnostic)}`);
979
+ if (resolveAvdSerial(device.avdName, { timeoutMs: 5e3 }).serial !== resolved.serial) throw new Error(`Could not verify AVD ${device.avdName} still runs on ${resolved.serial} after checking readiness.`);
980
+ phase("device", `reopen agent-device on ${resolved.serial}`);
981
+ }
982
+ const consolePort = Number(resolved.serial.replace(/^emulator-/, ""));
983
+ withConfigLock(() => {
984
+ const current = deviceSlotPlatforms(loadConfig()?.projects?.[root], slot)?.android;
985
+ if (!current?.owned || current.avdName !== device.avdName) throw new Error("The owned emulator assignment changed before installation.");
986
+ if (current.consolePort !== consolePort) setDevice(root, PLATFORM, {
987
+ ...current,
988
+ consolePort
989
+ }, slot);
990
+ });
991
+ return resolved.serial;
992
+ }
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 }) {
951
994
  let androidPackage = initialPackage;
952
995
  let leaseWarned = false;
953
996
  const raiseLeaseFor = (boundMs, beforeInstall) => {
@@ -964,11 +1007,15 @@ async function finishAndroidRun({ lease, releaseLease, root, json, metroCheck, u
964
1007
  return null;
965
1008
  };
966
1009
  const booted = await bootPromise;
1010
+ const runCommand = nativeRunCommand("android", slot, {
1011
+ physical,
1012
+ deviceId: booted.serial
1013
+ });
967
1014
  if (booted.failed) {
968
1015
  const diag = noDeviceDiagnostic({
969
1016
  reason: booted.reason ?? "The emulator did not boot.",
970
1017
  logFile: emuLog,
971
- 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.`,
972
1019
  localEmulator: !physical
973
1020
  });
974
1021
  return fail(NO_DEVICE, diag.message, diag.remedy, {
@@ -976,7 +1023,22 @@ async function finishAndroidRun({ lease, releaseLease, root, json, metroCheck, u
976
1023
  logPath: diag.logPath ? displayPath(root, diag.logPath) : null
977
1024
  });
978
1025
  }
979
- const serial = booted.serial;
1026
+ let serial;
1027
+ try {
1028
+ serial = await resolveInstallSerial({
1029
+ root,
1030
+ slot,
1031
+ device,
1032
+ physical,
1033
+ remoteDevice,
1034
+ serial: booted.serial,
1035
+ resolveAvdSerial,
1036
+ waitForDeviceBoot,
1037
+ phase
1038
+ });
1039
+ } catch (error) {
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.`);
1041
+ }
980
1042
  phase("device", physical ? `${device.deviceName || serial} (${serial}) connected, not owned by Stim` : `${device.avdName || serial} (${serial}) ${device.adopted || device.adoptionPending ? "adopted" : "booted"} ${bootDuration()}`);
981
1043
  const packageFromApk = readApkPackage(apkPath);
982
1044
  if (packageFromApk && androidPackage && packageFromApk !== androidPackage) phase("install", chalk.dim(`applicationId ${packageFromApk} (from the APK; project files say ${androidPackage})`));
@@ -1008,7 +1070,7 @@ async function finishAndroidRun({ lease, releaseLease, root, json, metroCheck, u
1008
1070
  if (adopting) try {
1009
1071
  withConfigLock(() => {
1010
1072
  const config = loadConfig();
1011
- const current = config?.projects?.[root]?.platforms?.android;
1073
+ const current = deviceSlotPlatforms(config?.projects?.[root], slot)?.android;
1012
1074
  if (!config || !current || current.avdName !== device.avdName) throw new Error("The adopted emulator assignment changed during installation.");
1013
1075
  delete current.adoptionPending;
1014
1076
  saveConfig(config);
@@ -1070,6 +1132,7 @@ async function finishAndroidRun({ lease, releaseLease, root, json, metroCheck, u
1070
1132
  if (launched.failed) {
1071
1133
  printNativeCrashReport({
1072
1134
  root,
1135
+ slot,
1073
1136
  platform: "android",
1074
1137
  deviceId: serial,
1075
1138
  appId: androidPackage,
@@ -1132,6 +1195,7 @@ async function finishAndroidRun({ lease, releaseLease, root, json, metroCheck, u
1132
1195
  if (physical) raiseLeaseFor(0, false);
1133
1196
  const collectorPid = await startCollector({
1134
1197
  root,
1198
+ slot,
1135
1199
  serial,
1136
1200
  packageName: androidPackage,
1137
1201
  spawn,
@@ -1145,6 +1209,7 @@ async function finishAndroidRun({ lease, releaseLease, root, json, metroCheck, u
1145
1209
  if (physical) raiseLeaseFor(release ? RELEASE_VERIFY_WAIT_MS : DEBUG_VERIFY_STEP_MS, false);
1146
1210
  const { state: launchState, warning: launchWarning } = await verifyAndroidRun({
1147
1211
  root,
1212
+ slot,
1148
1213
  release,
1149
1214
  remoteRelease,
1150
1215
  remoteDevice: Boolean(remoteDevice),
@@ -1159,6 +1224,7 @@ async function finishAndroidRun({ lease, releaseLease, root, json, metroCheck, u
1159
1224
  metroPort,
1160
1225
  isExpo,
1161
1226
  physical,
1227
+ device,
1162
1228
  scheme,
1163
1229
  component: launched.component ?? null,
1164
1230
  phase
@@ -1177,6 +1243,7 @@ async function finishAndroidRun({ lease, releaseLease, root, json, metroCheck, u
1177
1243
  const leaseFacts = lease?.facts() ?? null;
1178
1244
  releaseLease();
1179
1245
  const facts = reportAndroidResult({
1246
+ slot,
1180
1247
  json,
1181
1248
  useBuildCache,
1182
1249
  variant,
@@ -1239,7 +1306,7 @@ function androidCommand(program) {
1239
1306
  registerAndroid(program);
1240
1307
  }
1241
1308
  function registerAndroid(program) {
1242
- 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) => {
1243
1310
  if (REMOTE_DEVICE_BACKENDS.includes(value)) return value;
1244
1311
  throw new InvalidArgumentError(`expected one of: ${REMOTE_DEVICE_BACKENDS.join(", ")}`);
1245
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) => {
@@ -1249,8 +1316,10 @@ function registerAndroid(program) {
1249
1316
  process.exit(1);
1250
1317
  return;
1251
1318
  }
1252
- if (!(await runAndroid({
1319
+ if (!(await withWorkspaceProcessLock(workspaceDir(root), "native-run", () => runAndroid({
1253
1320
  root,
1321
+ slot: opts.slot,
1322
+ easProfile: opts.easProfile,
1254
1323
  json: Boolean(opts.json),
1255
1324
  metroCheck: opts.metroCheck !== false,
1256
1325
  useBuildCache: opts.buildCache !== false,
@@ -1260,10 +1329,13 @@ function registerAndroid(program) {
1260
1329
  device: opts.device ?? null,
1261
1330
  wait: opts.wait,
1262
1331
  waitConflict: waitFlagConflict(process.argv)
1332
+ }), {
1333
+ external: true,
1334
+ waitMs: 18e5
1263
1335
  })).ok) process.exit(1);
1264
1336
  });
1265
1337
  }
1266
- 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, resolveMetro = resolveProjectMetro, 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) } = {}) {
1267
1339
  return {
1268
1340
  root,
1269
1341
  json,
@@ -1277,6 +1349,8 @@ function resolveRunAndroidOptions({ root, json = false, remoteDevice: commandRem
1277
1349
  detectRemoteProviders,
1278
1350
  metroCheck,
1279
1351
  useBuildCache,
1352
+ easProfile,
1353
+ resolveEasBuild,
1280
1354
  variantFlag,
1281
1355
  systemImageFlag,
1282
1356
  deviceFlag,
@@ -1298,7 +1372,10 @@ function resolveRunAndroidOptions({ root, json = false, remoteDevice: commandRem
1298
1372
  ensureDevice,
1299
1373
  listSystemImages,
1300
1374
  ensureDeviceBooted,
1375
+ resolveAvdSerial,
1376
+ waitForDeviceBoot,
1301
1377
  resolveMetro,
1378
+ prewarmMetro,
1302
1379
  resolveMetroRetrying,
1303
1380
  readState,
1304
1381
  pidAlive,
@@ -1342,8 +1419,38 @@ function resolveRunAndroidOptions({ root, json = false, remoteDevice: commandRem
1342
1419
  emit
1343
1420
  };
1344
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
+ }
1345
1451
  async function runAndroid(options = {}) {
1346
- 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, resolveMetro, 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);
1347
1454
  const started = now();
1348
1455
  const startedAt = new Date(started).toISOString();
1349
1456
  const projectProblem = appProjectProblem(root);
@@ -1388,8 +1495,11 @@ async function runAndroid(options = {}) {
1388
1495
  };
1389
1496
  }
1390
1497
  const logsDir = workspaceLogsDir(root);
1391
- const buildLog = join(logsDir, "build-android.ndjson");
1392
- 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
+ });
1393
1503
  const record = {
1394
1504
  fingerprint: null,
1395
1505
  cacheKey: null,
@@ -1514,7 +1624,7 @@ async function runAndroid(options = {}) {
1514
1624
  const remoteSettingError = remoteDeviceSettingError(settings);
1515
1625
  if (remoteSettingError) return fail("STIM_BAD_ARG", remoteSettingError, `Set ios.remote and android.remote to one of: ${REMOTE_DEVICE_BACKENDS.join(", ")}.`);
1516
1626
  const systemImage = resolveSystemImage(systemImageFlag, settings);
1517
- const variant = resolveVariant(variantFlag, settings);
1627
+ const variant = easProfile !== void 0 ? "debug" : resolveVariant(variantFlag, settings);
1518
1628
  const flavorRefusal = productFlavorRefusal({
1519
1629
  flavors: readProductFlavors(root),
1520
1630
  variant
@@ -1524,7 +1634,7 @@ async function runAndroid(options = {}) {
1524
1634
  const cachePolicy = artifactCachePolicy(optimizations, requestedBuildCache, release);
1525
1635
  const useBuildCache = cachePolicy.read;
1526
1636
  const isExpo = detectIsExpo(root);
1527
- const physical = deviceFlag !== null && deviceFlag !== void 0 && deviceFlag !== false;
1637
+ const physical = isPhysicalDeviceRequest(deviceFlag);
1528
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.");
1529
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.");
1530
1640
  const noWait = waitFlag === false;
@@ -1536,6 +1646,7 @@ async function runAndroid(options = {}) {
1536
1646
  const waitSeconds = waitParsed.seconds;
1537
1647
  const remoteBackend = physical ? null : commandRemoteBackend ?? remoteAndroidSetting(settings);
1538
1648
  const imageRefusal = systemImageRefusal({
1649
+ slot,
1539
1650
  flag: systemImageFlag,
1540
1651
  resolved: systemImage,
1541
1652
  physical,
@@ -1543,6 +1654,17 @@ async function runAndroid(options = {}) {
1543
1654
  listImages: listSystemImages
1544
1655
  });
1545
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);
1546
1668
  const requestedSerial = typeof deviceFlag === "string" ? deviceFlag : null;
1547
1669
  let androidPackage = detectAndroidPackage(root);
1548
1670
  record.bundleId = androidPackage;
@@ -1673,7 +1795,8 @@ async function runAndroid(options = {}) {
1673
1795
  const diag = noDeviceDiagnostic({
1674
1796
  reason: `Could not ensure an owned Android emulator: ${err?.message || err}`,
1675
1797
  logFile: emuLog,
1676
- remedy: "Check that JAVA_HOME and ANDROID_HOME are set correctly, and that an arm64 system image is installed (`sdkmanager \"system-images;android-36;google_apis;arm64-v8a\"`)."
1798
+ localEmulator: !(err instanceof AvdRecoveryError),
1799
+ remedy: err instanceof AvdRecoveryError ? err.remedy : "Check that JAVA_HOME and ANDROID_HOME are set correctly, and that an arm64 system image is installed (`sdkmanager \"system-images;android-36;google_apis;arm64-v8a\"`)."
1677
1800
  });
1678
1801
  return fail(NO_DEVICE, diag.message, diag.remedy, {
1679
1802
  lines: diag.lines,
@@ -1753,6 +1876,18 @@ async function runAndroid(options = {}) {
1753
1876
  let storeSources = [];
1754
1877
  let apkPath = null;
1755
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
+ }
1756
1891
  const fingerprintTimer = stepTimer(now);
1757
1892
  try {
1758
1893
  const computed = await fingerprint(root, { platform: PLATFORM });
@@ -1825,6 +1960,13 @@ async function runAndroid(options = {}) {
1825
1960
  return true;
1826
1961
  }
1827
1962
  const runFromFingerprint = async () => {
1963
+ if (metroCheck && metroPort !== null && optimizations.metroWarmup) prewarmMetro({
1964
+ port: metroPort,
1965
+ platform: "android",
1966
+ isExpo,
1967
+ appId: androidPackage,
1968
+ bundleUrl: metroWarmupUrlSetting(settings, "android")
1969
+ });
1828
1970
  if (!await resolveInitialFingerprint()) return phaseFailure;
1829
1971
  let remote = null;
1830
1972
  let abandonedRemote = false;
@@ -1877,7 +2019,7 @@ async function runAndroid(options = {}) {
1877
2019
  } else phase("cache", `remote miss (${remote.name}) ${remoteTimer()}`);
1878
2020
  }
1879
2021
  }
1880
- await resolveRemoteArtifact();
2022
+ if (!easBuild) await resolveRemoteArtifact();
1881
2023
  let waitedForBuild = null;
1882
2024
  let releasedWait = null;
1883
2025
  async function waitForSharedBuild() {
@@ -1910,7 +2052,7 @@ async function runAndroid(options = {}) {
1910
2052
  releasedWait = null;
1911
2053
  const holder = attempt.held;
1912
2054
  const who = holder.projectRoot || "another workspace";
1913
- phase("build", `${who} is already building ${shortHash(hash)} (pid ${holder.pid})${holder.logFile ? ` -- tail ${holder.logFile}` : ""}`);
2055
+ phase("build", `${who} is already building ${shortHash(hash)} (pid ${holder.pid})${holder.logFile ? ` -- tail ${holder.logFile}` : ""} -- stim guide lifecycle concurrency`);
1914
2056
  let waited = null;
1915
2057
  try {
1916
2058
  const ceilingMs = WAIT_CEILING_MS - (now() - waitStarted);
@@ -1942,7 +2084,7 @@ async function runAndroid(options = {}) {
1942
2084
  pid: holder.pid,
1943
2085
  ms: waited.waitedMs
1944
2086
  };
1945
- phase("build", `waited ${formatDuration(waited.waitedMs)} for ${who}'s build -> installed from cache`);
2087
+ phase("build", `waited ${formatDuration(waited.waitedMs)} for ${who}'s build -> installed from cache -- stim guide lifecycle concurrency`);
1946
2088
  } else if (waited?.lockReleased) {
1947
2089
  failedHolder = void 0;
1948
2090
  releasedWait = {
@@ -1961,7 +2103,7 @@ async function runAndroid(options = {}) {
1961
2103
  }
1962
2104
  return true;
1963
2105
  }
1964
- if (!await waitForSharedBuild()) return phaseFailure;
2106
+ if (!easBuild && !await waitForSharedBuild()) return phaseFailure;
1965
2107
  let swapDir = null;
1966
2108
  let swapFellBack = false;
1967
2109
  const installableCachedApk = async (key, cachedPath) => {
@@ -2049,7 +2191,7 @@ async function runAndroid(options = {}) {
2049
2191
  phase("cache", `hit ${shortHash(storeHash)} (post-prebuild key)`);
2050
2192
  if (releasedWait) {
2051
2193
  waitedForBuild = releasedWait.facts;
2052
- phase("build", `waited ${formatDuration(waitedForBuild.ms)} for ${releasedWait.who}'s build -> installed from cache`);
2194
+ phase("build", `waited ${formatDuration(waitedForBuild.ms)} for ${releasedWait.who}'s build -> installed from cache -- stim guide lifecycle concurrency`);
2053
2195
  }
2054
2196
  }
2055
2197
  }
@@ -2161,8 +2303,10 @@ async function runAndroid(options = {}) {
2161
2303
  }
2162
2304
  return true;
2163
2305
  }
2164
- await prepareCachedArtifact();
2165
- if (!await buildArtifact()) return phaseFailure;
2306
+ if (!easBuild) {
2307
+ await prepareCachedArtifact();
2308
+ if (!await buildArtifact()) return phaseFailure;
2309
+ }
2166
2310
  record.appPath = apkPath;
2167
2311
  let leaseHandle = null;
2168
2312
  let stopLeaseSignals = null;
@@ -2207,6 +2351,7 @@ async function runAndroid(options = {}) {
2207
2351
  }
2208
2352
  try {
2209
2353
  return await finishAndroidRun({
2354
+ slot,
2210
2355
  lease: leaseHandle,
2211
2356
  releaseLease,
2212
2357
  root,
@@ -2223,6 +2368,8 @@ async function runAndroid(options = {}) {
2223
2368
  physical,
2224
2369
  remoteDevice,
2225
2370
  bootPromise,
2371
+ resolveAvdSerial,
2372
+ waitForDeviceBoot,
2226
2373
  bootDuration: () => bootDuration,
2227
2374
  apkPath,
2228
2375
  androidPackage,
@@ -1,11 +1,11 @@
1
- import { t as getExecutor } from "./exec-bsN9MJXb.mjs";
2
- import { k as withDirLock, u as getConfigDir } from "./config-D7uu8Gbq.mjs";
3
- import { r as register } from "./cache-manifest-oeH18ERr.mjs";
4
- import { D as workspaceLogsDir, x as sharedCcache, y as ensureWorkspaceStorage } from "./state-IYEY-zi8.mjs";
1
+ import { t as getExecutor } from "./exec-CyylIdq9.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-xmyGBNy5.mjs";
8
- import { f as startBuildHeartbeat, r as HEARTBEAT_INTERVAL_MS } from "./xcode-CDB6fytw.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";