stim 1.33.0 → 1.35.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 (125) hide show
  1. package/dist/{acquire-artifact-DTh_LxrE.mjs → acquire-artifact-0E_iw7Rf.mjs} +1 -1
  2. package/dist/{act-Cur4YKtK.mjs → act-CyxFP8Vj.mjs} +9 -10
  3. package/dist/{activity-C8kBq9w0.mjs → activity-BQpJpAjA.mjs} +2 -2
  4. package/dist/{agent-device-usage-output-BTRaK_up.mjs → agent-device-usage-output-BoOC3ZF9.mjs} +3 -3
  5. package/dist/{android-D5mCdVl2.mjs → android-CjcJE7uA.mjs} +5 -5
  6. package/dist/{android-BL-QqHjc.mjs → android-YRb1UzSy.mjs} +27 -29
  7. package/dist/{android-project-BDlPnfmL.d.mts → android-project-DhO6el8N.d.mts} +1 -1
  8. package/dist/api.d.mts +1 -1
  9. package/dist/api.mjs +21 -21
  10. package/dist/{artifact-BufzdGbT.mjs → artifact-2NIRz6SC.mjs} +19 -15
  11. package/dist/{artifact-BMjubIP2.mjs → artifact-BG4h_WTC.mjs} +11 -11
  12. package/dist/{auto-placement-D7P98glJ.mjs → auto-placement-CBdWa87P.mjs} +4 -5
  13. package/dist/{browser-web-C74et-Nv.mjs → browser-web-CNlRugJv.mjs} +5 -5
  14. package/dist/{build-DFBGWOTc.mjs → build-CNE3Wo5-.mjs} +15 -16
  15. package/dist/build-artifact-cache-D7B6seMj.mjs +174 -0
  16. package/dist/build-artifact-cache.d.mts +25 -0
  17. package/dist/build-artifact-cache.mjs +2 -0
  18. package/dist/{build-cache-Cqj4FGnX.mjs → build-cache-_fkewiwH.mjs} +6 -1
  19. package/dist/{build-plan-T3Hi24ax.mjs → build-plan-BSgR9yKo.mjs} +5 -5
  20. package/dist/{cli-B2YSjc9s.mjs → cli-DK24VzHi.mjs} +17 -17
  21. package/dist/cli.mjs +1 -1
  22. package/dist/{client-0_Mnfz0a.mjs → client-Dy0eJJtz.mjs} +49 -11
  23. package/dist/{command-output-DWI9tGPU.mjs → command-output-CGxrQ3Bd.mjs} +1 -1
  24. package/dist/{dependencies-CgvBZbXD.mjs → dependencies-IVl2Ga2D.mjs} +27 -30
  25. package/dist/{dev-client-ChsHwgrn.mjs → dev-client-DDApoVvd.mjs} +2 -2
  26. package/dist/{device-android-B8TQP5AX.mjs → device-android-C-biM49_.mjs} +3 -3
  27. package/dist/{ccache-BXtzEL6M.mjs → device-capacity-C0PiTyRb.mjs} +1004 -63
  28. package/dist/device-host-worker.mjs +6 -7
  29. package/dist/{device-B0htCt2u.mjs → device-mOFYiHiw.mjs} +8 -8
  30. package/dist/{device-DOehZpyx.mjs → device-p_cp8IzW.mjs} +6 -6
  31. package/dist/{device-pool-DP4sCeCC.mjs → device-pool-nsMvt2X2.mjs} +2 -2
  32. package/dist/{device-provision-CnNOIxCF.mjs → device-provision-BYYJuRKZ.mjs} +7 -7
  33. package/dist/{doctor-C6Xn96RS.mjs → doctor-BBAb8NLe.mjs} +1 -1
  34. package/dist/{doctor-9GrW5YSP.mjs → doctor-BptE4PaK.mjs} +10 -11
  35. package/dist/{doctor-nRLBoajo.mjs → doctor-RosbBfTV.mjs} +11 -12
  36. package/dist/doctor-fingerprint.mjs +1 -1
  37. package/dist/{doctor-watchman-CDhpC3Oh.mjs → doctor-watchman-CKOnCINh.mjs} +2 -2
  38. package/dist/{eas-build-CpJ4h9tG.mjs → eas-build-BxJdzQnF.mjs} +2 -2
  39. package/dist/{eas-fallback-B3z5nL1g.mjs → eas-fallback-DU2WDF8r.mjs} +2 -2
  40. package/dist/{eas-session-ledger-Bspy-VJQ.mjs → eas-session-ledger-DCUfh6m7.mjs} +1 -1
  41. package/dist/{error-diagnostics-DoLGklYX.mjs → error-diagnostics-D6wlK-yX.mjs} +3 -3
  42. package/dist/{gc-DolXpMSB.mjs → gc-BUkonUfz.mjs} +10 -10
  43. package/dist/{gc-BWoOp9q7.mjs → gc-CvMEytXz.mjs} +2 -2
  44. package/dist/{guide-CmZB0GtP.mjs → guide-BWozWG7D.mjs} +25 -15
  45. package/dist/{hosted-android-ByG-ICuh.mjs → hosted-android-y6K3DKQT.mjs} +2 -2
  46. package/dist/{hosted-client-D19Lv0tg.mjs → hosted-client-Bb89P6an.mjs} +2 -2
  47. package/dist/{hosted-logs-D7anR6Dk.mjs → hosted-logs-DYPsnWMF.mjs} +2 -2
  48. package/dist/{hosted-logs-sync-D182b1eJ.mjs → hosted-logs-sync-DfUJ2GpF.mjs} +4 -4
  49. package/dist/{hosted-macos-D0DDQxvl.mjs → hosted-macos-DsEzYVY_.mjs} +4 -4
  50. package/dist/{hosted-native-BzXIy97W.mjs → hosted-native-C9xQYYFb.mjs} +4 -4
  51. package/dist/{ios-AdhPKXO3.mjs → ios-BCKxjpRp.mjs} +8 -8
  52. package/dist/{ios-dbXzjTxx.mjs → ios-FhMkDfNC.mjs} +19 -21
  53. package/dist/{ios-device-DXbHZfFe.mjs → ios-device-DMOnaH5n.mjs} +1 -1
  54. package/dist/{ios-project-BzfLigfh.mjs → ios-project-PXBDoaii.mjs} +5 -5
  55. package/dist/ios-signing-CCFNjjWU.mjs +202 -0
  56. package/dist/{js-swap-EDVOJmqx.mjs → js-swap-07ikLRJJ.mjs} +3 -3
  57. package/dist/{launch-CKHpthsw.mjs → launch-CTq5gBim.mjs} +13 -13
  58. package/dist/{lightened-Cv4ToJRX.mjs → lightened-DikgC3-S.mjs} +2 -2
  59. package/dist/{logs-IagR9dWI.mjs → logs-DwI3Rkpf.mjs} +5 -5
  60. package/dist/{logs-DCQ3_tGS.mjs → logs-YDMfEohb.mjs} +3 -3
  61. package/dist/{macos-Ch7vuM-G.mjs → macos-BoNM-l6f.mjs} +14 -14
  62. package/dist/{macos-pc0aVVbN.mjs → macos-C_e9f5-5.mjs} +1 -1
  63. package/dist/maintenance-run.mjs +3 -3
  64. package/dist/{native-android-B3CEDq4R.mjs → native-android-Mv4YTrVN.mjs} +11 -11
  65. package/dist/{native-gradle-artifact-inputs-BLnO2hHR.mjs → native-gradle-artifact-inputs-C_TU6wPq.mjs} +2 -2
  66. package/dist/{native-runtime-e1OU8gEN.mjs → native-runtime-oDqT4UES.mjs} +3 -3
  67. package/dist/{native-xcode-inputs-DL3ETb8s.mjs → native-xcode-inputs-DsozghTX.mjs} +1 -1
  68. package/dist/{native-xcode-ios-D4e8k2Jy.mjs → native-xcode-ios-UXnH9NRY.mjs} +13 -13
  69. package/dist/offload-worker.d.mts +6 -1
  70. package/dist/offload-worker.mjs +40 -15
  71. package/dist/{phone-reverses-rvyxW1Fl.mjs → phone-reverses-B9WyCBKQ.mjs} +1 -1
  72. package/dist/{placement-log-nnGnu8mo.mjs → placement-log-D0DZLcJk.mjs} +1 -1
  73. package/dist/{plan-placement-D3JD-T-K.mjs → plan-placement-DqmLrWoX.mjs} +4 -4
  74. package/dist/{ports-CZJJfJ8Q.mjs → ports-C1_1Qgdy.mjs} +1 -1
  75. package/dist/{prebuild-Bhs7TTcz.mjs → prebuild-BptrXmh_.mjs} +6 -6
  76. package/dist/{project-DWEXiBA9.mjs → project-Dqqj3IkW.mjs} +11 -11
  77. package/dist/{react-native-android-C6Y270GS.mjs → react-native-android-Bsm_o4A8.mjs} +18 -18
  78. package/dist/{react-native-build-DaPfzf_e.mjs → react-native-build-BTUcE926.mjs} +4 -4
  79. package/dist/{react-native-doctor-DGMPxkPS.mjs → react-native-doctor-CfsCTKZt.mjs} +4 -4
  80. package/dist/{react-native-ios-dX48_92n.mjs → react-native-ios-_cK2pjZn.mjs} +25 -19
  81. package/dist/{reload-BGwblBJ9.mjs → reload-CXgce4Yo.mjs} +1 -1
  82. package/dist/{reload-DsOByDfy.mjs → reload-DsTMtMDU.mjs} +6 -6
  83. package/dist/{remote-D0uJZ76R.mjs → remote-BsmYHUxH.mjs} +6 -6
  84. package/dist/{result-DuimMZKJ.mjs → result-DK6nTO-M.mjs} +9 -10
  85. package/dist/{server-command-BC0EgMfY.mjs → server-command-Cu2w-UjB.mjs} +2 -2
  86. package/dist/{server-expo-15RjL0ir.mjs → server-expo-CjQwxvIi.mjs} +1 -1
  87. package/dist/server-expo-DbSypdNl.mjs +2 -0
  88. package/dist/{settings-CuP55ieT.mjs → settings-DcmVICCn.mjs} +2 -2
  89. package/dist/{settings-payload-BIGGQ3or.mjs → settings-payload-BoiFMiFq.mjs} +1 -1
  90. package/dist/{settings-write-D85j8mej.mjs → settings-write-B3LzEVwf.mjs} +2 -2
  91. package/dist/{slot-launch-D4oREwhx.mjs → slot-launch-DsQmPdlg.mjs} +2 -2
  92. package/dist/{stage-DKC6tDXu.mjs → stage-Dqk0qvaI.mjs} +1 -1
  93. package/dist/{start-BsEV4S-s.mjs → start-DIFiT2P5.mjs} +5 -5
  94. package/dist/{start-DfPSLc__.mjs → start-GkRBR1e3.mjs} +1 -1
  95. package/dist/{stats-ByEUblPr.mjs → stats-Dz08IopP.mjs} +3 -3
  96. package/dist/{status-C6hLlDqW.mjs → status-DhKv_e8a.mjs} +11 -12
  97. package/dist/{status-FP1MH9qn.mjs → status-Dost__Vn.mjs} +1 -1
  98. package/dist/{status-watch-D8s823CZ.mjs → status-watch-UecLnPQD.mjs} +1 -1
  99. package/dist/status-worker.mjs +1 -1
  100. package/dist/{stop-CpTy6bod.mjs → stop-CcZISot5.mjs} +2 -2
  101. package/dist/{stop-5LKfVv-0.mjs → stop-QFOqgICJ.mjs} +2 -2
  102. package/dist/{stop-BVI2-L7s.mjs → stop-vRe6TQk5.mjs} +9 -9
  103. package/dist/supervisor-run.mjs +7 -8
  104. package/dist/{support-CqyWfZZI.mjs → support-CNqJDBrH.mjs} +3 -3
  105. package/dist/support-DW575Dnx.mjs +2050 -0
  106. package/dist/{swiftpm-artifact-inputs-uq5oV7xR.mjs → swiftpm-artifact-inputs-DQTJTczX.mjs} +1 -1
  107. package/dist/{swiftpm-macos-BuhqYB3n.mjs → swiftpm-macos-BKczZ0k9.mjs} +4 -4
  108. package/dist/{toolchain-D_jotUG7.mjs → toolchain-BDlugtoh.mjs} +1 -1
  109. package/dist/{toolchain-s5ZmG42Z.mjs → toolchain-DRQyCvlp.mjs} +406 -8
  110. package/dist/web-D1m1LKEz.mjs +2 -0
  111. package/dist/{web-DwYCTFdh.mjs → web-TDWjHUT3.mjs} +3 -3
  112. package/dist/web-run.mjs +1 -1
  113. package/dist/{workspaces-B3ahAHZc.mjs → workspaces-CY3KSdC0.mjs} +2 -2
  114. package/dist/{worktree-CrjsPIs2.mjs → worktree-D6myrQ2H.mjs} +1 -1
  115. package/dist/{worktree-BhsML6GV.mjs → worktree-fKKe8j5M.mjs} +10 -10
  116. package/package.json +12 -7
  117. package/dist/device-capacity-BAkETfIl.mjs +0 -867
  118. package/dist/device-ios-DFaYFdar.mjs +0 -716
  119. package/dist/device-lease-run-DpiCnRbZ.mjs +0 -282
  120. package/dist/idle-shutdown-6i8CHoNq.mjs +0 -142
  121. package/dist/ios-signing-D-tSX1iF.mjs +0 -489
  122. package/dist/selection-xS50Mt0V.mjs +0 -791
  123. package/dist/server-expo-gYIypb0-.mjs +0 -2
  124. package/dist/support-D8OH0z98.mjs +0 -287
  125. package/dist/web-cCfM8kwV.mjs +0 -2
@@ -1,24 +1,24 @@
1
1
  import { C as operationState, D as ownership_claim_exports, E as debugLog, S as isTermination, T as process_identity_exports, d as throwIfCancelled, f as WorkspaceProcessCleanupError, l as cancellableSleep, m as workspaceProcessLockError, n as isTimeoutError, p as withWorkspaceProcessLock, t as getExecutor, u as cancelledError } from "./exec-CGuDzt40.mjs";
2
- import { D as workspaceId$1, E as workspaceDir, M as withDirLock$1, b as sharedCcache$1, d as writeWorkspaceState, j as workspaceStateFile$1, k as workspaceLogsDir$1, l as withWorkspaceStateLockAsync, r as readWorkspaceState$1, t as clearWorkspaceStateKey, y as sharedBuildCache } from "./workspace-state-BdZ-QgmB.mjs";
2
+ import { D as workspaceId$1, E as workspaceDir, M as withDirLock$1, b as sharedCcache$1, d as writeWorkspaceState, j as workspaceStateFile$1, k as workspaceLogsDir$1, l as withWorkspaceStateLockAsync, r as readWorkspaceState$1, s as updateWorkspaceStateAsync, t as clearWorkspaceStateKey, y as sharedBuildCache } from "./workspace-state-BdZ-QgmB.mjs";
3
3
  import { n as stripAnsi } from "./process-output-Fpl8PYi8.mjs";
4
- import { a as formatElapsed, l as phaseLine, o as formatLongDuration, s as isAppLaunchError, u as plural } from "./command-output-DWI9tGPU.mjs";
5
- import { a as clearDevice, at as isOnMountedVolume, ct as volumeRootFor, d as getConfigDir, ft as deviceSlotPlatforms, ht as projectDeviceSlots, k as withConfigLockAsync$1, m as getProject, nt as directorySizeAsync, o as clearSupervisor, rt as formatBytes, st as measuredDirectorySizeAsync, tt as directorySize, v as loadConfig$1 } from "./config-BW0fAtoK.mjs";
4
+ import { a as formatElapsed, l as phaseLine, o as formatLongDuration, s as isAppLaunchError, u as plural } from "./command-output-CGxrQ3Bd.mjs";
5
+ import { a as clearDevice, at as isOnMountedVolume, ct as volumeRootFor, d as getConfigDir, dt as deviceSlotKey, ft as deviceSlotPlatforms, ht as projectDeviceSlots, k as withConfigLockAsync$1, m as getProject, nt as directorySizeAsync, o as clearSupervisor, p as getPlatformDeviceLimits, rt as formatBytes, st as measuredDirectorySizeAsync, tt as directorySize, u as getConcurrencyLimits, v as loadConfig$1 } from "./config-BW0fAtoK.mjs";
6
6
  import { a as killOnAbort, n as runOperationFileQuiet, r as childEnvironment, t as runOperationFile } from "./operation-exec-DKlBcKAL.mjs";
7
- import { Z as resolveProjectSettingsAsync, d as androidLayoutSetting, ft as planMetroReach, m as defaultAndroidLayout } from "./settings-B5IBDfTx.mjs";
8
- import { C as resolvePackageJson, M as readMetroTunnel, N as readPidFile, P as readRemoteSession, i as findProjectRoot$1, j as clearWorkspaceSupervisor, k as clearRemoteSession, s as ownedDeviceLabel, v as detectIsExpo } from "./project-DWEXiBA9.mjs";
9
- import { R as listAvdsAsync, U as ownedAvdDirectory, X as parseAvdSystemImage, Y as parseAvdDeviceProfile, d as androidToolPath, ft as isStimOwnedAvd, gt as dropParked, m as avdIniPaths, o as androidHome, pt as isStimOwnedSim, q as ownedAvdSerialResolverAsync, xt as readParked, y as avdStorageRoots, z as listInstalledSystemImages } from "./android-Iq0M2t2d.mjs";
7
+ import { Z as resolveProjectSettingsAsync, d as androidLayoutSetting, ft as planMetroReach, g as deviceReclaimIdleMinutesSetting, m as defaultAndroidLayout } from "./settings-B5IBDfTx.mjs";
8
+ import { C as resolvePackageJson, M as readMetroTunnel, N as readPidFile, P as readRemoteSession, i as findProjectRoot$1, j as clearWorkspaceSupervisor, k as clearRemoteSession, s as ownedDeviceLabel, v as detectIsExpo } from "./project-Dqqj3IkW.mjs";
9
+ import { N as listAdbDevices, P as listAdbDevicesAsync, R as listAvdsAsync, U as ownedAvdDirectory, X as parseAvdSystemImage, Y as parseAvdDeviceProfile, d as androidToolPath, ft as isStimOwnedAvd, gt as dropParked, m as avdIniPaths, o as androidHome, pt as isStimOwnedSim, q as ownedAvdSerialResolverAsync, xt as readParked, y as avdStorageRoots, z as listInstalledSystemImages } from "./android-Iq0M2t2d.mjs";
10
10
  import { a as listeningPidsAsync, c as pidExists, d as resolveProjectMetro, h as isMetroRunning, r as killMetroTreeAsync, u as processCwds } from "./metro-B5PVz0Wv.mjs";
11
11
  import { c as readNdjsonGenerations$1, i as createNdjsonWriter, r as createNdjsonTail } from "./ndjson-CrjIsGzw.mjs";
12
- import { T as formatSpace, a as removeEasSessionClaim, d as parseWatchRoots, f as parseWatchmanClients, i as recordEasSessionClaim, l as parseSubscriberNames, n as easMachineStateRoot$1, o as removeEasSessionClaimAsync, p as parseWatchmanPid, r as readEasSessionLedger$1, s as compareStimVersions, t as assertEasMachineRootWritable, u as parseTriggerCount, w as environmentState, y as capacity } from "./eas-session-ledger-Bspy-VJQ.mjs";
12
+ import { T as formatSpace, a as removeEasSessionClaim, d as parseWatchRoots, f as parseWatchmanClients, i as recordEasSessionClaim, l as parseSubscriberNames, n as easMachineStateRoot$1, o as removeEasSessionClaimAsync, p as parseWatchmanPid, r as readEasSessionLedger$1, s as compareStimVersions, t as assertEasMachineRootWritable, u as parseTriggerCount, w as environmentState, y as capacity } from "./eas-session-ledger-DCUfh6m7.mjs";
13
13
  import { i as registeredCaches, r as register } from "./cache-manifest-8JX9S1QJ.mjs";
14
- import { At as mapLimited, B as workspaceBuildInProgress, Dt as createGcResults, Et as GC_OUTPUT, F as collectOwnedDeviceActivityAsync, L as idleShutdownCandidates, Mt as sizeConcurrency, N as collectIdleDevicesAsync, _t as teardownParkedIosSim, bt as measureDu, dt as eraseParkedIosSim, gt as teardownParkedAvd, ht as teardownOwnedIosSim, i as clearWorkspaceOutputs, l as keptByLock, o as collectWorkspaceOutputs, pt as teardownOwnedAvd, ut as eraseParkedAvd, v as withIdleWorkspace, vt as probePublicHttp, wt as listBuildSlots, x as resolveSupervisorTarget, xt as writeUsageCache, yt as canonical$1, z as shutDownIdleDevices } from "./workspaces-B3ahAHZc.mjs";
14
+ import { At as mapLimited, B as workspaceBuildInProgress, Dt as createGcResults, Et as GC_OUTPUT, F as collectOwnedDeviceActivityAsync, L as idleShutdownCandidates, Mt as sizeConcurrency, N as collectIdleDevicesAsync, P as collectOwnedDeviceActivity, _t as teardownParkedIosSim, bt as measureDu, dt as eraseParkedIosSim, gt as teardownParkedAvd, ht as teardownOwnedIosSim, i as clearWorkspaceOutputs, l as keptByLock, o as collectWorkspaceOutputs, ot as recordCapacityRefusal, pt as teardownOwnedAvd, st as recordCapacityWait, ut as eraseParkedAvd, v as withIdleWorkspace, vt as probePublicHttp, wt as listBuildSlots, x as resolveSupervisorTarget, xt as writeUsageCache, yt as canonical$1, z as shutDownIdleDevices$1 } from "./workspaces-CY3KSdC0.mjs";
15
15
  import { t as canonicalPath } from "./paths-BsJMoPP2.mjs";
16
- import { it as nativeRunWaitNotice, st as runCancellationSignal, ut as claimForEmptying } from "./toolchain-s5ZmG42Z.mjs";
17
- import { N as listBuildLocks, b as leaseIsExpired, g as workspaceActivity, o as envDir, p as readHostProcessTableAsync, x as listLeaseFiles } from "./activity-C8kBq9w0.mjs";
18
- import { u as expoConfigContents } from "./build-cache-Cqj4FGnX.mjs";
19
- import { G as deviceHoldsBundle, O as sanitizeDeviceLabel, W as deviceHoldsApk, b as parseSimctlList, f as listAllIosSimsAsync, g as listUserApps, j as uninstallIosApp, p as listIosDeviceTypesAsync, u as iosSimulatorFailureAdvice, y as parseRuntimeVersion } from "./ios-BdZdNsAj.mjs";
16
+ import { $ as readBundleSchemes, Z as approvableSchemes, _t as runCancellationSignal, bt as claimForEmptying, mt as nativeRunWaitNotice } from "./toolchain-DRQyCvlp.mjs";
17
+ import { N as listBuildLocks, _ as workspaceDeviceLastUseAt, b as leaseIsExpired, g as workspaceActivity, o as envDir, p as readHostProcessTableAsync, x as listLeaseFiles } from "./activity-BQpJpAjA.mjs";
18
+ import { u as expoConfigContents } from "./build-cache-_fkewiwH.mjs";
19
+ import { G as deviceHoldsBundle, O as sanitizeDeviceLabel, W as deviceHoldsApk, b as parseSimctlList, d as listAllIosSims, f as listAllIosSimsAsync, g as listUserApps, j as uninstallIosApp, l as iosRuntimeMatches, p as listIosDeviceTypesAsync, u as iosSimulatorFailureAdvice, y as parseRuntimeVersion } from "./ios-BdZdNsAj.mjs";
20
20
  import { n as supervisorError } from "./errors-DHm_U5gv.mjs";
21
- import { c as findBinUpward, n as expoBinFromPackage, r as expoBinPath, s as expoTelemetryEnv } from "./server-expo-15RjL0ir.mjs";
21
+ import { c as findBinUpward, n as expoBinFromPackage, r as expoBinPath, s as expoTelemetryEnv } from "./server-expo-CjQwxvIi.mjs";
22
22
  import { t as DEV_MENU_LAUNCH_ARGS } from "./ios-device-DLdEDWJj.mjs";
23
23
  import { t as resolveMaintenanceSettings } from "./settings-D9LtQaUw.mjs";
24
24
  import { createRequire } from "node:module";
@@ -26,12 +26,13 @@ import { createHash, randomUUID } from "node:crypto";
26
26
  import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, realpathSync, renameSync, rmSync, statSync, statfsSync, unlinkSync, writeFileSync } from "node:fs";
27
27
  import { homedir, platform, totalmem } from "node:os";
28
28
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
29
- import { LOG_ROTATE_BYTES, METRO_NAMED_CACHE_LAYOUT, ambientContext, cacheDirRefusal, compiledHelper, configDir, ensurePrivateDir, rotatedLogPath, withAmbientContext, withDirLock } from "@stim-cli/core";
30
- import { DEV_SERVER_STOP_REQUEST_KEY, MACHINE_OWNER_KINDS, MAINTENANCE_LOG_NAME, budgetSweepFile, budgetSweepLock, buildWorkerRoot, gcLogFile, isJsonObject, loadConfig, machineCacheUsageFile, maintenanceNdjsonFile, readCreatedDevices, readDevServerStop, readDevServerStopRequest, readJsonObject, readMachineCacheUsage, runId, settingDefinition, settingValueError, workspaceLogsDir } from "@stim-cli/core/state";
29
+ import { LOG_ROTATE_BYTES, METRO_NAMED_CACHE_LAYOUT, ambientContext, cacheDirRefusal, compiledHelper, configDir, ensurePrivateDir, rotatedLogPath, withAmbientContext, withDirLock, workspaceId } from "@stim-cli/core";
30
+ import { DEVICE_IDLE_SHUTDOWN_KEY, DEVICE_RECLAIM_REASON, DEV_SERVER_STOP_REQUEST_KEY, MACHINE_OWNER_KINDS, MAINTENANCE_LOG_NAME, automaticMachineEnabled, budgetSweepFile, budgetSweepLock, buildWorkerRoot, gcLogFile, isJsonObject, loadConfig, machineCacheUsageFile, maintenanceNdjsonFile, readCreatedDevices, readDevServerStop, readDevServerStopRequest, readDeviceIdleShutdowns, readJsonObject, readMachineCacheUsage, runId, settingDefinition, settingValueError, workspaceLogsDir } from "@stim-cli/core/state";
31
31
  import { basename as basename$1, dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1, relative as relative$1, resolve as resolve$1 } from "path";
32
32
  import chalk from "chalk";
33
33
  import { existsSync as existsSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, realpathSync as realpathSync$1, rmSync as rmSync$1, statSync as statSync$1 } from "fs";
34
34
  import { fileURLToPath } from "node:url";
35
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
35
36
  import { access, lstat, readFile, readdir, rm, stat, statfs } from "node:fs/promises";
36
37
  import { homedir as homedir$1, tmpdir as tmpdir$1 } from "os";
37
38
  //#region src/budget-sweep.ts
@@ -2761,48 +2762,6 @@ async function readRemoteSimulatorArch(daemon, { fetch: fetchImpl = fetch, timeo
2761
2762
  }
2762
2763
  }
2763
2764
  //#endregion
2764
- //#region src/engine/app-schemes.ts
2765
- function schemesFromInfoPlist(plist) {
2766
- const types = plist?.CFBundleURLTypes;
2767
- if (!Array.isArray(types)) return [];
2768
- const out = [];
2769
- for (const type of types) {
2770
- const schemes = type?.CFBundleURLSchemes;
2771
- if (Array.isArray(schemes)) out.push(...schemes.filter((s) => typeof s === "string" && s.trim() !== ""));
2772
- }
2773
- return out;
2774
- }
2775
- function readBundleSchemes(appPath, { exec = null } = {}) {
2776
- if (typeof appPath !== "string" || appPath.trim() === "") return [];
2777
- const e = exec || getExecutor();
2778
- let out;
2779
- try {
2780
- out = e.runFile("plutil", [
2781
- "-convert",
2782
- "json",
2783
- "-o",
2784
- "-",
2785
- join(appPath, "Info.plist")
2786
- ], { timeoutMs: 1e4 });
2787
- } catch {
2788
- return [];
2789
- }
2790
- try {
2791
- return schemesFromInfoPlist(JSON.parse(String(out)));
2792
- } catch {
2793
- return [];
2794
- }
2795
- }
2796
- const THIRD_PARTY_SCHEME = /^(?:fb\d+|com\.googleusercontent\.apps\.|msauth\.|msauthv2|twitterkit-|db-[a-z0-9]+$|spotify|snapchat|com\.facebook)/i;
2797
- function approvableSchemes(schemes) {
2798
- return (Array.isArray(schemes) ? schemes : []).filter((s) => typeof s === "string" && s.trim() !== "").map((s) => s.trim()).filter((s) => !/^(?:https?|mailto|tel|sms|itms(?:-apps)?)$/i.test(s) && !THIRD_PARTY_SCHEME.test(s));
2799
- }
2800
- function pickDevClientScheme(schemes) {
2801
- const all = approvableSchemes(schemes);
2802
- const expo = all.filter((s) => s.startsWith("exp+"));
2803
- return (expo.length ? expo : all).toSorted((a, b) => b.length - a.length)[0] ?? null;
2804
- }
2805
- //#endregion
2806
2765
  //#region src/engine/app-install.ts
2807
2766
  const INSTALL_ERROR = "STIM_INSTALL_FAILED";
2808
2767
  const LAUNCH_ERROR = "STIM_LAUNCH_FAILED";
@@ -7817,7 +7776,7 @@ function estimateMemoryUse({ config, sims, androidSerial, supervisorRunning }) {
7817
7776
  liveWorkspaces: liveCount
7818
7777
  };
7819
7778
  }
7820
- async function listSims(signal, operation) {
7779
+ async function listSims$1(signal, operation) {
7821
7780
  try {
7822
7781
  return await listAllIosSimsAsync({
7823
7782
  timeoutMs: DEVICE_LIST_TIMEOUT_MS,
@@ -7853,7 +7812,7 @@ async function readMemoryUse(simSnapshot, signal, operation) {
7853
7812
  operation
7854
7813
  });
7855
7814
  const config = loadConfig$1();
7856
- const sims = simSnapshot && !signal ? await snapshotSims(simSnapshot) : await listSims(signal, operation);
7815
+ const sims = simSnapshot && !signal ? await snapshotSims(simSnapshot) : await listSims$1(signal, operation);
7857
7816
  signal?.throwIfAborted();
7858
7817
  const serials = /* @__PURE__ */ new Map();
7859
7818
  for (const record of Object.values(config?.projects ?? {})) for (const { platforms } of projectDeviceSlots(record)) {
@@ -7911,7 +7870,7 @@ async function reclaimIdleDevices({ root, dryRun, note, record, now, env, signal
7911
7870
  const other = isOtherWorkspace(root);
7912
7871
  const collect = async (at) => {
7913
7872
  signal?.throwIfAborted();
7914
- const devices = await collectIdleDevicesAsync(loadConfig$1(), await listSims(signal, operation) ?? [], [], at, env, signal, operation);
7873
+ const devices = await collectIdleDevicesAsync(loadConfig$1(), await listSims$1(signal, operation) ?? [], [], at, env, signal, operation);
7915
7874
  signal?.throwIfAborted();
7916
7875
  return idleShutdownCandidates(devices.filter((device) => other(device.project)), 0);
7917
7876
  };
@@ -7920,7 +7879,7 @@ async function reclaimIdleDevices({ root, dryRun, note, record, now, env, signal
7920
7879
  targets: idle.map(deviceLabel),
7921
7880
  failures: 0
7922
7881
  };
7923
- const { failures, shutDown } = await shutDownIdleDevices(idle, 0, () => collect(Date.now()), stepOutput(note, record), signal, operation);
7882
+ const { failures, shutDown } = await shutDownIdleDevices$1(idle, 0, () => collect(Date.now()), stepOutput(note, record), signal, operation);
7924
7883
  return {
7925
7884
  targets: shutDown.map(deviceLabel),
7926
7885
  failures
@@ -7945,7 +7904,7 @@ async function reclaimIdleDevServers({ root, dryRun, note, record, now, env, sig
7945
7904
  targets: [],
7946
7905
  failures: 0
7947
7906
  };
7948
- const sims = await listSims(signal, operation);
7907
+ const sims = await listSims$1(signal, operation);
7949
7908
  signal?.throwIfAborted();
7950
7909
  if (sims === null) {
7951
7910
  note(chalk.dim(phaseLine("budget", "dev servers left alone: simulators could not be listed to prove them idle")));
@@ -8515,4 +8474,986 @@ function resolveCcache({ root, layout = defaultAndroidLayout(root), dir = shared
8515
8474
  };
8516
8475
  }
8517
8476
  //#endregion
8518
- export { gradleVersionLines as $, memoryCacheKind as $n, INSTALL_ERROR as $t, diskPressureLevel as A, checkEasAuth as An, ensureRemoteBootOwned as At, includesArchives as B, bundleInvocation as Bn, RELEASE_VERIFY_WAIT_MS as Bt, devServerStopLevel as C, gateMetroOrigin as Cn, describeError as Ct, requestDevServerStop as D, RESOLVE_TIMEOUT_MS as Dn, bundleEntryPoint as Dt, metroLastStop as E, runProcessOperation as En, binOnPath as Et, gcOptionsOf as F, providerFromConfig as Fn, withEasProjectLock as Ft, planCacheEmptying as G, easCliSupport as Gn, unattributedLaunchLines as Gt, includesRecordings as H, remoteCommand as Hn, isBundleProof as Ht, maintenanceItem as I, readProjectConfig as In, LAUNCH_BUNDLING as It, selectsBeyondCaches as J, parseArchiveSelector as Jn, verifyLaunch as Jt, previewScopedEmptying as K, easCliUpgradeRemedy as Kn, unverifiedLaunchLines as Kt, openGcLog as L, resolveEasCliBin as Ln, LAUNCH_FATAL as Lt, levelStillShort as M, isEasAuthFailureText as Mn, remoteAndroidDeps as Mt, lowestFree as N, loadProjectProvider as Nn, remoteIosDeps as Nt, vanishedSupervisorMessage as O, UPLOAD_TIMEOUT_MS as On, endRecordedSession as Ot, protectWindowMs as P, ownerFromConfig as Pn, resolveRemoteContext as Pt, deleteGradleVersions as Q, inspectWatchmanMemory as Qn, DEFAULT_METRO_PORT as Qt, maintenanceLogger as R, resolveRemote as Rn, LAUNCH_UNVERIFIED as Rt, describeMetroLastStop as S, readRemoteSimulatorArch as Sn, deleteEasSessions as St, logVanishedSupervisor as T, processContext as Tn, REMOTE_SESSION_ERROR as Tt, includesWorkspaceOutputs as U, MIN_EAS_CLI_DEVICE_VERSION as Un, readCollectorRecords as Ut, includesParkedDevices as V, remoteCliInvocation as Vn, VERIFY_TIMEOUT_MS as Vt, isWorkspacesScope as W, MIN_EAS_CLI_SIMULATOR_VERSION as Wn, readMetroRecords as Wt, collectGradleVersions as X, WATCHMAN_KIND as Xn, APP_READINESS_TIMEOUT_MS as Xt, trimCaches as Y, GRADLE_DAEMONS_KIND as Yn, verifyReleaseLaunch as Yt, deleteGradleVersion as Z, collectMemoryReport as Zn, ADB_INSTALL_TIMEOUT_MS as Zt, resolveBudget as _, reverseMetroPorts as _n, findStaleDeviceRecords as _t, projectCmakeLauncher as a, devClientUrl as an, scopedMemoryReport as ar, DEVICE_LIST_TIMEOUT_MS$1 as at, stopOwnedMetroForReset as b, readBundleSchemes as bn, withAndroidAvdSizes as bt, budgetGate as c, hasMetroReverse as cn, attributeMachineUsage as cr, collectParkedSims as ct, enforceBudget as d, installIosApp as dn, pruneCache as dr, deleteProjectDevices as dt, IOS_DEV_MENU_OFF_DEFAULTS_PLIST as en, memoryLines as er, gradleVersionsPending as et, inspectBudget as f, iosAppProcess as fn, sizeCaches as fr, describeParkedSims as ft, readVolumeSpaceAsync as g, restartedAppNote as gn, findOrphanedDevices as gt, readVolumeSpace as h, launchIosApp as hn, eraseParkedDevices as ht, ccacheInstallCommand as i, clearOtherUserApps as in, reclaimMemory as ir, collectInventory as it, levelFloors as j, easAuthNote as jn, readInstalledEasCliVersionAsync as jt, withdrawDevServerStopRequest as k, cacheLevel as kn, ensureMetroReachable as kt, budgetLine as l, installAndroidApp as ln, readFootprints as lr, deleteParkedAvds as lt, peekBudget as m, launchAndroidReleaseApp as mn, deviceSweepIsScoped as mt, CCACHE_UNAVAILABLE as n, androidDevClientUrl as nn, memorySweepIsScoped as nr, waitForGradleExit as nt, readCcacheActivity as o, deviceShellArg as on, stopIdleGradleDaemons as or, collectDeviceLeases as ot, machineNumber as p, launchAndroidApp as pn, describeUnverifiableDevices as pt, selectCaches as q, ownedSessionName as qn, verifyAndroidReleaseLaunch as qt, ccacheActivityLine as r, appProcessFromPid as rn, memoryWorkPending as rr, getMachineCacheReport as rt, resolveCcache as s, ensureMetroReverse as sn, phase as sr, collectParkedAvds as st, CCACHE_NOT_RUN as t, androidAppProcess as tn, memoryScope as tr, includesGradleVersions as tt, cacheLabel as u, installConflictKind as un, discoverCaches as ur, deleteParkedSims as ut, trimmableCaches as v, writeDebugHttpHost as vn, findStaleProjectDevices as vt, devServerStopRecord as w, exitAfterFlush as wn, withRemoteSessionGcLocks as wt, describeDevServerStop as x, schemesFromInfoPlist as xn, collectEasSessionSweep as xt, stopOwnedMetro as y, pickDevClientScheme as yn, unverifiedDeviceCommand as yt, emptyCaches as z, uploadRemote as zn, MANIFEST_VERIFY_TIMEOUT_MS as zt };
8477
+ //#region src/workspace/display-label.ts
8478
+ function baseLabel({ root, label }) {
8479
+ return label || basename(root);
8480
+ }
8481
+ function pathSegments(root) {
8482
+ return root.split(/[\\/]+/).filter(Boolean).toReversed();
8483
+ }
8484
+ function workspaceLabels(workspaces) {
8485
+ const labels = /* @__PURE__ */ new Map();
8486
+ const groups = /* @__PURE__ */ new Map();
8487
+ for (const workspace of workspaces) {
8488
+ const base = baseLabel(workspace);
8489
+ const group = groups.get(base) ?? [];
8490
+ if (!group.some(({ root }) => root === workspace.root)) group.push(workspace);
8491
+ groups.set(base, group);
8492
+ }
8493
+ for (const [base, group] of groups) {
8494
+ const [only] = group;
8495
+ if (group.length === 1 && only) {
8496
+ labels.set(only.root, base);
8497
+ continue;
8498
+ }
8499
+ const segments = group.map(({ root }) => pathSegments(root));
8500
+ const longest = Math.max(...segments.map((parts) => parts.length));
8501
+ let depth = 0;
8502
+ while (depth < longest && segments.every((parts) => parts[depth] === segments[0]?.[depth])) depth++;
8503
+ const suffixes = segments.map((parts) => parts[depth]);
8504
+ const distinct = new Set(suffixes).size === group.length;
8505
+ group.forEach(({ root }, index) => {
8506
+ const suffix = distinct ? suffixes[index] : root;
8507
+ labels.set(root, suffix ? `${base} (${suffix})` : base);
8508
+ });
8509
+ }
8510
+ return labels;
8511
+ }
8512
+ function workspaceDisplayLabel(root, config) {
8513
+ const registered = Object.entries(config?.projects ?? {}).map(([path, project]) => ({
8514
+ root: path,
8515
+ label: project.label
8516
+ }));
8517
+ const own = registered.find((entry) => entry.root === root);
8518
+ return workspaceLabels([...registered, own ?? { root }]).get(root) ?? basename(root);
8519
+ }
8520
+ //#endregion
8521
+ //#region src/devices/idle-shutdown.ts
8522
+ const SIM_LIST_TIMEOUT_MS = 1e4;
8523
+ const MINUTE_MS = 6e4;
8524
+ /**
8525
+ * How long a booted owned device has been idle, when that is at least `idleMs` and nothing keeps it in use: no
8526
+ * driver, Stim or agent-device lock, or unreadable activity evidence, no build in the workspace, and no activity
8527
+ * (including an open stim-server viewer) for `idleMs`. Null keeps the device running.
8528
+ */
8529
+ function idleShutdownDueMs(activity, { idleMs, now, buildInProgress, platform = process.platform }) {
8530
+ if (buildInProgress || activity.state === "driven") return null;
8531
+ const unknown = platform === "win32" ? activity.basis.filter((basis) => basis !== "driver-process") : activity.basis;
8532
+ if (activity.state === "unknown" && unknown.length > 0) return null;
8533
+ const last = Date.parse(activity.lastActivityAt ?? "");
8534
+ if (!Number.isFinite(last)) return null;
8535
+ const idle = now - last;
8536
+ return idle >= idleMs ? idle : null;
8537
+ }
8538
+ function listSims() {
8539
+ try {
8540
+ return listAllIosSims({ timeoutMs: SIM_LIST_TIMEOUT_MS });
8541
+ } catch {
8542
+ return [];
8543
+ }
8544
+ }
8545
+ /** Booted owned devices idle for `idleMs`, in one workspace or all registered workspaces when root is absent. */
8546
+ function dueIdleDevices(root, idleMs, now = Date.now()) {
8547
+ const config = loadConfig$1();
8548
+ if (!config) return [];
8549
+ const self = root === void 0 ? void 0 : canonicalPath(root);
8550
+ const projects = Object.fromEntries(Object.entries(config.projects).filter(([path]) => {
8551
+ if (self !== void 0 && canonicalPath(path) !== self) return false;
8552
+ const usedAt = workspaceDeviceLastUseAt(path);
8553
+ return !Number.isFinite(usedAt) || now - usedAt >= idleMs;
8554
+ }));
8555
+ if (!Object.keys(projects).length) return [];
8556
+ const builds = new Map(Object.keys(projects).map((path) => [path, workspaceBuildInProgress(path)]));
8557
+ return collectOwnedDeviceActivity({
8558
+ ...config,
8559
+ projects
8560
+ }, listSims(), now).flatMap((device) => {
8561
+ const idleForMs = idleShutdownDueMs(device.activity, {
8562
+ idleMs,
8563
+ now,
8564
+ buildInProgress: builds.get(device.project) ?? true
8565
+ });
8566
+ return idleForMs === null ? [] : [{
8567
+ device,
8568
+ idleForMs
8569
+ }];
8570
+ });
8571
+ }
8572
+ /** Appends an idle shutdown record to the workspace's metro.ndjson, where the supervisor writes its own. */
8573
+ function writeIdleShutdownEvent(root, event) {
8574
+ const writer = createNdjsonWriter(join(workspaceLogsDir$1(root), "metro.ndjson"), { maxBytes: LOG_ROTATE_BYTES });
8575
+ writer.write({
8576
+ src: "metro",
8577
+ ...event
8578
+ });
8579
+ writer.close();
8580
+ }
8581
+ /**
8582
+ * Shuts down, through centralized teardown, each of the workspace's owned devices that is still idle for `idleMs`,
8583
+ * and records why. The caller holds the workspace's native-run lock, so no `ios` or `android` run is booting or
8584
+ * installing on them.
8585
+ */
8586
+ async function shutDownIdleDevices(root, idleMs, log, now = Date.now(), { only, reason } = {}) {
8587
+ let shutDown = 0;
8588
+ for (const { device, idleForMs } of dueIdleDevices(root, idleMs, now)) {
8589
+ if (only && (device.kind !== only.kind || device.id !== only.id || device.project !== only.project || device.slot !== only.slot)) continue;
8590
+ const what = `${device.kind === "ios" ? "simulator" : "emulator"} ${device.name}`;
8591
+ const outcome = device.kind === "ios" ? await teardownOwnedIosSim(device.id, {
8592
+ label: device.name,
8593
+ workspace: root
8594
+ }) : await teardownOwnedAvd(device.id, {
8595
+ owner: {
8596
+ projectPath: device.project,
8597
+ slot: device.slot
8598
+ },
8599
+ workspace: root
8600
+ });
8601
+ if (outcome.status === "torn-down") {
8602
+ shutDown++;
8603
+ const idleMinutes = Math.floor(idleForMs / MINUTE_MS);
8604
+ try {
8605
+ await updateWorkspaceStateAsync(root, (state) => ({
8606
+ ...state,
8607
+ [DEVICE_IDLE_SHUTDOWN_KEY]: {
8608
+ ...readDeviceIdleShutdowns(state),
8609
+ [deviceSlotKey(device.kind, device.slot)]: {
8610
+ at: new Date(now).toISOString(),
8611
+ idleMinutes,
8612
+ ...reason ? { reason } : {}
8613
+ }
8614
+ }
8615
+ }));
8616
+ } catch (error) {
8617
+ log({
8618
+ level: "warn",
8619
+ event: "device_idle_shutdown_failed",
8620
+ msg: `shut down idle ${what} but could not record it: ${error?.message || error}`
8621
+ });
8622
+ }
8623
+ log({
8624
+ level: "info",
8625
+ event: "device_idle_shutdown",
8626
+ msg: `shut down ${what}, idle ${formatLongDuration(idleForMs)}${reason === DEVICE_RECLAIM_REASON ? ", reclaimed for a waiting run" : ""}`
8627
+ });
8628
+ } else if (outcome.status !== "missing") log({
8629
+ level: "warn",
8630
+ event: "device_idle_shutdown_failed",
8631
+ msg: `could not shut down idle ${what}: ${outcome.reason}`,
8632
+ ...outcome.status === "skipped" ? { skipped: true } : {}
8633
+ });
8634
+ }
8635
+ return shutDown;
8636
+ }
8637
+ /** Forgets the slot's idle shutdown once a run boots its device again. */
8638
+ async function clearDeviceIdleShutdown(root, platform, slot = "default") {
8639
+ const key = deviceSlotKey(platform, slot);
8640
+ if (!(key in readDeviceIdleShutdowns(readWorkspaceState$1(root)))) return;
8641
+ await updateWorkspaceStateAsync(root, (state) => {
8642
+ const { [key]: _cleared, ...rest } = readDeviceIdleShutdowns(state);
8643
+ const { [DEVICE_IDLE_SHUTDOWN_KEY]: _all, ...others } = state;
8644
+ return Object.keys(rest).length ? {
8645
+ ...others,
8646
+ [DEVICE_IDLE_SHUTDOWN_KEY]: rest
8647
+ } : others;
8648
+ });
8649
+ }
8650
+ //#endregion
8651
+ //#region src/devices/queue-reclaim.ts
8652
+ async function reclaimIdleDevice(root, idleMs, out, now, skipped, platform) {
8653
+ try {
8654
+ const self = canonicalPath(root);
8655
+ const candidate = dueIdleDevices(void 0, idleMs, now()).filter(({ device }) => {
8656
+ try {
8657
+ return realpathSync(device.project) !== self;
8658
+ } catch {
8659
+ return false;
8660
+ }
8661
+ }).filter(({ device }) => !platform || device.kind === platform).toSorted((a, b) => b.idleForMs - a.idleForMs).find(({ device }) => !skipped.has(`${device.kind}:${device.id}`));
8662
+ if (!candidate) return 0;
8663
+ const { device, idleForMs } = candidate;
8664
+ const label = workspaceDisplayLabel(device.project, loadConfig$1());
8665
+ const log = (event) => {
8666
+ writeIdleShutdownEvent(device.project, event);
8667
+ if (event.level === "warn") out(`${"device".padEnd(11)} ${event.msg}`);
8668
+ };
8669
+ skipped.add(`${device.kind}:${device.id}`);
8670
+ const run = await withIdleWorkspace(device.project, () => shutDownIdleDevices(device.project, idleMs, log, now(), {
8671
+ only: device,
8672
+ reason: DEVICE_RECLAIM_REASON
8673
+ }), {
8674
+ purpose: "device queue reclaim",
8675
+ supervisor: false,
8676
+ managedLocks: false
8677
+ });
8678
+ if (!run.ran) {
8679
+ log({
8680
+ level: "warn",
8681
+ event: "device_idle_shutdown_failed",
8682
+ msg: `could not reclaim ${device.name}: ${run.reasons.join("; ")}`
8683
+ });
8684
+ return 0;
8685
+ }
8686
+ if (!run.value) return 0;
8687
+ skipped.delete(`${device.kind}:${device.id}`);
8688
+ out(`${"device".padEnd(11)} reclaimed ${device.kind === "ios" ? "iOS simulator" : "Android emulator"} ${device.name} (workspace ${label}, idle ${Math.floor(idleForMs / 6e4)}m) for this waiting run`);
8689
+ return run.value;
8690
+ } catch (error) {
8691
+ out(`${"device".padEnd(11)} could not reclaim an idle device: ${error?.message || error}`);
8692
+ return 0;
8693
+ }
8694
+ }
8695
+ //#endregion
8696
+ //#region src/engine/device-capacity.ts
8697
+ const LIVE_SIM_STATES = /* @__PURE__ */ new Set(["Booted", "Booting"]);
8698
+ const ADMISSION_LOCK = "device-admission";
8699
+ const ADMISSION_LOCK_WAIT_MS = 3e5;
8700
+ const LOCK_QUIET_MS = 5e3;
8701
+ const LOCK_PROGRESS_MS = 3e4;
8702
+ const INVENTORY_REFRESH_MS = 1e4;
8703
+ const LISTING_TIMEOUT_MS = 3e4;
8704
+ const NO_ADB = {
8705
+ emulators: [],
8706
+ physical: [],
8707
+ unhealthy: []
8708
+ };
8709
+ var DeviceAdmissionRefusal = class extends Error {
8710
+ code;
8711
+ remedy;
8712
+ constructor(refusal) {
8713
+ super(refusal.message);
8714
+ this.code = refusal.code;
8715
+ this.remedy = refusal.remedy;
8716
+ }
8717
+ };
8718
+ var DeviceCountUnavailable = class extends Error {};
8719
+ function deviceKey(platform, key) {
8720
+ return `${platform}:${key}`;
8721
+ }
8722
+ function bootingDevicesRoot() {
8723
+ return join(getConfigDir(), "device-boots");
8724
+ }
8725
+ function liveEmulatorPorts(adb) {
8726
+ return [...adb.emulators, ...adb.unhealthy.filter((entry) => entry.kind === "emulator")];
8727
+ }
8728
+ function readBootingDevices() {
8729
+ const survey = (0, ownership_claim_exports.readClaimSet)(bootingDevicesRoot());
8730
+ const unresolved = survey.unresolved[0];
8731
+ if (unresolved) throw new ownership_claim_exports.ClaimRefusedError({
8732
+ claimPath: unresolved.path,
8733
+ root: bootingDevicesRoot(),
8734
+ reason: unresolved.reason,
8735
+ label: "device boot"
8736
+ });
8737
+ const booting = [];
8738
+ for (const holder of survey.live) {
8739
+ const { platform, key } = holder.details;
8740
+ if (typeof platform === "string" && typeof key === "string") booting.push({
8741
+ platform,
8742
+ key,
8743
+ displayName: typeof holder.details.displayName === "string" ? holder.details.displayName : void 0
8744
+ });
8745
+ }
8746
+ return booting;
8747
+ }
8748
+ function listSimsForCount() {
8749
+ return process.platform === "darwin" ? listAllIosSims({ timeoutMs: LISTING_TIMEOUT_MS }) : [];
8750
+ }
8751
+ function listAdbForCount() {
8752
+ return listAdbDevices({ timeoutMs: LISTING_TIMEOUT_MS });
8753
+ }
8754
+ function toolAbsent(error) {
8755
+ const { code, status, stderr } = error ?? {};
8756
+ return code === "ENOENT" || status === 127 || /unable to find utility|invalid active developer path|Xcode license/.test(String(stderr ?? ""));
8757
+ }
8758
+ function recordsOwnedEmulator(config) {
8759
+ return Object.values(config?.projects || {}).some((project) => projectDeviceSlots(project).some(({ platforms }) => platforms.android?.owned && typeof platforms.android.consolePort === "number"));
8760
+ }
8761
+ function liveOwnedDeviceKeys({ sims = [], adbEmulators = [], config = null, booting = [], exclude = [] }) {
8762
+ const keys = /* @__PURE__ */ new Set();
8763
+ for (const sim of sims) {
8764
+ if (!sim?.name?.startsWith("stim-")) continue;
8765
+ if (exclude.some((device) => device.platform === "ios" && device.key === sim.udid)) continue;
8766
+ if (LIVE_SIM_STATES.has(sim.state)) keys.add(deviceKey("ios", sim.udid));
8767
+ }
8768
+ const livePorts = new Set(adbEmulators.map((e) => e.consolePort));
8769
+ for (const proj of Object.values(config?.projects || {})) for (const { platforms } of projectDeviceSlots(proj)) {
8770
+ const android = platforms.android;
8771
+ if (android?.owned && android.avdName && typeof android.consolePort === "number" && livePorts.has(android.consolePort)) keys.add(deviceKey("android", android.avdName));
8772
+ }
8773
+ for (const device of booting) if (!exclude.some((excluded) => excluded.platform === device.platform && excluded.key === device.key)) keys.add(deviceKey(device.platform, device.key));
8774
+ return keys;
8775
+ }
8776
+ function inventoryKeys(inventory, exclude = []) {
8777
+ return liveOwnedDeviceKeys({
8778
+ ...inventory,
8779
+ adbEmulators: liveEmulatorPorts(inventory.adb),
8780
+ exclude
8781
+ });
8782
+ }
8783
+ const PLATFORM_CAPS = {
8784
+ ios: {
8785
+ setting: "concurrency.maxIosDevices",
8786
+ noun: "iOS simulator"
8787
+ },
8788
+ android: {
8789
+ setting: "concurrency.maxAndroidDevices",
8790
+ noun: "Android emulator"
8791
+ }
8792
+ };
8793
+ function isCappedPlatform(platform) {
8794
+ return platform === "ios" || platform === "android";
8795
+ }
8796
+ function platformCount(keys, platform) {
8797
+ return [...keys].filter((key) => key.startsWith(`${platform}:`)).length;
8798
+ }
8799
+ /** The caps that apply to a boot on `platform` given the booted `keys`, the platform's own cap first. */
8800
+ function deviceLimits(keys, platform, max, platformMax) {
8801
+ const limits = [];
8802
+ if (platformMax > 0 && isCappedPlatform(platform)) limits.push({
8803
+ ...PLATFORM_CAPS[platform],
8804
+ count: platformCount(keys, platform),
8805
+ max: platformMax,
8806
+ platform
8807
+ });
8808
+ if (max > 0) limits.push({
8809
+ setting: "concurrency.maxDevices",
8810
+ noun: "device",
8811
+ count: keys.size,
8812
+ max
8813
+ });
8814
+ return limits;
8815
+ }
8816
+ /** Whether a queued waiter is held back by its own platform cap, so it does not hold up the runs behind it. */
8817
+ function platformWalled(keys, waiter, platformMax) {
8818
+ const platform = String(waiter.details.platform);
8819
+ if (!isCappedPlatform(platform)) return false;
8820
+ const cap = platformMax[platform] ?? 0;
8821
+ return cap > 0 && platformCount(keys, platform) >= cap;
8822
+ }
8823
+ /** The first cap with no room left, or the first cap when all have room (for a run waiting behind another). */
8824
+ function bindingLimit(limits) {
8825
+ return limits.find((limit) => limit.count >= limit.max) ?? limits[0];
8826
+ }
8827
+ function atCapacityRefusal({ count, max, setting, noun }) {
8828
+ return {
8829
+ code: "STIM_AT_CAPACITY",
8830
+ message: count < max ? `${count}/${max} Stim ${noun}s are in use, but another run is ahead in the device slot queue.` : `${count} Stim ${noun}(s) are already booted and ${setting} is ${max}, so booting another would exceed the cap.`,
8831
+ remedy: `stop an environment (stim stop) or raise ${setting}`
8832
+ };
8833
+ }
8834
+ function uncountedRefusal(error) {
8835
+ return {
8836
+ code: "STIM_NO_DEVICE",
8837
+ message: `A device cap (concurrency.maxDevices, concurrency.maxIosDevices or concurrency.maxAndroidDevices) is set, and Stim could not count the booted devices: ${error?.message || error}`,
8838
+ remedy: `Listing devices times out when the machine is overloaded: retry once the load falls. Otherwise run \`stim doctor\` to check the simulator and adb toolchains, and check that ${join(getConfigDir(), "config.json")} and ${bootingDevicesRoot()} are readable.`
8839
+ };
8840
+ }
8841
+ function listed(source, empty, absent = () => false) {
8842
+ try {
8843
+ return (typeof source === "function" ? source() : source) ?? empty;
8844
+ } catch (error) {
8845
+ if (absent(error)) return empty;
8846
+ throw new DeviceCountUnavailable("", { cause: error });
8847
+ }
8848
+ }
8849
+ async function listedAwait(read, empty, absent = () => false) {
8850
+ try {
8851
+ return await read() ?? empty;
8852
+ } catch (error) {
8853
+ if (absent(error)) return empty;
8854
+ throw new DeviceCountUnavailable("", { cause: error });
8855
+ }
8856
+ }
8857
+ async function readInventoryAsync({ sims = () => process.platform === "darwin" ? listAllIosSimsAsync({ timeoutMs: LISTING_TIMEOUT_MS }) : [], adb = () => listAdbDevicesAsync({ timeoutMs: LISTING_TIMEOUT_MS }), config = loadConfig$1, booting = readBootingDevices }) {
8858
+ try {
8859
+ const recorded = await listedAwait(() => typeof config === "function" ? config() : config, null);
8860
+ return {
8861
+ booting: await listedAwait(() => typeof booting === "function" ? booting() : booting, []),
8862
+ sims: await listedAwait(() => typeof sims === "function" ? sims() : sims, [], toolAbsent),
8863
+ adb: recordsOwnedEmulator(recorded) ? await listedAwait(() => typeof adb === "function" ? adb() : adb, NO_ADB, toolAbsent) : NO_ADB,
8864
+ config: recorded
8865
+ };
8866
+ } catch (error) {
8867
+ if (error instanceof DeviceCountUnavailable) {
8868
+ if ((0, ownership_claim_exports.isClaimRefusal)(error.cause)) throw error.cause;
8869
+ return uncountedRefusal(error.cause);
8870
+ }
8871
+ throw error;
8872
+ }
8873
+ }
8874
+ function readInventory({ sims = listSimsForCount, adb = listAdbForCount, config = loadConfig$1, booting = readBootingDevices }) {
8875
+ try {
8876
+ const recorded = listed(config, null);
8877
+ return {
8878
+ booting: listed(booting, []),
8879
+ sims: listed(sims, [], toolAbsent),
8880
+ adb: recordsOwnedEmulator(recorded) ? listed(adb, NO_ADB, toolAbsent) : NO_ADB,
8881
+ config: recorded
8882
+ };
8883
+ } catch (error) {
8884
+ if (error instanceof DeviceCountUnavailable) {
8885
+ if ((0, ownership_claim_exports.isClaimRefusal)(error.cause)) throw error.cause;
8886
+ return uncountedRefusal(error.cause);
8887
+ }
8888
+ throw error;
8889
+ }
8890
+ }
8891
+ /**
8892
+ * Owned simulators that are booted or booting, owned emulators adb lists in any state, and devices another
8893
+ * run is booting under `concurrency.maxDevices`, each counted once unless excluded; when a listing fails, why the count is unknown.
8894
+ */
8895
+ function countLiveOwnedDevices(sources = {}, { exclude = [] } = {}) {
8896
+ const counts = countLiveOwnedDevicesByPlatform(sources, { exclude });
8897
+ return "unknown" in counts ? counts : counts.ios + counts.android;
8898
+ }
8899
+ /** The counts of {@link countLiveOwnedDevices} split by platform. */
8900
+ function countLiveOwnedDevicesByPlatform(sources = {}, { exclude = [] } = {}) {
8901
+ try {
8902
+ const inventory = readInventory(sources);
8903
+ if ("code" in inventory) return { unknown: inventory.message };
8904
+ const keys = inventoryKeys(inventory, exclude);
8905
+ return {
8906
+ ios: platformCount(keys, "ios"),
8907
+ android: platformCount(keys, "android")
8908
+ };
8909
+ } catch (error) {
8910
+ if ((0, ownership_claim_exports.isClaimRefusal)(error)) return { unknown: error.message };
8911
+ throw error;
8912
+ }
8913
+ }
8914
+ async function listedAsync(source, empty) {
8915
+ try {
8916
+ return await source();
8917
+ } catch (error) {
8918
+ if (toolAbsent(error)) return empty;
8919
+ throw new DeviceCountUnavailable("", { cause: error });
8920
+ }
8921
+ }
8922
+ async function countLiveOwnedDevicesFrom(sources, { exclude = [] } = {}) {
8923
+ try {
8924
+ const inventory = await readInventoryAsync(sources);
8925
+ return "code" in inventory ? { unknown: inventory.message } : inventoryKeys(inventory, exclude).size;
8926
+ } catch (error) {
8927
+ if ((0, ownership_claim_exports.isClaimRefusal)(error)) return { unknown: error.message };
8928
+ throw error;
8929
+ }
8930
+ }
8931
+ async function countLiveOwnedDevicesAsync(operation) {
8932
+ try {
8933
+ operation.signal.throwIfAborted();
8934
+ const config = loadConfig$1();
8935
+ const booting = readBootingDevices();
8936
+ const [sims, adb] = await Promise.all([process.platform === "darwin" ? listedAsync(() => listAllIosSimsAsync({ timeoutMs: LISTING_TIMEOUT_MS }), []) : [], recordsOwnedEmulator(config) ? listedAsync(() => listAdbDevicesAsync({ timeoutMs: LISTING_TIMEOUT_MS }), NO_ADB) : NO_ADB]);
8937
+ operation.signal.throwIfAborted();
8938
+ return countLiveOwnedDevices({
8939
+ config,
8940
+ booting,
8941
+ sims,
8942
+ adb
8943
+ });
8944
+ } catch (error) {
8945
+ operation.signal.throwIfAborted();
8946
+ if ((0, ownership_claim_exports.isClaimRefusal)(error)) return { unknown: error.message };
8947
+ if (error instanceof DeviceCountUnavailable) return { unknown: uncountedRefusal(error.cause).message };
8948
+ throw error;
8949
+ }
8950
+ }
8951
+ function workspaceHasLiveDevice({ platform, project, slot = "default", sims = [], adbEmulators = [] } = {}) {
8952
+ if (!platform) return false;
8953
+ const record = deviceSlotPlatforms(project, slot)?.[platform];
8954
+ if (!record) return false;
8955
+ if (platform === "ios") return sims.some((s) => s.udid === record.deviceUdid && LIVE_SIM_STATES.has(s.state));
8956
+ return typeof record.consolePort === "number" && adbEmulators.some((e) => e.consolePort === record.consolePort);
8957
+ }
8958
+ function deviceCapacityRefusal({ platform, project, slot = "default", max, platformMax, sims = [], adb = null, config = null, booting = [] } = {}) {
8959
+ if (!(max && max > 0) && !(platformMax && platformMax > 0)) return null;
8960
+ const adbEmulators = liveEmulatorPorts(adb ?? NO_ADB);
8961
+ if (workspaceHasLiveDevice({
8962
+ platform,
8963
+ project,
8964
+ slot,
8965
+ sims,
8966
+ adbEmulators
8967
+ })) return null;
8968
+ const full = deviceLimits(liveOwnedDeviceKeys({
8969
+ sims,
8970
+ adbEmulators,
8971
+ config,
8972
+ booting
8973
+ }), platform ?? "", max ?? 0, platformMax ?? 0).find((limit) => limit.count >= limit.max);
8974
+ return full ? {
8975
+ ...atCapacityRefusal(full),
8976
+ max: full.max
8977
+ } : null;
8978
+ }
8979
+ /**
8980
+ * The early `concurrency.maxDevices` check, before Metro starts or a device is created. It is advisory, so an
8981
+ * unknown count passes: `withDeviceBootAdmission` makes the binding decision when the boot starts.
8982
+ */
8983
+ async function checkDeviceCapacity({ platform, project, slot = "default", max, platformMax = platform && isCappedPlatform(platform) ? getPlatformDeviceLimits()[platform] : 0, ...sources } = {}) {
8984
+ if (!(max && max > 0) && !(platformMax && platformMax > 0)) return null;
8985
+ let inventory;
8986
+ try {
8987
+ inventory = await readInventoryAsync(sources);
8988
+ } catch (error) {
8989
+ if ((0, ownership_claim_exports.isClaimRefusal)(error)) return null;
8990
+ throw error;
8991
+ }
8992
+ if ("code" in inventory) return null;
8993
+ return deviceCapacityRefusal({
8994
+ platform,
8995
+ project,
8996
+ slot,
8997
+ max,
8998
+ platformMax,
8999
+ ...inventory
9000
+ });
9001
+ }
9002
+ async function peekDeviceSlots({ platform, project, slot = "default", max = getConcurrencyLimits().maxDevices, platformMax = getPlatformDeviceLimits()[platform], sources = {} }) {
9003
+ try {
9004
+ const inventory = await readInventoryAsync(sources);
9005
+ if ("code" in inventory) return {
9006
+ count: null,
9007
+ max,
9008
+ queued: 0,
9009
+ localLive: false
9010
+ };
9011
+ const owned = deviceSlotPlatforms(project, slot)?.[platform]?.owned === true;
9012
+ const keys = inventoryKeys(inventory);
9013
+ const limits = deviceLimits(keys, platform, max, platformMax);
9014
+ const tightest = limits.length ? limits.reduce((a, b) => b.max - b.count < a.max - a.count ? b : a) : void 0;
9015
+ return {
9016
+ count: tightest?.count ?? keys.size,
9017
+ max: tightest?.max ?? max,
9018
+ queued: liveWaiters().filter((waiter) => !platformWalled(keys, waiter, {
9019
+ ...getPlatformDeviceLimits(),
9020
+ [platform]: platformMax
9021
+ })).length,
9022
+ localLive: owned && workspaceHasLiveDevice({
9023
+ platform,
9024
+ project,
9025
+ slot,
9026
+ sims: inventory.sims,
9027
+ adbEmulators: liveEmulatorPorts(inventory.adb)
9028
+ }),
9029
+ ...tightest?.platform ? { limit: tightest.setting } : {}
9030
+ };
9031
+ } catch (error) {
9032
+ if (!(0, ownership_claim_exports.isClaimRefusal)(error)) throw error;
9033
+ return {
9034
+ count: null,
9035
+ max,
9036
+ queued: 0,
9037
+ localLive: false
9038
+ };
9039
+ }
9040
+ }
9041
+ const DEFAULT_DEVICE_SLOT_WAIT_MS = 6e5;
9042
+ function deviceSlotWaitingLine({ count, max, holders, elapsedMs, queue, setting }) {
9043
+ return `${"device".padEnd(11)} waiting for a device slot (${count}/${max} in use${setting ? ` by ${setting}` : ""}${holders.length ? `: ${holders.join(", ")}` : ""}), ${formatElapsed(elapsedMs)} elapsed -- stim guide lifecycle concurrency${queue ? `; ${queue}` : ""}`;
9044
+ }
9045
+ function unrecordedHolder(label) {
9046
+ return {
9047
+ label,
9048
+ workspace: null,
9049
+ slot: null
9050
+ };
9051
+ }
9052
+ function slotHolders(inventory, keys) {
9053
+ const owners = /* @__PURE__ */ new Map();
9054
+ const projects = Object.entries(inventory.config?.projects ?? {});
9055
+ const labels = workspaceLabels(projects.map(([root, project]) => ({
9056
+ root,
9057
+ label: project.label
9058
+ })));
9059
+ for (const [root, project] of projects) {
9060
+ const label = labels.get(root) ?? basename(root);
9061
+ for (const { slot, platforms } of projectDeviceSlots(project)) {
9062
+ if (platforms.ios?.deviceUdid) owners.set(deviceKey("ios", platforms.ios.deviceUdid), {
9063
+ label,
9064
+ workspace: root,
9065
+ slot
9066
+ });
9067
+ if (platforms.android?.avdName) owners.set(deviceKey("android", platforms.android.avdName), {
9068
+ label,
9069
+ workspace: root,
9070
+ slot
9071
+ });
9072
+ }
9073
+ }
9074
+ for (const sim of inventory.sims) {
9075
+ const key = deviceKey("ios", sim.udid);
9076
+ if (!owners.has(key)) owners.set(key, unrecordedHolder(sim.name));
9077
+ }
9078
+ const booting = /* @__PURE__ */ new Set();
9079
+ for (const device of inventory.booting) {
9080
+ const key = deviceKey(device.platform, device.key);
9081
+ booting.add(key);
9082
+ if (!owners.has(key)) owners.set(key, unrecordedHolder(device.displayName ?? device.key));
9083
+ }
9084
+ return [...keys].map((key) => {
9085
+ const { label, workspace, slot } = owners.get(key) ?? unrecordedHolder(key.slice(key.indexOf(":") + 1));
9086
+ const platform = key.startsWith("android:") ? "android" : "ios";
9087
+ return booting.has(key) ? {
9088
+ platform,
9089
+ label,
9090
+ workspace,
9091
+ slot,
9092
+ booting: true
9093
+ } : {
9094
+ platform,
9095
+ label,
9096
+ workspace,
9097
+ slot
9098
+ };
9099
+ }).toSorted((a, b) => a.label.localeCompare(b.label) || a.platform.localeCompare(b.platform));
9100
+ }
9101
+ function deviceHolders(inventory, keys) {
9102
+ return [...new Set(slotHolders(inventory, keys).map((holder) => holder.label))].toSorted();
9103
+ }
9104
+ /**
9105
+ * `concurrency.maxDevices` usage for status (the owned devices the cap counts and who holds each) and the usage of each
9106
+ * per-platform cap that is set, from one inventory read. `sims` is the listing status already read; an unknown count
9107
+ * reports why instead of failing.
9108
+ */
9109
+ async function deviceSlotUsages(max, platformMax, sources = {}) {
9110
+ const capped = [];
9111
+ for (const platform of ["ios", "android"]) {
9112
+ const cap = platformMax[platform] ?? 0;
9113
+ if (cap > 0) capped.push([platform, cap]);
9114
+ }
9115
+ const platformDevices = (count) => capped.length ? { platformDevices: Object.fromEntries(capped.map(([platform, cap]) => [platform, {
9116
+ max: cap,
9117
+ ...count(platform)
9118
+ }])) } : {};
9119
+ try {
9120
+ const inventory = await readInventoryAsync(sources);
9121
+ if ("code" in inventory) return {
9122
+ ...max > 0 ? { devices: {
9123
+ max,
9124
+ inUse: null,
9125
+ unknown: inventory.message,
9126
+ holders: []
9127
+ } } : {},
9128
+ ...platformDevices(() => ({
9129
+ inUse: null,
9130
+ unknown: inventory.message
9131
+ }))
9132
+ };
9133
+ const keys = inventoryKeys(inventory);
9134
+ return {
9135
+ ...max > 0 ? { devices: {
9136
+ max,
9137
+ inUse: keys.size,
9138
+ holders: slotHolders(inventory, keys)
9139
+ } } : {},
9140
+ ...platformDevices((platform) => ({ inUse: platformCount(keys, platform) }))
9141
+ };
9142
+ } catch (error) {
9143
+ if (!(0, ownership_claim_exports.isClaimRefusal)(error)) throw error;
9144
+ return {
9145
+ ...max > 0 ? { devices: {
9146
+ max,
9147
+ inUse: null,
9148
+ unknown: error.message,
9149
+ holders: []
9150
+ } } : {},
9151
+ ...platformDevices(() => ({
9152
+ inUse: null,
9153
+ unknown: error.message
9154
+ }))
9155
+ };
9156
+ }
9157
+ }
9158
+ function liveWaiters() {
9159
+ const root = join(getConfigDir(), "device-waits");
9160
+ const survey = (0, ownership_claim_exports.inspectClaimSet)(root, { label: "device wait" });
9161
+ if (survey.exclusive) throw new ownership_claim_exports.ClaimRefusedError({
9162
+ root,
9163
+ claimPath: survey.exclusive.path,
9164
+ reason: "the wait queue is held exclusively",
9165
+ label: "device wait"
9166
+ });
9167
+ return survey.shared.toSorted((a, b) => (Number(a.details.sequence) || 0) - (Number(b.details.sequence) || 0) || a.startedAt.localeCompare(b.startedAt) || a.claimId.localeCompare(b.claimId));
9168
+ }
9169
+ function takeWaitTicket(device, waiters) {
9170
+ const root = join(getConfigDir(), "device-waits");
9171
+ const attempt = (0, ownership_claim_exports.tryAcquireClaim)({
9172
+ root,
9173
+ mode: "shared",
9174
+ label: "device wait",
9175
+ details: {
9176
+ ...device,
9177
+ sequence: Math.max(0, ...waiters.map((holder) => Number(holder.details.sequence) || 0)) + 1
9178
+ }
9179
+ });
9180
+ if (attempt.acquired) return attempt.acquired;
9181
+ throw new ownership_claim_exports.ClaimRefusedError({
9182
+ root,
9183
+ claimPath: attempt.held?.path ?? root,
9184
+ reason: "the wait queue is held exclusively",
9185
+ label: "device wait"
9186
+ });
9187
+ }
9188
+ function takeBootMarker(device) {
9189
+ const attempt = (0, ownership_claim_exports.tryAcquireClaim)({
9190
+ root: bootingDevicesRoot(),
9191
+ mode: "shared",
9192
+ label: "device boot",
9193
+ details: { ...device }
9194
+ });
9195
+ if (attempt.acquired) return attempt.acquired;
9196
+ throw new DeviceAdmissionRefusal({
9197
+ code: "STIM_NO_DEVICE",
9198
+ message: `Another process holds ${bootingDevicesRoot()} exclusively, so this boot cannot be counted toward the device caps.`,
9199
+ remedy: "Retry once that process finishes."
9200
+ });
9201
+ }
9202
+ async function admissionTransaction(action, lockWaitMs, out, signal) {
9203
+ const started = Date.now();
9204
+ let lastLine = null;
9205
+ const onHeld = () => {
9206
+ const elapsed = Date.now() - started;
9207
+ if (elapsed < LOCK_QUIET_MS || lastLine !== null && Date.now() - lastLine < LOCK_PROGRESS_MS) return;
9208
+ lastLine = Date.now();
9209
+ out(phaseLine("device", `waiting for other runs to finish counting booted devices (${formatElapsed(elapsed)})`));
9210
+ };
9211
+ try {
9212
+ return await withWorkspaceProcessLock(getConfigDir(), ADMISSION_LOCK, action, {
9213
+ external: true,
9214
+ waitMs: lockWaitMs,
9215
+ onHeld,
9216
+ signal
9217
+ });
9218
+ } catch (error) {
9219
+ if (workspaceProcessLockError(error) !== "timeout") throw error;
9220
+ throw new DeviceAdmissionRefusal({
9221
+ code: "STIM_NO_DEVICE",
9222
+ message: `Waited ${formatElapsed(lockWaitMs)} for other runs to finish counting booted devices for the device caps.`,
9223
+ remedy: "Listing devices is slow, which usually means the machine is overloaded. Retry once the load falls."
9224
+ });
9225
+ }
9226
+ }
9227
+ /** Boots an owned device under the Stim home's cap, reserving capacity before releasing admission. */
9228
+ async function withDeviceBootAdmission(device, boot, { root, max = getConcurrencyLimits().maxDevices, platformMax = getPlatformDeviceLimits(), sources = {}, lockWaitMs = ADMISSION_LOCK_WAIT_MS, out = () => {}, waitMs = DEFAULT_DEVICE_SLOT_WAIT_MS, noWait = false, automatic = false, displayName = workspaceDisplayLabel(root, loadConfig$1()), reclaimIdleMinutes = deviceReclaimIdleMinutesSetting({}), now = Date.now, signal, sleep = (ms) => setTimeout$1(ms, void 0, { signal }), waitingFor = () => {}, onWait = () => {}, onBootStart = () => {} }) {
9229
+ const requireMembership = () => {
9230
+ if (automatic && !automaticMachineEnabled("device", "local")) throw new DeviceAdmissionRefusal({
9231
+ code: "STIM_HOSTING_REFUSED",
9232
+ message: "local is disabled in this requester's automatic device pool.",
9233
+ remedy: "Enable local in remote.devicePoolDisabled or select a machine explicitly."
9234
+ });
9235
+ };
9236
+ signal?.throwIfAborted();
9237
+ requireMembership();
9238
+ const ownCap = isCappedPlatform(device.platform) ? platformMax[device.platform] ?? 0 : 0;
9239
+ if (!(max > 0) && !(ownCap > 0)) {
9240
+ onBootStart();
9241
+ return boot();
9242
+ }
9243
+ const workspace = workspaceId(root);
9244
+ device = {
9245
+ ...device,
9246
+ workspace,
9247
+ displayName
9248
+ };
9249
+ let ticket;
9250
+ let marker;
9251
+ let started;
9252
+ let lastLine = -Infinity;
9253
+ let visibleWait = false;
9254
+ let lastInventory;
9255
+ let lastTurn = false;
9256
+ let binding;
9257
+ let lastListed = -Infinity;
9258
+ let reclaimed = 0;
9259
+ let lastReclaim = -Infinity;
9260
+ const skippedReclaims = /* @__PURE__ */ new Set();
9261
+ try {
9262
+ try {
9263
+ for (;;) {
9264
+ const result = await admissionTransaction(async () => {
9265
+ signal?.throwIfAborted();
9266
+ requireMembership();
9267
+ const waiters = liveWaiters();
9268
+ const first = waiters[0];
9269
+ const mine = waiters.findIndex((holder) => holder.claimId === ticket?.claimId);
9270
+ const ahead = mine < 0 ? waiters : waiters.slice(0, mine);
9271
+ const admit = () => {
9272
+ const admitted = takeBootMarker(device);
9273
+ if ((0, ownership_claim_exports.releaseClaim)(ticket)) ticket = void 0;
9274
+ return { marker: admitted };
9275
+ };
9276
+ const wait = ({ limit, holders }, turn) => {
9277
+ const ticketId = ticket?.claimId;
9278
+ const position = ticketId ? waiters.findIndex((holder) => holder.claimId === ticketId) + 1 : waiters.length + 1;
9279
+ const headName = first?.details.displayName ?? displayName;
9280
+ const queue = `queue position ${position}${position > 1 ? " behind" : ", head"} ${headName}; queue: ${join(getConfigDir(), "device-waits")}`;
9281
+ if (noWait || waitMs === 0) throw new DeviceAdmissionRefusal(atCapacityRefusal(limit));
9282
+ if (started !== void 0 && now() - started >= waitMs) throw new DeviceAdmissionRefusal({
9283
+ code: "STIM_AT_CAPACITY",
9284
+ message: `Waited ${formatElapsed(now() - started)} for a device slot; ${limit.count}/${limit.max} Stim ${limit.noun}s are in use (${limit.setting}); ${queue}.`,
9285
+ remedy: `Stop an environment (stim stop), retry with a longer --wait <seconds>, or raise ${limit.setting}.`
9286
+ });
9287
+ if (!ticket) {
9288
+ ticket = takeWaitTicket(device, waiters);
9289
+ started = now();
9290
+ }
9291
+ const firstOfPlatform = ahead.every((holder) => holder.details.platform !== device.platform);
9292
+ return {
9293
+ limit,
9294
+ holders,
9295
+ queue,
9296
+ reclaim: turn && firstOfPlatform && limit.count >= limit.max
9297
+ };
9298
+ };
9299
+ if (ticket && lastInventory && ahead.length > 0 && !lastTurn && now() - lastListed < INVENTORY_REFRESH_MS) {
9300
+ const booting = sources.booting ?? readBootingDevices;
9301
+ const own = deviceKey(device.platform, device.key);
9302
+ if ((typeof booting === "function" ? booting() : booting).some((entry) => deviceKey(entry.platform, entry.key) === own)) return admit();
9303
+ return wait(lastInventory, false);
9304
+ }
9305
+ const inventory = await readInventoryAsync(sources);
9306
+ if ("code" in inventory) throw new DeviceAdmissionRefusal(inventory);
9307
+ const keys = inventoryKeys(inventory);
9308
+ const own = deviceKey(device.platform, device.key);
9309
+ const ownBooting = inventory.booting.some((entry) => deviceKey(entry.platform, entry.key) === own);
9310
+ if (device.platform === "ios" && keys.has(own) || ownBooting) return admit();
9311
+ keys.delete(own);
9312
+ const limits = deviceLimits(keys, device.platform, max, ownCap);
9313
+ const turn = ahead.every((waiter) => platformWalled(keys, waiter, platformMax));
9314
+ if (turn && limits.every((limit) => limit.count < limit.max)) return admit();
9315
+ lastListed = now();
9316
+ lastTurn = turn;
9317
+ const limit = bindingLimit(limits);
9318
+ if (!limit) return admit();
9319
+ binding = limit;
9320
+ lastInventory = {
9321
+ limit,
9322
+ holders: deviceHolders(inventory, limit.platform ? new Set([...keys].filter((key) => key.startsWith(`${limit.platform}:`))) : keys)
9323
+ };
9324
+ return wait(lastInventory, turn);
9325
+ }, lockWaitMs, out, signal);
9326
+ if ("marker" in result) {
9327
+ marker = result.marker;
9328
+ signal?.throwIfAborted();
9329
+ break;
9330
+ }
9331
+ signal?.throwIfAborted();
9332
+ const elapsedMs = now() - started;
9333
+ visibleWait = true;
9334
+ waitingFor({
9335
+ kind: "device-slot",
9336
+ inUse: result.limit.count,
9337
+ max: result.limit.max,
9338
+ since: new Date(started).toISOString()
9339
+ });
9340
+ if (now() - lastLine >= 1e4) {
9341
+ lastLine = now();
9342
+ out(deviceSlotWaitingLine({
9343
+ count: result.limit.count,
9344
+ max: result.limit.max,
9345
+ holders: result.holders,
9346
+ elapsedMs,
9347
+ queue: result.queue,
9348
+ ...result.limit.platform ? { setting: result.limit.setting } : {}
9349
+ }));
9350
+ }
9351
+ signal?.throwIfAborted();
9352
+ if (result.reclaim && now() - lastReclaim >= 15e3) {
9353
+ lastReclaim = now();
9354
+ try {
9355
+ if (reclaimIdleMinutes > 0) reclaimed += await reclaimIdleDevice(root, reclaimIdleMinutes * 6e4, out, now, skippedReclaims, result.limit.platform);
9356
+ } catch (error) {
9357
+ out(phaseLine("device", `could not reclaim an idle device: ${error?.message || error}`));
9358
+ }
9359
+ }
9360
+ await sleep(Math.min(2e3, Math.max(0, waitMs - elapsedMs)));
9361
+ }
9362
+ } catch (error) {
9363
+ if (error instanceof DeviceAdmissionRefusal && error.code === "STIM_AT_CAPACITY") {
9364
+ const at = now();
9365
+ await withConfigLockAsync$1(() => recordCapacityRefusal({
9366
+ platform: device.platform,
9367
+ max: binding?.max ?? max,
9368
+ workspace
9369
+ }, at)).catch(() => {});
9370
+ }
9371
+ throw error;
9372
+ } finally {
9373
+ (0, ownership_claim_exports.releaseClaim)(ticket);
9374
+ if (visibleWait) waitingFor(null);
9375
+ if (started !== void 0) {
9376
+ const ms = Math.max(0, Math.round(now() - started));
9377
+ const at = now();
9378
+ await withConfigLockAsync$1(() => recordCapacityWait({
9379
+ platform: device.platform,
9380
+ ms,
9381
+ max: binding?.max ?? max,
9382
+ workspace,
9383
+ ...reclaimed ? { reclaimed } : {}
9384
+ }, at)).catch(() => {});
9385
+ onWait(ms);
9386
+ }
9387
+ }
9388
+ requireMembership();
9389
+ onBootStart();
9390
+ return await boot();
9391
+ } finally {
9392
+ (0, ownership_claim_exports.releaseClaim)(marker);
9393
+ }
9394
+ }
9395
+ function deviceTypeMismatch(recordedTypeId, requestedName, deviceTypes) {
9396
+ if (!requestedName || !recordedTypeId) return null;
9397
+ const wanted = (deviceTypes || []).find((d) => d.name === requestedName);
9398
+ if (!wanted) return null;
9399
+ if (wanted.identifier === recordedTypeId) return null;
9400
+ const recorded = (deviceTypes || []).find((d) => d.identifier === recordedTypeId);
9401
+ return `this project's sim is ${recorded ? recorded.name : recordedTypeId}, but --device-type asked for ${requestedName}`;
9402
+ }
9403
+ function runtimeMismatch(recordedRuntimeId, requested, runtimes) {
9404
+ if (!requested || !recordedRuntimeId) return null;
9405
+ const wanted = runtimes.find((r) => iosRuntimeMatches(r, requested));
9406
+ if (!wanted || wanted.identifier === recordedRuntimeId) return null;
9407
+ return `this project's sim runs iOS ${parseRuntimeVersion(recordedRuntimeId)}, but --runtime asked for ${requested}`;
9408
+ }
9409
+ function installedNames(names) {
9410
+ const unique = [...new Set(names.filter((n) => typeof n === "string" && n !== ""))];
9411
+ return unique.length > 0 ? unique.join(", ") : "none";
9412
+ }
9413
+ /** Appends which settings layer supplied an unresolved device selector, so the user knows where to fix it. */
9414
+ function layerNote(refusal, key, flag, origin) {
9415
+ if (flag !== void 0 && flag !== null) return refusal;
9416
+ if (!origin) return refusal;
9417
+ return {
9418
+ message: `${refusal.message} ${key} is set at the ${origin} layer.`,
9419
+ remedy: `${refusal.remedy} Fix it with \`stim settings set ${key} <value> --scope ${origin}\` or \`stim settings unset ${key} --scope ${origin}\`.`
9420
+ };
9421
+ }
9422
+ function unknownIosRuntimeRefusal(requested, runtimes) {
9423
+ if (!requested) return null;
9424
+ if ((runtimes || []).some((r) => iosRuntimeMatches(r, requested))) return null;
9425
+ return {
9426
+ message: `No installed simulator runtime matches "${requested}". Installed runtimes: ${installedNames((runtimes || []).map((r) => r.version))}.`,
9427
+ remedy: "Pass `--runtime` (or set ios.runtime) a version printed above (\"26.5\") or a runtime's full name (\"iOS 26.5\"); nothing else matches. Install more runtimes through Xcode."
9428
+ };
9429
+ }
9430
+ function creatableIosDeviceTypeNames(runtimes, runtime) {
9431
+ return (runtime ? (runtimes || []).filter((r) => iosRuntimeMatches(r, runtime)) : runtimes || []).flatMap((r) => (r.supportedDeviceTypes || []).map((d) => d.name));
9432
+ }
9433
+ function unknownIosDeviceTypeRefusal(requested, runtimes, runtime) {
9434
+ if (!requested) return null;
9435
+ const creatable = creatableIosDeviceTypeNames(runtimes, runtime);
9436
+ if (creatable.includes(requested)) return null;
9437
+ return {
9438
+ message: `No device type named "${requested}" can be created on ${runtime ? `runtime ${runtime}` : "any installed simulator runtime"}. ${runtime ? `Device types runtime ${runtime} supports` : "Device types the installed runtimes support"}: ${installedNames(creatable)}.`,
9439
+ remedy: "Pass `--device-type` (or set ios.deviceType) one of the names printed above, exactly as `xcrun simctl list devicetypes` spells it. `xcrun simctl list devicetypes` also lists watchOS, tvOS and visionOS models, which no iOS runtime can create. Install more models through Xcode."
9440
+ };
9441
+ }
9442
+ function unknownAndroidSystemImageRefusal(requested, images) {
9443
+ if (!requested) return null;
9444
+ if ((images || []).some((i) => i.pkg === requested)) return null;
9445
+ return {
9446
+ message: `No installed Android system image is named "${requested}". Installed system images: ${installedNames((images || []).map((i) => i.pkg))}.`,
9447
+ remedy: "Pass `--system-image` (or set android.systemImage) to one of the package ids printed above."
9448
+ };
9449
+ }
9450
+ function unknownAndroidDeviceProfileRefusal(requested, profiles) {
9451
+ if (!requested) return null;
9452
+ if (profiles.includes(requested)) return null;
9453
+ return {
9454
+ message: `No Android hardware profile is named "${requested}". Profiles avdmanager offers: ${installedNames(profiles)}.`,
9455
+ remedy: "Pass `--device-profile` (or set android.deviceProfile) to one of the ids printed above, exactly as `avdmanager list device -c` spells it, e.g. \"pixel_fold\" or \"pixel_tablet\"."
9456
+ };
9457
+ }
9458
+ //#endregion
9459
+ export { levelStillShort as $, providerFromConfig as $n, remoteAndroidDeps as $t, budgetLine as A, installAndroidApp as An, sizeCaches as Ar, deleteParkedAvds as At, stopOwnedMetro as B, readRemoteSimulatorArch as Bn, unverifiedDeviceCommand as Bt, CCACHE_UNAVAILABLE as C, androidDevClientUrl as Cn, scopedMemoryReport as Cr, waitForGradleExit as Ct, readCcacheActivity as D, deviceShellArg as Dn, readFootprints as Dr, collectDeviceLeases as Dt, projectCmakeLauncher as E, devClientUrl as En, attributeMachineUsage as Er, DEVICE_LIST_TIMEOUT_MS$1 as Et, peekBudget as F, launchAndroidReleaseApp as Fn, deviceSweepIsScoped as Ft, devServerStopRecord as G, RESOLVE_TIMEOUT_MS as Gn, withRemoteSessionGcLocks as Gt, describeDevServerStop as H, exitAfterFlush as Hn, collectEasSessionSweep as Ht, readVolumeSpace as I, launchIosApp as In, eraseParkedDevices as It, requestDevServerStop as J, checkEasAuth as Jn, bundleEntryPoint as Jt, logVanishedSupervisor as K, UPLOAD_TIMEOUT_MS as Kn, REMOTE_SESSION_ERROR as Kt, readVolumeSpaceAsync as L, restartedAppNote as Ln, findOrphanedDevices as Lt, enforceBudget as M, installIosApp as Mn, deleteProjectDevices as Mt, inspectBudget as N, iosAppProcess as Nn, describeParkedSims as Nt, resolveCcache as O, ensureMetroReverse as On, discoverCaches as Or, collectParkedAvds as Ot, machineNumber as P, launchAndroidApp as Pn, describeUnverifiableDevices as Pt, levelFloors as Q, ownerFromConfig as Qn, readInstalledEasCliVersionAsync as Qt, resolveBudget as R, reverseMetroPorts as Rn, findStaleDeviceRecords as Rt, CCACHE_NOT_RUN as S, androidAppProcess as Sn, reclaimMemory as Sr, includesGradleVersions as St, ccacheInstallCommand as T, clearOtherUserApps as Tn, phase as Tr, collectInventory as Tt, describeMetroLastStop as U, processContext as Un, deleteEasSessions as Ut, stopOwnedMetroForReset as V, gateMetroOrigin as Vn, withAndroidAvdSizes as Vt, devServerStopLevel as W, runProcessOperation as Wn, describeError as Wt, withdrawDevServerStopRequest as X, isEasAuthFailureText as Xn, ensureMetroReachable as Xt, vanishedSupervisorMessage as Y, easAuthNote as Yn, endRecordedSession as Yt, diskPressureLevel as Z, loadProjectProvider as Zn, ensureRemoteBootOwned as Zt, withDeviceBootAdmission as _, APP_READINESS_TIMEOUT_MS as _n, memoryCacheKind as _r, collectGradleVersions as _t, countLiveOwnedDevicesAsync as a, LAUNCH_UNVERIFIED as an, remoteCliInvocation as ar, maintenanceLogger as at, shutDownIdleDevices as b, INSTALL_ERROR as bn, memorySweepIsScoped as br, gradleVersionLines as bt, deviceSlotUsages as c, VERIFY_TIMEOUT_MS as cn, MIN_EAS_CLI_SIMULATOR_VERSION as cr, includesParkedDevices as ct, peekDeviceSlots as d, readMetroRecords as dn, ownedSessionName as dr, isWorkspacesScope as dt, remoteIosDeps as en, readProjectConfig as er, lowestFree as et, runtimeMismatch as f, unattributedLaunchLines as fn, parseArchiveSelector as fr, planCacheEmptying as ft, unknownIosRuntimeRefusal as g, verifyReleaseLaunch as gn, inspectWatchmanMemory as gr, trimCaches as gt, unknownIosDeviceTypeRefusal as h, verifyLaunch as hn, collectMemoryReport as hr, selectsBeyondCaches as ht, countLiveOwnedDevices as i, LAUNCH_FATAL as in, bundleInvocation as ir, openGcLog as it, cacheLabel as j, installConflictKind as jn, deleteParkedSims as jt, budgetGate as k, hasMetroReverse as kn, pruneCache as kr, collectParkedSims as kt, deviceTypeMismatch as l, isBundleProof as ln, easCliSupport as lr, includesRecordings as lt, unknownAndroidSystemImageRefusal as m, verifyAndroidReleaseLaunch as mn, WATCHMAN_KIND as mr, selectCaches as mt, DeviceAdmissionRefusal as n, withEasProjectLock as nn, resolveRemote as nr, gcOptionsOf as nt, countLiveOwnedDevicesByPlatform as o, MANIFEST_VERIFY_TIMEOUT_MS as on, remoteCommand as or, emptyCaches as ot, unknownAndroidDeviceProfileRefusal as p, unverifiedLaunchLines as pn, GRADLE_DAEMONS_KIND as pr, previewScopedEmptying as pt, metroLastStop as q, cacheLevel as qn, binOnPath as qt, checkDeviceCapacity as r, LAUNCH_BUNDLING as rn, uploadRemote as rr, maintenanceItem as rt, countLiveOwnedDevicesFrom as s, RELEASE_VERIFY_WAIT_MS as sn, MIN_EAS_CLI_DEVICE_VERSION as sr, includesArchives as st, DEFAULT_DEVICE_SLOT_WAIT_MS as t, resolveRemoteContext as tn, resolveEasCliBin as tr, protectWindowMs as tt, layerNote as u, readCollectorRecords as un, easCliUpgradeRemedy as ur, includesWorkspaceOutputs as ut, clearDeviceIdleShutdown as v, ADB_INSTALL_TIMEOUT_MS as vn, memoryLines as vr, deleteGradleVersion as vt, ccacheActivityLine as w, appProcessFromPid as wn, stopIdleGradleDaemons as wr, getMachineCacheReport as wt, writeIdleShutdownEvent as x, IOS_DEV_MENU_OFF_DEFAULTS_PLIST as xn, memoryWorkPending as xr, gradleVersionsPending as xt, dueIdleDevices as y, DEFAULT_METRO_PORT as yn, memoryScope as yr, deleteGradleVersions as yt, trimmableCaches as z, writeDebugHttpHost as zn, findStaleProjectDevices as zt };