stim 1.20.2 → 1.21.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 (217) hide show
  1. package/README.md +88 -8
  2. package/dist/acquire-artifact-CD4lbiWE.mjs +153 -0
  3. package/dist/act-DHAw2A2S.mjs +2667 -0
  4. package/dist/activity-BzVfHSrY.mjs +1376 -0
  5. package/dist/{agent-device-usage-output-Dzgv6lgR.mjs → agent-device-usage-output-zh1vhbLp.mjs} +21 -65
  6. package/dist/{android-HqP1_vB_.mjs → android-BEJ8Jpl7.mjs} +2 -2
  7. package/dist/{android-DCKZrNvV.mjs → android-BLgbNqox.mjs} +128 -78
  8. package/dist/{android-Dlzxshgp.mjs → android-CV_GCELZ.mjs} +5 -5
  9. package/dist/{android-Mz_Inn3w.mjs → android-DUYkNF4u.mjs} +184 -102
  10. package/dist/{android-cas-Bl-cLJBk.mjs → android-cas-Ct5b1xCK.mjs} +7 -6
  11. package/dist/android-cas-compiler.mjs +2 -2
  12. package/dist/android-project-BCAAmMwz.mjs +12 -0
  13. package/dist/api.d.mts +740 -8
  14. package/dist/api.mjs +585 -1
  15. package/dist/{artifact-HSwkIWEQ.mjs → artifact-B8T3S5gi.mjs} +223 -201
  16. package/dist/{artifact-D3czlUvT.mjs → artifact-DAuZ-FNU.mjs} +158 -148
  17. package/dist/{browser-web-CA-F1GQo.mjs → browser-web-QQzVjz3O.mjs} +26 -24
  18. package/dist/budget-Bs6SxxzO.mjs +9154 -0
  19. package/dist/{build-DYDbfkk5.mjs → build-CoPzQRNw.mjs} +144 -96
  20. package/dist/build-cache-DmyR9Zzc.mjs +602 -0
  21. package/dist/build-plan-CaFskB7_.mjs +188 -0
  22. package/dist/{cache-manifest-XH-fvDUj.mjs → cache-manifest-DkkqguoK.mjs} +2 -2
  23. package/dist/cache-manifest.mjs +1 -1
  24. package/dist/{ccache-zdKiYQ3E.mjs → ccache-BJRZ9pG3.mjs} +10 -9
  25. package/dist/{cdp-Dq_ypAlJ.mjs → cdp-CWmIUMbw.mjs} +18 -5
  26. package/dist/{chrome-BnzZMqsR.mjs → chrome-B6tfCta1.mjs} +7 -4
  27. package/dist/{cli-CLP3diBm.mjs → cli-0DYf8QhQ.mjs} +20 -20
  28. package/dist/cli.mjs +1 -1
  29. package/dist/client-B7QVOlCr.d.mts +253 -0
  30. package/dist/client-UXD70ZbM.mjs +1406 -0
  31. package/dist/collector-run.d.mts +4 -40
  32. package/dist/collector-run.mjs +9 -8
  33. package/dist/{command-output-BTy8hZ0F.mjs → command-output-B1UrjZHL.mjs} +1 -1
  34. package/dist/config-CYcBF478.mjs +949 -0
  35. package/dist/{created-devices-Dzd5C2LF.mjs → created-devices-CqeBlrEn.mjs} +3 -3
  36. package/dist/{dependency-state-DG0p31-y.mjs → dependency-state-BbKQW83h.mjs} +20 -5
  37. package/dist/{detached-entry-B9C_zeQC.mjs → detached-entry-DFRyG1_o.mjs} +1 -1
  38. package/dist/{dev-client-DAWVIGBu.mjs → dev-client-UOeESKm6.mjs} +2 -2
  39. package/dist/device-DCxH7_By.mjs +1068 -0
  40. package/dist/{device-BECOiufl.mjs → device-ExJq8Ei7.mjs} +35 -35
  41. package/dist/{device-capacity-CBUAcRyQ.mjs → device-capacity-CMlqEcvw.mjs} +43 -13
  42. package/dist/device-host-worker.mjs +65 -61
  43. package/dist/{device-ios-COSi6oS1.mjs → device-ios-DRUeKaFV.mjs} +48 -45
  44. package/dist/{device-lease-run-Cw2M4z51.mjs → device-lease-run-C8raGFoP.mjs} +14 -26
  45. package/dist/{device-pool-Bf-pOhSw.mjs → device-pool-DMRpVX9a.mjs} +5 -5
  46. package/dist/doctor-BLHCYxzi.mjs +2 -0
  47. package/dist/{doctor-kfMBnUw6.mjs → doctor-DTvaDOda.mjs} +281 -317
  48. package/dist/{doctor-CB8oZKCk.mjs → doctor-Dhqebm0q.mjs} +183 -91
  49. package/dist/doctor-fingerprint.mjs +19 -0
  50. package/dist/{doctor-watchman-BRyV2adA.mjs → doctor-watchman-Cv1F10Kj.mjs} +40 -21
  51. package/dist/{eas-build-JyfqyHfC.mjs → eas-build-BomfRnFT.mjs} +53 -20
  52. package/dist/{build-plan-B7SZsc2N.mjs → eas-fallback-DJwZpy0c.mjs} +26 -208
  53. package/dist/{watchman-CPQFZLXY.mjs → eas-session-ledger-op6A54yi.mjs} +229 -11
  54. package/dist/{error-diagnostics-BR677IDM.mjs → error-diagnostics-Bf0rZvG9.mjs} +172 -82
  55. package/dist/exec-H20xSg2T.d.mts +65 -0
  56. package/dist/exec-_UbtCQcG.mjs +750 -0
  57. package/dist/gc-C-WwL0yi.mjs +3 -0
  58. package/dist/{gc-qWOMdnER.mjs → gc-_-dCXdyL.mjs} +584 -796
  59. package/dist/git-C3iTsJaZ.mjs +73 -0
  60. package/dist/{guide-G9cAbVAc.mjs → guide-Yqwt2bd6.mjs} +941 -269
  61. package/dist/{hosted-android-C3MHGld1.mjs → hosted-android-Bw-honsd.mjs} +8 -6
  62. package/dist/{hosted-client-ZEKpS9vw.mjs → hosted-client-DB_gBPfY.mjs} +34 -19
  63. package/dist/{hosted-ios-l-o3iM9J.mjs → hosted-ios-CUvOf4Vr.mjs} +1 -1
  64. package/dist/{hosted-logs-EzQM7-3Z.mjs → hosted-logs-DL_P5AdN.mjs} +77 -43
  65. package/dist/{hosted-macos-BACVAVqE.mjs → hosted-macos-QI141yKk.mjs} +21 -17
  66. package/dist/{hosted-native-A4s1QPXe.mjs → hosted-native-Ksyjgzq-.mjs} +30 -21
  67. package/dist/idle-BVeRGroT.d.mts +24 -0
  68. package/dist/{idle-shutdown-DtxhPTKe.mjs → idle-shutdown-DaKmZi0z.mjs} +11 -10
  69. package/dist/{ios-BwVCF7oA.mjs → ios-CQ68hG9s.mjs} +8 -8
  70. package/dist/{ios-RkW4pnQf.mjs → ios-CpByZy12.mjs} +140 -80
  71. package/dist/{ios-device-B5veSDDQ.mjs → ios-device-CXGP1bsD.mjs} +1 -1
  72. package/dist/{ios-device-D7pi2Sca.mjs → ios-device-CwFMehUS.mjs} +51 -26
  73. package/dist/{ios-project-K9hY_Jab.mjs → ios-project-WuwwBdvW.mjs} +13 -13
  74. package/dist/{ios-nOLJGVdy.mjs → ios-qNJt4dFm.mjs} +1 -1
  75. package/dist/ios-signing-DTTIChGs.mjs +489 -0
  76. package/dist/{ios-state-CBLe1xYw.mjs → ios-state-DvFX-K_l.mjs} +1 -1
  77. package/dist/{ios-Ce7bc7br.mjs → ios-ubFsBzwq.mjs} +129 -86
  78. package/dist/{js-swap-Bb3nsS07.mjs → js-swap-Cc1dttps.mjs} +53 -26
  79. package/dist/{launch-F4sOqAhB.mjs → launch-CD1yVc7U.mjs} +91 -90
  80. package/dist/{launch-C3qJ6Agb.mjs → launch-Zq2mOSDy.mjs} +82 -541
  81. package/dist/logs-BS00Anxf.mjs +352 -0
  82. package/dist/logs-CMm05HxN.mjs +255 -0
  83. package/dist/{macos-D7uDvO-z.mjs → macos-B65--pPd.mjs} +1 -1
  84. package/dist/{macos-Ch5VJmcS.mjs → macos-C-0qJ6zq.mjs} +225 -212
  85. package/dist/macos-run.mjs +1 -1
  86. package/dist/maintenance-run.mjs +122 -247
  87. package/dist/{metro-DnCEjCxs.mjs → metro-DEXpIhHC.mjs} +67 -12
  88. package/dist/{metro-gateway-CX9Xlkq2.mjs → metro-gateway-BMJNlj3E.mjs} +20 -12
  89. package/dist/{named-ports-4WopRPg-.mjs → named-ports-BWTzt6mu.mjs} +8 -63
  90. package/dist/native-android-JACyjTZs.mjs +844 -0
  91. package/dist/{native-gradle-inputs-BsLRrl7u.mjs → native-gradle-inputs-Bep9JcSF.mjs} +128 -140
  92. package/dist/native-inputs-TRTTTRxt.mjs +91 -0
  93. package/dist/native-run-DY_RLx3C.mjs +277 -0
  94. package/dist/{native-runtime-k8dTIrbK.mjs → native-runtime-Dg1Q7tfF.mjs} +9 -8
  95. package/dist/{native-xcode-inputs-CykopJdM.mjs → native-xcode-inputs-CB2iQeWq.mjs} +27 -18
  96. package/dist/{native-xcode-ios-Bbf30Gm0.mjs → native-xcode-ios-CXX39F7o.mjs} +96 -47
  97. package/dist/{ndjson-GOnbyvso.mjs → ndjson-CgE_o3Dl.mjs} +3 -3
  98. package/dist/offload-worker.d.mts +1 -46
  99. package/dist/offload-worker.mjs +197 -66
  100. package/dist/{ownership-DCtw_2IA.mjs → ownership-DEmM5vhh.mjs} +3 -3
  101. package/dist/{page-DRHy329N.mjs → page-CPaHeYqj.mjs} +7 -4
  102. package/dist/paths-BsJMoPP2.mjs +12 -0
  103. package/dist/paths-DQtTgsBf.mjs +42 -0
  104. package/dist/{placement-log-MOfPVgaM.mjs → placement-log-BqGbTa61.mjs} +5 -5
  105. package/dist/{plan-placement-V0TdzYXp.mjs → plan-placement-Bwj-IjY1.mjs} +6 -6
  106. package/dist/{ports-DdSUicVo.mjs → ports-D-MmCTdv.mjs} +3 -3
  107. package/dist/prebuild-uD_ipCxF.mjs +449 -0
  108. package/dist/{project-_phNIJfs.mjs → project-BZNjmW_8.mjs} +65 -37
  109. package/dist/{pull-request-CQ8sPbZa.mjs → pull-request-Lfetn8Kr.mjs} +2 -2
  110. package/dist/pull-request-zjCSAryH.d.mts +120 -0
  111. package/dist/pull-requests.d.mts +1 -119
  112. package/dist/pull-requests.mjs +1 -1
  113. package/dist/{react-native-android-CM1XRDwv.mjs → react-native-android-DuzH0cn3.mjs} +74 -48
  114. package/dist/{gradle-DSrXb6lG.mjs → react-native-build-rhk4smwQ.mjs} +292 -15
  115. package/dist/react-native-doctor-CQpuIMso.mjs +234 -0
  116. package/dist/{react-native-ios-Dhq_eiYQ.mjs → react-native-ios-Bhq1rJeV.mjs} +46 -28
  117. package/dist/{recordings-D0d5aQt0.mjs → recordings-DYteQSu4.mjs} +12 -9
  118. package/dist/{reload-C8JhxjSh.mjs → reload-CmTRFJaX.mjs} +93 -47
  119. package/dist/reload-DzjSl5P_.mjs +2 -0
  120. package/dist/{remote-BiR6aYjy.mjs → remote-ByYkXdGD.mjs} +15 -10
  121. package/dist/result-DNiS-tTP.mjs +434 -0
  122. package/dist/{run-BtPgpKks.mjs → run-D23HmJMs.mjs} +4 -5
  123. package/dist/selection-CSl9i5qU.mjs +619 -0
  124. package/dist/{server-bare-R2m_gsH8.mjs → server-bare-CxTrvDg8.mjs} +3 -4
  125. package/dist/{server-command-CTtCkmcQ.mjs → server-command-CtzloH3v.mjs} +14 -7
  126. package/dist/server-expo-C-90EhcG.mjs +2 -0
  127. package/dist/{server-expo-DsguMGUF.mjs → server-expo-DT_F_uVI.mjs} +50 -9
  128. package/dist/{settings-B717odTD.mjs → settings-CsJDEXgK.mjs} +234 -24
  129. package/dist/{settings-vk3mJGSm.d.mts → settings-DkdmlelE.d.mts} +6 -4
  130. package/dist/settings-T1gtc3bf.mjs +114 -0
  131. package/dist/settings-write-HyVZ8eQ8.mjs +359 -0
  132. package/dist/settings.schema.json +163 -16
  133. package/dist/{simslim-CCr3rE-A.mjs → simslim-Cgk17SXV.mjs} +14 -12
  134. package/dist/{slot-launch-DPt0m9Nd.mjs → slot-launch-DF8VfT64.mjs} +15 -20
  135. package/dist/{spawn-entry-Dcb7sDUz.mjs → spawn-entry-jw19m8Nl.mjs} +0 -1
  136. package/dist/stage-BIS0ydsV.mjs +183 -0
  137. package/dist/{start-BWqzODpb.mjs → start-7lpH7hmN.mjs} +63 -44
  138. package/dist/{start-TXeMmmO2.mjs → start-RChynRDp.mjs} +1 -1
  139. package/dist/{state-D1Icyjlu.mjs → state-C4ZtUisW.mjs} +40 -13
  140. package/dist/{state-CG_OFoaA.mjs → state-XMziexv2.mjs} +3 -2
  141. package/dist/{stats-BoY5sEPU.mjs → stats-53T7OTb9.mjs} +7 -13
  142. package/dist/stats-report-BFfMpWWF.mjs +16 -0
  143. package/dist/{status-WgFbkRzj.mjs → status-BL7jVyAB.mjs} +535 -447
  144. package/dist/status-CSi6tlRq.mjs +2 -0
  145. package/dist/{status-BTA7kKiQ.mjs → status-DqEUSSxs.mjs} +6 -4
  146. package/dist/{status-watch-C23ILtf9.mjs → status-watch-GDCfqpTE.mjs} +187 -22
  147. package/dist/status-worker.d.mts +1 -0
  148. package/dist/status-worker.mjs +19 -0
  149. package/dist/stim-desktop-C12we7lz.mjs +112 -0
  150. package/dist/{stop-DR6VoDIk.mjs → stop-Bk_1qiV6.mjs} +2 -2
  151. package/dist/{stop-BNmBMi1F.mjs → stop-CPzlM1L-.mjs} +138 -98
  152. package/dist/{stop-DPEhunlw.mjs → stop-D7h6jAXQ.mjs} +11 -10
  153. package/dist/supervisor-run.d.mts +8 -17
  154. package/dist/supervisor-run.mjs +63 -27
  155. package/dist/{support-DMTqj3Hm.mjs → support-67vraa1Q.mjs} +18 -11
  156. package/dist/{support-CwjbFs9H.mjs → support-Dlu37g6H.mjs} +12 -12
  157. package/dist/{swiftpm-macos-DRNVJWlb.mjs → swiftpm-macos-p8WZbxKU.mjs} +35 -24
  158. package/dist/toolchain-DhKwp9iI.mjs +2 -0
  159. package/dist/toolchain-Hp9sCpK4.mjs +873 -0
  160. package/dist/{trigger-CigAYjeL.mjs → trigger-BAahxUPq.mjs} +20 -5
  161. package/dist/trigger-Bf3uDcpv.mjs +2 -0
  162. package/dist/{warm-progress-BuY-Fvfn.mjs → warm-progress-BHX5r0Ei.mjs} +250 -156
  163. package/dist/{web-BDBMxDpx.mjs → web-CJmIj6AI.mjs} +18 -13
  164. package/dist/web-DknQETPs.mjs +2 -0
  165. package/dist/web-run.mjs +11 -12
  166. package/dist/windows-report.d.mts +1 -0
  167. package/dist/windows-report.mjs +18 -0
  168. package/dist/windows-report.ps1 +43 -0
  169. package/dist/workspace-process-lock-1_1OoVW5.mjs +57 -0
  170. package/dist/{workspace-state-DJt_MMvf.mjs → workspace-state-Cjuigeeu.mjs} +62 -58
  171. package/dist/{workspaces-C36s2jb9.mjs → workspaces-BccMFmGQ.mjs} +1188 -1079
  172. package/dist/worktree-CK2MpJW5.mjs +2 -0
  173. package/dist/{worktree-CgvJYJ-Q.mjs → worktree-DQ6Zi2Mv.mjs} +304 -53
  174. package/dist/xcode-Zlx3qjEs.mjs +896 -0
  175. package/package.json +7 -5
  176. package/shim/android-optimizations.gradle +7 -5
  177. package/dist/activity-BSV745Kz.mjs +0 -629
  178. package/dist/api-qvOzWyfO.mjs +0 -154
  179. package/dist/api-run.mjs +0 -294
  180. package/dist/app-install-BIKtMfPq.mjs +0 -896
  181. package/dist/artifact-lifecycle-DzPpBYvK.mjs +0 -30
  182. package/dist/budget-f7516Zk0.mjs +0 -4106
  183. package/dist/build-facts-DcLv2HSd.d.mts +0 -111
  184. package/dist/cancellation-C_22VQTH.mjs +0 -24
  185. package/dist/client-qQAaXSUs.mjs +0 -1771
  186. package/dist/config-BYDxlMY3.mjs +0 -562
  187. package/dist/device-DuiPJQSn.mjs +0 -290
  188. package/dist/device-lease-BX1sJQ2E.mjs +0 -1224
  189. package/dist/device-remote-xRT0Vmo9.mjs +0 -1272
  190. package/dist/eas-session-ledger-Cm0Gwq2F.mjs +0 -207
  191. package/dist/exec-DqKo9GKJ.mjs +0 -268
  192. package/dist/git-DDfFzkMm.mjs +0 -45
  193. package/dist/launch-verify-B6Q8hZP-.mjs +0 -428
  194. package/dist/logs-BGrEUaEs.mjs +0 -383
  195. package/dist/miss-reason-mKzZ4VvN.mjs +0 -238
  196. package/dist/native-android-D5qc3-_v.mjs +0 -226
  197. package/dist/ndjson-Du3j0AQ9.d.mts +0 -17
  198. package/dist/plan-C4TjdPsR.mjs +0 -260
  199. package/dist/prebuild-DYbucPXy.mjs +0 -212
  200. package/dist/preview-78a9kR_-.mjs +0 -1011
  201. package/dist/react-native-build-CtCccJtb.mjs +0 -239
  202. package/dist/react-native-doctor-CfRnFfjs.mjs +0 -78
  203. package/dist/remote-cache-OyOyVIS3.mjs +0 -526
  204. package/dist/result-CaJg5Iat.mjs +0 -186
  205. package/dist/server-expo-Bigriskl.mjs +0 -2
  206. package/dist/settings-BORNaaCO.mjs +0 -402
  207. package/dist/spawn-claims-DVS5RkMS.mjs +0 -72
  208. package/dist/stage-KNTQopqv.mjs +0 -157
  209. package/dist/stim-desktop-BzBN2Em2.mjs +0 -63
  210. package/dist/tailnet-Dh-qiYzG.mjs +0 -265
  211. package/dist/toolchain-BFkAcstd.mjs +0 -2
  212. package/dist/toolchain-DxSsNssu.mjs +0 -1657
  213. package/dist/trigger-9_USgmxz.mjs +0 -2
  214. package/dist/web-Dq-i3wzJ.mjs +0 -2
  215. package/dist/worktree-DpLdCHga.mjs +0 -2
  216. package/dist/{api-run.d.mts → doctor-fingerprint.d.mts} +0 -0
  217. package/dist/{errors-DzkMYh8Q.mjs → errors-ccaPc4ox.mjs} +2 -2
@@ -0,0 +1,1068 @@
1
+ import { t as getExecutor, u as operationState } from "./exec-_UbtCQcG.mjs";
2
+ import { G as parseDeviceSlotOption, H as deviceSlotKey, K as projectDeviceSlots, _ as loadConfig$1, p as getProject } from "./config-CYcBF478.mjs";
3
+ import { t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
4
+ import { r as clockTime } from "./command-output-B1UrjZHL.mjs";
5
+ import { K as resolveIosProjectDir, Xt as removeTemporaryEntry, Yt as makeTemporaryDirectory, q as resolveProjectSettings, tt as settingValueAt } from "./settings-CsJDEXgK.mjs";
6
+ import { f as projectRegistry, g as selectNativeXcodeProject, i as findProjectRoot$1, p as NativeXcodeError, t as NO_PROJECT_REFUSAL } from "./project-BZNjmW_8.mjs";
7
+ import { In as runProcessOperation } from "./budget-Bs6SxxzO.mjs";
8
+ import { C as parseLeaseDuration, D as releaseWorkspaceLeases, j as takeLease, y as fileLeaseIo } from "./activity-BzVfHSrY.mjs";
9
+ import { $ as resolvePhysicalDevice, D as getAvdNameForSerialAsync, I as memoizeEmulatorProbe, Y as probeEmulatorSerial, c as androidPoolNoCandidatesRefusal, k as listAdbDevices, q as physicalDeviceModel, s as androidPoolCandidates } from "./android-DUYkNF4u.mjs";
10
+ import { m as resolveScheme, v as xcodebuildArgs } from "./xcode-Zlx3qjEs.mjs";
11
+ import { a as discoverXcodeProject } from "./react-native-build-rhk4smwQ.mjs";
12
+ import { l as parseDeviceWait, p as waitForDevice } from "./device-lease-run-C8raGFoP.mjs";
13
+ import { m as resolveConfiguration, y as resolveSchemeSelection } from "./support-Dlu37g6H.mjs";
14
+ import { n as findSigningIdentities, o as parsePlist, s as parseProvisioningProfilePlist, t as decodeProvisioningProfile } from "./ios-signing-DTTIChGs.mjs";
15
+ import { t as selectFromPool } from "./device-pool-DMRpVX9a.mjs";
16
+ import { a as iosPoolCandidates, c as isWirelessIosDevice, f as resolveIosPhysicalDevice, l as listIosDevices, o as iosPoolNoCandidatesRefusal } from "./ios-device-CwFMehUS.mjs";
17
+ import { i as nativeXcodePackages, t as nativeXcodeHasPackages } from "./native-xcode-inputs-CB2iQeWq.mjs";
18
+ import { existsSync, readdirSync, realpathSync, statSync } from "node:fs";
19
+ import { homedir } from "node:os";
20
+ import { join } from "node:path";
21
+ import { settingDefinition, settingValueError } from "@stim-cli/core/state";
22
+ import chalk from "chalk";
23
+ import { createInterface } from "node:readline/promises";
24
+ //#region src/engine/ios-provision.ts
25
+ const KEY_SETTINGS_REMEDY = "Set all three with `stim settings set <key> <value> --scope machine` (ios.appStoreConnectKeyPath, ios.appStoreConnectKeyId, ios.appStoreConnectIssuerId), or export STIM_ASC_KEY_PATH, STIM_ASC_KEY_ID and STIM_ASC_ISSUER_ID; unset all three to use the Xcode account instead.";
26
+ function keySetting(key, machine, env) {
27
+ const definition = settingDefinition(key);
28
+ const envName = definition?.env;
29
+ const fromEnv = envName ? env[envName] : void 0;
30
+ const value = fromEnv || settingValueAt(machine, key);
31
+ if (value === void 0 || value === "") return { missing: key };
32
+ const problem = definition ? settingValueError(definition, value) : null;
33
+ if (problem !== null || typeof value !== "string") return {
34
+ code: "STIM_BAD_ARG",
35
+ message: `${fromEnv ? envName : key} is not valid: expected ${problem ?? "a string"}. Stim does not print its value.`,
36
+ remedy: KEY_SETTINGS_REMEDY
37
+ };
38
+ return {
39
+ value,
40
+ source: fromEnv && envName ? envName : key
41
+ };
42
+ }
43
+ /** Each value comes from its environment variable when that is set and non-empty, otherwise from the machine config. */
44
+ function provisionAuth(machine, env, fileExists = existsSync) {
45
+ const keyPath = keySetting("ios.appStoreConnectKeyPath", machine, env);
46
+ const keyId = keySetting("ios.appStoreConnectKeyId", machine, env);
47
+ const issuerId = keySetting("ios.appStoreConnectIssuerId", machine, env);
48
+ const read = [
49
+ keyPath,
50
+ keyId,
51
+ issuerId
52
+ ];
53
+ for (const each of read) if ("code" in each) return each;
54
+ const missing = read.flatMap((each) => "missing" in each ? [each.missing] : []);
55
+ if (missing.length === read.length) return { kind: "xcode-account" };
56
+ if (!("value" in keyPath && "value" in keyId && "value" in issuerId)) return {
57
+ code: "STIM_BAD_ARG",
58
+ message: `An App Store Connect API key needs a key path, key ID and issuer ID; ${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} not set.`,
59
+ remedy: KEY_SETTINGS_REMEDY
60
+ };
61
+ if (!fileExists(keyPath.value)) return {
62
+ code: "STIM_BAD_ARG",
63
+ message: `The App Store Connect API key file that ${keyPath.source} names does not exist.`,
64
+ remedy: `Point ${keyPath.source} at the AuthKey_<KEY_ID>.p8 file App Store Connect gave you. ${KEY_SETTINGS_REMEDY}`
65
+ };
66
+ return {
67
+ kind: "api-key",
68
+ keyPath: keyPath.value,
69
+ keyId: keyId.value,
70
+ issuerId: issuerId.value
71
+ };
72
+ }
73
+ /** Replaces the key path, which can name a private key's location, wherever xcodebuild echoes it. */
74
+ function redactKeyPath(text, auth) {
75
+ if (auth.kind !== "api-key") return text;
76
+ const paths = /* @__PURE__ */ new Set([auth.keyPath]);
77
+ try {
78
+ paths.add(realpathSync(auth.keyPath));
79
+ } catch {}
80
+ return [...paths].flatMap((path) => [
81
+ `'${path}'`,
82
+ path.replace(/([^A-Za-z0-9_\-./])/g, "\\$1"),
83
+ path
84
+ ]).toSorted((a, b) => b.length - a.length).reduce((redacted, form) => redacted.replaceAll(form, "<ios.appStoreConnectKeyPath>"), text);
85
+ }
86
+ const PLATFORM_NAMES = {
87
+ iphoneos: "iOS",
88
+ watchos: "watchOS",
89
+ appletvos: "tvOS",
90
+ xros: "visionOS",
91
+ macosx: "macOS"
92
+ };
93
+ function platformName(platform) {
94
+ return PLATFORM_NAMES[platform] ?? platform;
95
+ }
96
+ function describeAuth(auth) {
97
+ return auth.kind === "api-key" ? `the App Store Connect API key ${auth.keyId}` : "the Apple account signed in to Xcode > Settings > Accounts";
98
+ }
99
+ function provisionXcodebuildArgs({ project, scheme, configuration, udid, derivedDataPath, auth, packageArgs = [] }) {
100
+ return xcodebuildArgs({
101
+ project,
102
+ scheme,
103
+ configuration,
104
+ sdk: "iphoneos",
105
+ udid,
106
+ derivedDataPath,
107
+ extraArgs: [
108
+ ...packageArgs,
109
+ "-allowProvisioningUpdates",
110
+ "-allowProvisioningDeviceRegistration",
111
+ ...auth.kind === "api-key" ? [
112
+ "-authenticationKeyPath",
113
+ auth.keyPath,
114
+ "-authenticationKeyID",
115
+ auth.keyId,
116
+ "-authenticationKeyIssuerID",
117
+ auth.issuerId
118
+ ] : []
119
+ ]
120
+ });
121
+ }
122
+ /** Every target of the scheme that xcodebuild signs with a provisioning profile, on any platform; null when the output is not JSON. */
123
+ function parseSigningTargets(output) {
124
+ let items;
125
+ try {
126
+ items = JSON.parse(String(output));
127
+ } catch {
128
+ return null;
129
+ }
130
+ const targets = [];
131
+ for (const item of Array.isArray(items) ? items : []) {
132
+ const raw = item && typeof item === "object" && "buildSettings" in item ? item.buildSettings : null;
133
+ if (!raw || typeof raw !== "object") continue;
134
+ const settings = { ...raw };
135
+ if (settings.CODE_SIGNING_ALLOWED !== "YES" || settings.PROVISIONING_PROFILE_REQUIRED !== "YES" || typeof settings.PRODUCT_BUNDLE_IDENTIFIER !== "string" || settings.PRODUCT_BUNDLE_IDENTIFIER === "") continue;
136
+ const team = typeof settings.DEVELOPMENT_TEAM === "string" ? settings.DEVELOPMENT_TEAM.trim() : "";
137
+ targets.push({
138
+ target: typeof settings.TARGET_NAME === "string" ? settings.TARGET_NAME : settings.PRODUCT_BUNDLE_IDENTIFIER,
139
+ bundleId: settings.PRODUCT_BUNDLE_IDENTIFIER,
140
+ platform: typeof settings.PLATFORM_NAME === "string" ? settings.PLATFORM_NAME : "unknown",
141
+ team: team || null,
142
+ automatic: settings.CODE_SIGN_STYLE !== "Manual"
143
+ });
144
+ }
145
+ return targets;
146
+ }
147
+ function signingTargetsRefusal(targets, { scheme, configuration }) {
148
+ if (targets.length === 0) return {
149
+ code: "STIM_PROVISION_FAILED",
150
+ message: `Scheme ${scheme} builds no target that needs a provisioning profile in its ${configuration} configuration, so there is nothing to provision.`,
151
+ remedy: "Pass the app scheme with --scheme, or set ios.scheme, and check the configuration with --configuration."
152
+ };
153
+ const teamless = targets.filter((target) => target.team === null);
154
+ if (teamless.length > 0) return {
155
+ code: "STIM_PROVISION_FAILED",
156
+ message: `No development team is set for ${teamless.map((t) => `${t.target} (${t.bundleId})`).join(", ")} in the ${configuration} configuration. Stim provisions for the team the build signs with, and a later \`stim ios --device\` build uses the project's team too.`,
157
+ remedy: "Choose a team in Xcode > Signing & Capabilities, or for an Expo app set expo.ios.appleTeamId in the app config and run `npx expo prebuild -p ios` again, then retry."
158
+ };
159
+ const manual = targets.filter((target) => !target.automatic);
160
+ if (manual.length > 0) return {
161
+ code: "STIM_PROVISION_FAILED",
162
+ message: `${manual.map((t) => `${t.target} (${t.bundleId})`).join(", ")} use${manual.length === 1 ? "s" : ""} manual signing in the ${configuration} configuration, and xcodebuild creates or refreshes profiles only for automatic signing.`,
163
+ remedy: "Turn on Automatically manage signing in Xcode > Signing & Capabilities, or create the Development profile in the Apple Developer portal and install it."
164
+ };
165
+ return null;
166
+ }
167
+ function provisionPlanLines({ device, targets, auth, scheme, configuration }) {
168
+ return [
169
+ `This changes the Apple Developer account of team ${[...new Set(targets.map((target) => target.team))].join(", ")}, through ${describeAuth(auth)}:`,
170
+ ` - registers ${device.name} (${device.udid}) as a development device, when the team does not list it yet;`,
171
+ ...targets.map((target) => ` - creates or refreshes the ${platformName(target.platform)} App Development profile for ${target.bundleId} (target ${target.target}, scheme ${scheme}, ${configuration})${target.platform === "iphoneos" ? " so it lists that device" : ""};`),
172
+ " - creates an Apple Development certificate for this Mac when the keychain holds none for the team.",
173
+ `Those are all the targets scheme ${scheme} signs with a profile. It runs xcodebuild with -allowProvisioningUpdates -allowProvisioningDeviceRegistration for that scheme only and stops it once signing resolves; it installs nothing.`
174
+ ];
175
+ }
176
+ const TASK_LINE = /^[A-Z][A-Za-z]+ /;
177
+ const DIAGNOSTIC = /\b(?:error|warning|note):/i;
178
+ /**
179
+ * xcodebuild resolves signing while planning and runs build tasks only when planning succeeded: it prints
180
+ * `Build description path:`, then either the planning errors and `** BUILD FAILED **`, or the first task such as
181
+ * `CreateBuildDirectory`. An `error:` line therefore decides nothing on its own; the first task does.
182
+ */
183
+ function createPlanningWatch() {
184
+ let state = "planning";
185
+ return (line) => {
186
+ if (state === "ended") return false;
187
+ if (line.startsWith("** ")) {
188
+ state = "ended";
189
+ return false;
190
+ }
191
+ if (line.startsWith("Build description path: ")) {
192
+ state = "planned";
193
+ return false;
194
+ }
195
+ return state === "planned" && TASK_LINE.test(line) && !DIAGNOSTIC.test(line);
196
+ };
197
+ }
198
+ function errorText(line) {
199
+ const at = line.indexOf("error: ");
200
+ return (at < 0 ? line : line.slice(at + 7)).replace(/ \(in target '.*' from project '.*'\)$/, "").trim();
201
+ }
202
+ function provisionFailure(lines, auth, teams) {
203
+ const errors = lines.filter((line) => /\berror: /.test(line)).map(errorText);
204
+ const unique = [...new Set(errors)];
205
+ const has = (pattern) => unique.find((text) => pattern.test(text));
206
+ if (has(/^No Accounts\b/)) return {
207
+ code: "STIM_PROVISION_FAILED",
208
+ message: auth.kind === "api-key" ? `xcodebuild reported No Accounts although it was given ${describeAuth(auth)}.` : "xcodebuild has no Apple account to provision with: Xcode has no account signed in, and no App Store Connect API key is set.",
209
+ remedy: `Sign in under Xcode > Settings > Accounts with an Apple ID on team ${teams}, or ${KEY_SETTINGS_REMEDY.charAt(0).toLowerCase()}${KEY_SETTINGS_REMEDY.slice(1)} Then run \`stim device provision ios\` again.`
210
+ };
211
+ const noTeam = has(/^No Account for Team\b/);
212
+ const quoted = (text) => redactKeyPath(text, auth);
213
+ if (noTeam) return {
214
+ code: "STIM_PROVISION_FAILED",
215
+ message: `Xcode cannot reach team ${teams} through ${describeAuth(auth)}: ${quoted(noTeam)}`,
216
+ remedy: `Sign in to Xcode with an Apple ID that belongs to team ${teams}, or use an App Store Connect API key issued by that team. If the project names the wrong team, fix it in Xcode > Signing & Capabilities.`
217
+ };
218
+ const rejected = has(/Communication with Apple failed.*\b401\b/);
219
+ if (rejected) return {
220
+ code: "STIM_PROVISION_FAILED",
221
+ message: `Apple rejected ${describeAuth(auth)}: ${quoted(rejected)}`,
222
+ remedy: auth.kind === "api-key" ? "Check that the key ID and issuer ID match the .p8 file and that the key is still active under App Store Connect > Users and Access > Integrations." : "Sign in again under Xcode > Settings > Accounts."
223
+ };
224
+ if (has(/Unable to find a destination matching/)) return {
225
+ code: "STIM_PROVISION_FAILED",
226
+ message: "xcodebuild could not use the phone as a destination.",
227
+ remedy: "Unlock the phone, check that it is paired and has Developer Mode on, and that `xcrun devicectl list devices` lists it, then retry."
228
+ };
229
+ return {
230
+ code: "STIM_PROVISION_FAILED",
231
+ message: unique.length ? `xcodebuild could not provision signing: ${redactKeyPath(unique.slice(0, 5).join(" | "), auth)}` : "xcodebuild exited before signing resolved and printed no error.",
232
+ remedy: "Fix what xcodebuild reports, or open the workspace in Xcode, select the phone and build once, which provisions the same way after asking for an account."
233
+ };
234
+ }
235
+ function profileDirectories(home = homedir()) {
236
+ return [join(home, "Library", "Developer", "Xcode", "UserData", "Provisioning Profiles"), join(home, "Library", "MobileDevice", "Provisioning Profiles")];
237
+ }
238
+ function listProfileFiles(dirs) {
239
+ const files = /* @__PURE__ */ new Map();
240
+ for (const dir of dirs) {
241
+ let names;
242
+ try {
243
+ names = readdirSync(dir);
244
+ } catch {
245
+ continue;
246
+ }
247
+ for (const name of names) {
248
+ if (!name.endsWith(".mobileprovision")) continue;
249
+ try {
250
+ files.set(join(dir, name), statSync(join(dir, name)).mtimeMs);
251
+ } catch {}
252
+ }
253
+ }
254
+ return files;
255
+ }
256
+ function applicationIdentifierOf(xml) {
257
+ const root = parsePlist(xml);
258
+ const entitlements = root && typeof root === "object" && "Entitlements" in root ? root.Entitlements : null;
259
+ const value = entitlements && typeof entitlements === "object" && "application-identifier" in entitlements ? entitlements["application-identifier"] : null;
260
+ return typeof value === "string" ? value : null;
261
+ }
262
+ async function readInstalledProfiles(files, { exec = null } = {}) {
263
+ const out = [];
264
+ for (const [file, mtimeMs] of files) {
265
+ const xml = await decodeProvisioningProfile(file, { exec });
266
+ out.push({
267
+ file,
268
+ mtimeMs,
269
+ profile: xml === null ? null : parseProvisioningProfilePlist(xml),
270
+ applicationIdentifier: xml === null ? null : applicationIdentifierOf(xml)
271
+ });
272
+ }
273
+ return out;
274
+ }
275
+ function coversBundle(applicationIdentifier, team, bundleId) {
276
+ if (applicationIdentifier === `${team}.${bundleId}`) return "exact";
277
+ if (applicationIdentifier?.endsWith("*")) {
278
+ const prefix = applicationIdentifier.slice(0, -1);
279
+ if (`${team}.${bundleId}`.startsWith(prefix)) return "wildcard";
280
+ }
281
+ return null;
282
+ }
283
+ function provisionChanges({ before, after, targets, udid, identitiesBefore, identitiesAfter, now }) {
284
+ const profiles = targets.map((target) => {
285
+ const team = target.team;
286
+ const onPhone = target.platform === "iphoneos";
287
+ const best = after.flatMap((entry) => {
288
+ const profile = entry.profile;
289
+ const expires = profile?.expirationDate;
290
+ const devices = profile?.provisionedDevices;
291
+ if (!profile || !team || profile.teamIdentifier !== team || !profile.getTaskAllow) return [];
292
+ if (!expires || expires.getTime() <= now) return [];
293
+ if (!devices) return [];
294
+ if (onPhone && !devices.some((device) => device.toLowerCase() === udid.toLowerCase())) return [];
295
+ const fit = coversBundle(entry.applicationIdentifier, team, target.bundleId);
296
+ return fit ? [{
297
+ entry,
298
+ profile,
299
+ fit,
300
+ expires,
301
+ devices
302
+ }] : [];
303
+ }).toSorted((a, b) => Number(b.fit === "exact") - Number(a.fit === "exact") || b.expires.getTime() - a.expires.getTime())[0];
304
+ if (!best) return {
305
+ bundleId: target.bundleId,
306
+ onPhone,
307
+ profile: null,
308
+ installed: false
309
+ };
310
+ return {
311
+ bundleId: target.bundleId,
312
+ onPhone,
313
+ profile: {
314
+ name: best.profile.name,
315
+ uuid: best.profile.uuid,
316
+ expires: best.expires.toISOString(),
317
+ devices: best.devices.length
318
+ },
319
+ installed: before.get(best.entry.file) !== best.entry.mtimeMs
320
+ };
321
+ });
322
+ const known = new Set(identitiesBefore.map((identity) => identity.sha1));
323
+ return {
324
+ profiles,
325
+ identities: identitiesAfter.filter((identity) => !known.has(identity.sha1))
326
+ };
327
+ }
328
+ function provisionReportLines(changes, device, resolved = true) {
329
+ const lines = changes.profiles.flatMap((outcome) => {
330
+ if (outcome.profile) {
331
+ if (!resolved && !outcome.installed) return [];
332
+ const listing = outcome.onPhone ? `lists ${device.name} among ${outcome.profile.devices} device(s)` : `lists ${outcome.profile.devices} device(s)`;
333
+ return [`${outcome.bundleId}: ${outcome.installed ? "installed" : "already had"} profile "${outcome.profile.name ?? outcome.profile.uuid}" (${outcome.profile.uuid}), which ${listing} and expires ${outcome.profile.expires.slice(0, 10)}.`];
334
+ }
335
+ if (!resolved) return [];
336
+ return [outcome.onPhone ? `${outcome.bundleId}: xcodebuild resolved signing, but no installed Development profile listing ${device.udid} was found under ${profileDirectories().join(" or ")}.` : `${outcome.bundleId}: no installed Development profile for it was found under ${profileDirectories().join(" or ")}; its device is not the phone, so Stim does not check it further.`];
337
+ });
338
+ for (const identity of changes.identities) lines.push(`New signing identity in the keychain: ${identity.name}.`);
339
+ return lines;
340
+ }
341
+ const PROVISION_TIMEOUT_MS = 9e5;
342
+ /** How long xcodebuild gets after SIGINT before SIGTERM, and after SIGTERM before SIGKILL. */
343
+ const PROVISION_STOP_GRACE_MS = {
344
+ interrupt: 1e4,
345
+ terminate: 5e3
346
+ };
347
+ async function runProvisioningBuild({ args, cwd, env, signal, exec = null, timeoutMs = PROVISION_TIMEOUT_MS, graceMs = PROVISION_STOP_GRACE_MS, onChild = () => () => {} }) {
348
+ const executor = exec || getExecutor();
349
+ const lines = [];
350
+ const executing = createPlanningWatch();
351
+ let resolved = false;
352
+ let timedOut = false;
353
+ let cancelled = false;
354
+ let child;
355
+ try {
356
+ child = executor.spawn("xcodebuild", args, {
357
+ cwd,
358
+ stdio: [
359
+ "ignore",
360
+ "pipe",
361
+ "pipe"
362
+ ],
363
+ env: {
364
+ ...env,
365
+ NSUnbufferedIO: "YES",
366
+ FORCE_COLOR: "0"
367
+ }
368
+ });
369
+ } catch (error) {
370
+ return {
371
+ resolved: false,
372
+ lines,
373
+ code: null,
374
+ timedOut,
375
+ cancelled,
376
+ error: error instanceof Error ? error : new Error(String(error))
377
+ };
378
+ }
379
+ const forget = onChild(child);
380
+ const escalations = [];
381
+ let stopping = false;
382
+ const stop = (first) => {
383
+ if (stopping) return;
384
+ stopping = true;
385
+ child.kill(first);
386
+ const terminateAt = first === "SIGINT" ? graceMs.interrupt : 0;
387
+ if (first === "SIGINT") escalations.push(setTimeout(() => child.kill("SIGTERM"), terminateAt));
388
+ escalations.push(setTimeout(() => child.kill("SIGKILL"), terminateAt + graceMs.terminate));
389
+ };
390
+ const onLine = (line) => {
391
+ lines.push(line);
392
+ if (!resolved && !stopping && executing(line)) {
393
+ resolved = true;
394
+ stop("SIGINT");
395
+ }
396
+ };
397
+ const out = createLineReader(onLine);
398
+ const err = createLineReader(onLine);
399
+ child.stdout?.setEncoding("utf-8");
400
+ child.stderr?.setEncoding("utf-8");
401
+ child.stdout?.on("data", (chunk) => out.push(chunk));
402
+ child.stderr?.on("data", (chunk) => err.push(chunk));
403
+ const timer = setTimeout(() => {
404
+ timedOut = true;
405
+ stop("SIGTERM");
406
+ }, timeoutMs);
407
+ const onAbort = () => {
408
+ cancelled = true;
409
+ stop(signal?.reason === "SIGTERM" ? "SIGTERM" : "SIGINT");
410
+ };
411
+ signal?.addEventListener("abort", onAbort, { once: true });
412
+ if (signal?.aborted) onAbort();
413
+ return new Promise((resolve) => {
414
+ let settled = false;
415
+ const finish = (code, error) => {
416
+ if (settled) return;
417
+ settled = true;
418
+ clearTimeout(timer);
419
+ for (const escalation of escalations) clearTimeout(escalation);
420
+ signal?.removeEventListener("abort", onAbort);
421
+ forget();
422
+ out.flush();
423
+ err.flush();
424
+ resolve({
425
+ resolved: resolved || code === 0 && !cancelled && !timedOut,
426
+ lines,
427
+ code,
428
+ timedOut,
429
+ cancelled,
430
+ ...error ? { error } : {}
431
+ });
432
+ };
433
+ child.on("close", (code) => finish(code));
434
+ child.on("error", (error) => finish(null, error));
435
+ });
436
+ }
437
+ //#endregion
438
+ //#region src/commands/device-provision.ts
439
+ function located(project) {
440
+ const { flag, path, dir } = project;
441
+ return flag && path && dir ? {
442
+ ...project,
443
+ flag,
444
+ path,
445
+ dir
446
+ } : null;
447
+ }
448
+ async function selectXcode(root, settings, { scheme, configuration }) {
449
+ const selected = projectRegistry.selectIos(root);
450
+ if ("problem" in selected) return {
451
+ code: "STIM_NO_PROJECT",
452
+ message: selected.problem.message,
453
+ remedy: selected.problem.remedy
454
+ };
455
+ if (selected.id === "native-xcode") try {
456
+ const native = selectNativeXcodeProject(root, scheme ?? void 0, configuration);
457
+ const packages = nativeXcodePackages(root);
458
+ if (nativeXcodeHasPackages(native) && !existsSync(join(packages, "workspace-state.json"))) return {
459
+ code: "STIM_PROVISION_FAILED",
460
+ message: existsSync(packages) ? `Scheme ${native.scheme} uses Swift packages, and Stim's package directory ${packages} holds no workspace-state.json, so its resolution is incomplete.` : `Scheme ${native.scheme} uses Swift packages, and this workspace has not resolved them yet.`,
461
+ remedy: "Run `stim ios` once (it resolves them into Stim's package directory for a simulator build), or `stim ios --device <udid>` (it resolves them and stops at signing), then run `stim device provision ios` again."
462
+ };
463
+ return {
464
+ project: native.container,
465
+ scheme: native.scheme,
466
+ configuration,
467
+ packageArgs: nativeXcodeHasPackages(native) ? [
468
+ "-clonedSourcePackagesDirPath",
469
+ packages,
470
+ "-disableAutomaticPackageResolution"
471
+ ] : []
472
+ };
473
+ } catch (error) {
474
+ if (error instanceof NativeXcodeError) return {
475
+ code: error.code,
476
+ message: error.message,
477
+ remedy: error.remedy
478
+ };
479
+ throw error;
480
+ }
481
+ const discovered = discoverXcodeProject(root, resolveIosProjectDir(settings, root).dir);
482
+ const project = located(discovered);
483
+ if (discovered.error || !project) return {
484
+ code: "STIM_PROVISION_FAILED",
485
+ message: discovered.error?.message ?? `No Xcode project or workspace was found for ${root}.`,
486
+ remedy: discovered.error?.remedy ?? "Generate the ios/ project, then retry."
487
+ };
488
+ const resolved = await resolveScheme(project, { scheme });
489
+ if (resolved.error || !resolved.scheme) return resolved.error ?? {
490
+ code: "STIM_NO_SCHEME",
491
+ message: `Could not select an app scheme in ${project.path}.`,
492
+ remedy: "Pass --scheme <name>, or set ios.scheme."
493
+ };
494
+ return {
495
+ project,
496
+ scheme: resolved.scheme,
497
+ configuration,
498
+ packageArgs: []
499
+ };
500
+ }
501
+ function lastLines(text, count = 5) {
502
+ return text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(-count).join(" | ");
503
+ }
504
+ function errorField(error, key) {
505
+ return error && typeof error === "object" && key in error ? String(Reflect.get(error, key) ?? "") : "";
506
+ }
507
+ async function readBuildSettings({ project, scheme, configuration, packageArgs }) {
508
+ try {
509
+ return { output: await getExecutor().runFileAsync("xcodebuild", [
510
+ project.flag,
511
+ project.path,
512
+ "-scheme",
513
+ scheme,
514
+ "-configuration",
515
+ configuration,
516
+ "-sdk",
517
+ "iphoneos",
518
+ "-destination",
519
+ "generic/platform=iOS",
520
+ ...packageArgs,
521
+ "-showBuildSettings",
522
+ "-json"
523
+ ], {
524
+ cwd: project.dir,
525
+ timeoutMs: 18e4
526
+ }) };
527
+ } catch (error) {
528
+ return { error: lastLines(errorField(error, "stderr") || errorField(error, "stdout") || errorField(error, "message") || String(error)) };
529
+ }
530
+ }
531
+ function openTerminal() {
532
+ return createInterface({
533
+ input: process.stdin,
534
+ output: process.stderr
535
+ });
536
+ }
537
+ /** Asks `question` on the terminal; only `y` or `yes` confirms, and Ctrl+C or end of input answer no. */
538
+ async function confirmInTerminal(question, open = openTerminal) {
539
+ const input = open();
540
+ const controller = new AbortController();
541
+ const abort = () => controller.abort();
542
+ input.on("SIGINT", abort);
543
+ input.on("close", abort);
544
+ try {
545
+ return /^(y|yes)$/i.test((await input.question(question, { signal: controller.signal })).trim());
546
+ } catch (error) {
547
+ if (error instanceof Error && error.name === "AbortError") return false;
548
+ throw error;
549
+ } finally {
550
+ input.off("SIGINT", abort);
551
+ input.off("close", abort);
552
+ input.close();
553
+ }
554
+ }
555
+ const DEFAULT_DEPS$1 = {
556
+ findProjectRoot: findProjectRoot$1,
557
+ resolveSettings: (root) => resolveProjectSettings(root).settings,
558
+ machineConfig: loadConfig$1,
559
+ env: process.env,
560
+ fileExists: existsSync,
561
+ listIosDevices,
562
+ selectXcode,
563
+ readBuildSettings,
564
+ interactive: process.stdin.isTTY === true && process.stderr.isTTY === true,
565
+ confirm: (question) => confirmInTerminal(question),
566
+ profileFiles: listProfileFiles,
567
+ readInstalledProfiles,
568
+ findSigningIdentities,
569
+ runProvisioningBuild,
570
+ makeDerivedData: (root) => makeTemporaryDirectory(root, "stim-provision-"),
571
+ removeDerivedData: removeTemporaryEntry,
572
+ operation: void 0,
573
+ now: () => Date.now(),
574
+ out: (line) => console.log(line),
575
+ note: (line) => console.error(line)
576
+ };
577
+ const UNCONFIRMED_REMEDY = "Run it in a terminal and answer y, or pass --yes once the owner of the Apple Developer account has agreed to the changes listed. Agents never pass --yes without the user explicitly asking for this step.";
578
+ async function runProvision(platformArg, udidArg, opts = {}, overrides = {}) {
579
+ const d = {
580
+ ...DEFAULT_DEPS$1,
581
+ ...overrides
582
+ };
583
+ const json = Boolean(opts.json);
584
+ const report = (failure) => {
585
+ d.note(chalk.red(`${failure.code}: ${failure.message}`));
586
+ d.note(chalk.dim(failure.remedy));
587
+ if (json) d.out(JSON.stringify({
588
+ code: failure.code,
589
+ message: failure.message,
590
+ remedy: failure.remedy,
591
+ ...failure.plan ? { plan: failure.plan } : {},
592
+ ...failure.changes ? { changes: failure.changes } : {}
593
+ }));
594
+ return failure;
595
+ };
596
+ if (platformArg !== "ios") return report({
597
+ code: "STIM_BAD_ARG",
598
+ message: `\`stim device provision\` takes ios, not ${JSON.stringify(platformArg)}.`,
599
+ remedy: "Run `stim device provision ios`. Android debug builds install on a phone without any developer account change."
600
+ });
601
+ const root = d.findProjectRoot(process.cwd());
602
+ if (!root) return report(NO_PROJECT_REFUSAL);
603
+ const settings = d.resolveSettings(root);
604
+ const auth = provisionAuth(d.machineConfig(), d.env, d.fileExists);
605
+ if ("code" in auth) return report(auth);
606
+ const resolved = resolveIosPhysicalDevice(udidArg ?? null, await d.listIosDevices(), {
607
+ several: "Name the one to provision with `stim device provision ios <udid>`.",
608
+ notAPhone: "Name a physical iPhone or iPad from `xcrun devicectl list devices` with `stim device provision ios <udid>`. Simulators need no provisioning.",
609
+ physicalOnly: "`stim device provision ios` provisions only a physical iPhone or iPad",
610
+ transportOnly: "Stim provisions a phone only over a cable or Wi-Fi"
611
+ });
612
+ if (!resolved.udid) return report({
613
+ code: "STIM_NO_DEVICE",
614
+ message: String(resolved.error),
615
+ remedy: resolved.remedy ?? ""
616
+ });
617
+ const device = {
618
+ udid: resolved.udid,
619
+ name: resolved.name ?? resolved.udid
620
+ };
621
+ const selection = await d.selectXcode(root, settings, {
622
+ scheme: resolveSchemeSelection({ scheme: opts.scheme ?? null }, settings) ?? null,
623
+ configuration: resolveConfiguration(opts.configuration, settings) ?? "Debug"
624
+ });
625
+ if ("code" in selection) return report(selection);
626
+ const { project, scheme, configuration } = selection;
627
+ const buildSettings = await d.readBuildSettings(selection);
628
+ const targets = "output" in buildSettings ? parseSigningTargets(buildSettings.output) : null;
629
+ if (!targets) return report({
630
+ code: "STIM_PROVISION_FAILED",
631
+ message: "error" in buildSettings ? `xcodebuild could not report the ${configuration} build settings of scheme ${scheme}: ${buildSettings.error}` : `xcodebuild reported the ${configuration} build settings of scheme ${scheme}, but not as JSON Stim can read.`,
632
+ remedy: `Run \`xcodebuild ${project.flag} ${project.path} -scheme ${scheme} -configuration ${configuration} -sdk iphoneos -showBuildSettings -json\` to see what it reports.`
633
+ });
634
+ const targetRefusal = signingTargetsRefusal(targets, {
635
+ scheme,
636
+ configuration
637
+ });
638
+ if (targetRefusal) return report(targetRefusal);
639
+ const teams = [...new Set(targets.flatMap((target) => target.team ? [target.team] : []))];
640
+ const plan = {
641
+ platform: "ios",
642
+ udid: device.udid,
643
+ deviceName: device.name,
644
+ teams,
645
+ auth: auth.kind === "api-key" ? {
646
+ kind: "api-key",
647
+ keyId: auth.keyId
648
+ } : { kind: "xcode-account" },
649
+ scheme,
650
+ configuration,
651
+ targets: targets.map(({ target, bundleId, platform }) => ({
652
+ target,
653
+ bundleId,
654
+ platform
655
+ }))
656
+ };
657
+ for (const line of provisionPlanLines({
658
+ device,
659
+ targets,
660
+ auth,
661
+ scheme,
662
+ configuration
663
+ })) d.note(line);
664
+ if (!(opts.yes === true || !json && d.interactive && await d.confirm("Make these changes to the Apple Developer account? [y/N] "))) return report({
665
+ code: "STIM_PROVISION_UNCONFIRMED",
666
+ message: "Nothing changed: provisioning changes an Apple Developer account, so it runs only after explicit confirmation.",
667
+ remedy: UNCONFIRMED_REMEDY,
668
+ plan
669
+ });
670
+ let derivedDataPath;
671
+ try {
672
+ derivedDataPath = d.makeDerivedData(root);
673
+ } catch (error) {
674
+ return report({
675
+ code: "STIM_PROVISION_FAILED",
676
+ message: `Nothing changed: Stim could not create a temporary DerivedData directory: ${error instanceof Error ? error.message : String(error)}`,
677
+ remedy: "Set STIM_TMPDIR, or tempDir in the Stim machine config, to a writable directory outside Git working trees.",
678
+ plan
679
+ });
680
+ }
681
+ const removeDerivedData = () => {
682
+ try {
683
+ d.removeDerivedData(derivedDataPath);
684
+ } catch {}
685
+ };
686
+ const state = operationState(d.operation);
687
+ const drain = () => {
688
+ state.handled = true;
689
+ state.draining = true;
690
+ };
691
+ d.operation?.signal.addEventListener("abort", drain, { once: true });
692
+ let running = null;
693
+ const forcedExit = () => {
694
+ running?.kill("SIGKILL");
695
+ removeDerivedData();
696
+ };
697
+ state.exitCleanups.add(forcedExit);
698
+ const dirs = profileDirectories();
699
+ const profilesBefore = d.profileFiles(dirs);
700
+ const identitiesBefore = await d.findSigningIdentities();
701
+ d.note(chalk.dim(`Provisioning through ${describeAuth(auth)}; xcodebuild contacts Apple...`));
702
+ let run;
703
+ try {
704
+ if (d.operation?.signal.aborted) return report({
705
+ code: "STIM_PROVISION_FAILED",
706
+ message: "Nothing changed: provisioning was interrupted before xcodebuild started.",
707
+ remedy: "Run `stim device provision ios` again.",
708
+ plan
709
+ });
710
+ run = await d.runProvisioningBuild({
711
+ args: provisionXcodebuildArgs({
712
+ project,
713
+ scheme,
714
+ configuration,
715
+ udid: device.udid,
716
+ derivedDataPath,
717
+ auth,
718
+ packageArgs: selection.packageArgs
719
+ }),
720
+ cwd: project.dir,
721
+ env: d.env,
722
+ ...d.operation ? { signal: d.operation.signal } : {},
723
+ onChild: (child) => {
724
+ running = child;
725
+ return () => {
726
+ running = null;
727
+ };
728
+ }
729
+ });
730
+ } finally {
731
+ removeDerivedData();
732
+ state.exitCleanups.delete(forcedExit);
733
+ d.operation?.signal.removeEventListener("abort", drain);
734
+ }
735
+ const changes = provisionChanges({
736
+ before: profilesBefore,
737
+ after: await d.readInstalledProfiles(d.profileFiles(dirs)),
738
+ targets,
739
+ udid: device.udid,
740
+ identitiesBefore,
741
+ identitiesAfter: await d.findSigningIdentities(),
742
+ now: d.now()
743
+ });
744
+ if (!run.resolved) {
745
+ const failure = run.error ? {
746
+ code: "STIM_PROVISION_FAILED",
747
+ message: `Could not run xcodebuild: ${redactKeyPath(run.error.message, auth)}`,
748
+ remedy: "Install Xcode and select it with `sudo xcode-select -s /Applications/Xcode.app`."
749
+ } : run.cancelled ? {
750
+ code: "STIM_PROVISION_FAILED",
751
+ message: "Provisioning was interrupted before xcodebuild resolved signing.",
752
+ remedy: "Run `stim device provision ios` again to finish; it is safe to repeat."
753
+ } : run.timedOut ? {
754
+ code: "STIM_PROVISION_FAILED",
755
+ message: `xcodebuild did not resolve signing within ${PROVISION_TIMEOUT_MS / 6e4} minutes, so Stim stopped it.`,
756
+ remedy: "Check the network connection to Apple and that the phone stays unlocked and connected, then run `stim device provision ios` again."
757
+ } : provisionFailure(run.lines, auth, teams.join(", "));
758
+ const already = provisionReportLines(changes, device, false);
759
+ if (already.length > 0 && !json) {
760
+ d.note("Already changed before it stopped:");
761
+ for (const line of already) d.note(` ${redactKeyPath(line, auth)}`);
762
+ }
763
+ return report({
764
+ ...failure,
765
+ plan,
766
+ changes
767
+ });
768
+ }
769
+ const result = {
770
+ ...plan,
771
+ changes
772
+ };
773
+ if (json) d.out(JSON.stringify(result));
774
+ else {
775
+ const unconfirmed = changes.profiles.some((outcome) => outcome.onPhone && outcome.profile === null);
776
+ d.out(unconfirmed ? chalk.yellow(`xcodebuild resolved signing for ${device.name} (${device.udid}) on team ${teams.join(", ")}, but Stim could not confirm every installed profile.`) : chalk.green(`Provisioned signing for ${device.name} (${device.udid}) on team ${teams.join(", ")}.`));
777
+ for (const line of provisionReportLines(changes, device)) d.out(unconfirmed ? chalk.yellow(line) : line);
778
+ d.out(`Next: stim ios --device ${device.udid}`);
779
+ }
780
+ return result;
781
+ }
782
+ //#endregion
783
+ //#region src/commands/device.ts
784
+ const DEFAULT_FOR = "5m";
785
+ const DEFAULT_DEPS = {
786
+ findProjectRoot: findProjectRoot$1,
787
+ getProject,
788
+ avdNameOf: getAvdNameForSerialAsync,
789
+ listIosDevices,
790
+ listAdbDevices,
791
+ physicalDeviceModel,
792
+ probeEmulatorSerial,
793
+ takeLease,
794
+ releaseLeases: releaseWorkspaceLeases,
795
+ waitForDevice,
796
+ selectFromPool,
797
+ io: fileLeaseIo,
798
+ now: () => Date.now(),
799
+ sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
800
+ out: (line) => console.log(line),
801
+ note: (line) => console.error(line)
802
+ };
803
+ function grantLine(facts, durationText) {
804
+ return `locked ${facts.deviceName ? `${facts.deviceName} (${facts.id})` : facts.id} for ${facts.holder} until ${clockTime(facts.expiresAt)} (${durationText}). Renew: stim device lock ${facts.platform}${facts.slot ? ` --slot ${facts.slot}` : ""} --for ${durationText}. Release: stim device unlock${facts.slot ? ` --slot ${facts.slot}` : ""}.`;
805
+ }
806
+ function releasedLine(lease) {
807
+ return `unlocked ${lease.deviceName ? `${lease.deviceName} (${lease.id})` : lease.id} -- the ${lease.platform} lease ran until ${clockTime(lease.expiresAt)}.`;
808
+ }
809
+ function readPlatform(value) {
810
+ return value === "ios" || value === "android" ? value : null;
811
+ }
812
+ async function resolveDevice(platform, requested, d) {
813
+ if (platform === "ios") {
814
+ const resolved = resolveIosPhysicalDevice(requested, await d.listIosDevices());
815
+ if (!resolved.udid) return {
816
+ code: "STIM_NO_DEVICE",
817
+ message: resolved.error,
818
+ remedy: resolved.remedy ?? null
819
+ };
820
+ return {
821
+ id: resolved.udid,
822
+ deviceName: resolved.name ?? resolved.udid
823
+ };
824
+ }
825
+ const resolved = resolvePhysicalDevice(requested, d.listAdbDevices(), d.probeEmulatorSerial);
826
+ if (!resolved.serial) return {
827
+ code: "STIM_NO_DEVICE",
828
+ message: resolved.error,
829
+ remedy: resolved.remedy ?? null
830
+ };
831
+ return {
832
+ id: resolved.serial,
833
+ deviceName: d.physicalDeviceModel(resolved.serial) ?? resolved.serial
834
+ };
835
+ }
836
+ async function findOwnedVirtualDevice(project, platform, id, avdNameOf) {
837
+ let running;
838
+ for (const { slot, platforms } of projectDeviceSlots(project)) {
839
+ if (platform === "ios") {
840
+ const sim = platforms.ios;
841
+ if (sim?.owned && sim.deviceUdid && sim.deviceUdid.toLowerCase() === id.toLowerCase()) return {
842
+ id: sim.deviceUdid,
843
+ deviceName: sim.deviceName ?? null,
844
+ slot
845
+ };
846
+ continue;
847
+ }
848
+ const emulator = platforms.android;
849
+ if (!emulator?.owned || !emulator.avdName || !/^emulator-\d+$/.test(id)) continue;
850
+ running ??= { name: await avdNameOf(id) };
851
+ if (running.name === emulator.avdName) return {
852
+ id,
853
+ deviceName: emulator.deviceName ?? emulator.avdName,
854
+ slot
855
+ };
856
+ }
857
+ return null;
858
+ }
859
+ function isFailure(value) {
860
+ return "code" in value;
861
+ }
862
+ async function poolDevice(platform, idLabel, waitSeconds, deadline, root, d, slot) {
863
+ const isEmulator = memoizeEmulatorProbe(d.probeEmulatorSerial);
864
+ const pooled = await d.selectFromPool({
865
+ ...slot ? { slot } : {},
866
+ root,
867
+ platform,
868
+ idLabel,
869
+ list: async () => platform === "ios" ? iosPoolCandidates(await d.listIosDevices()).map((entry) => ({
870
+ id: entry.udid,
871
+ name: entry.name,
872
+ fallback: isWirelessIosDevice(entry)
873
+ })) : androidPoolCandidates(d.listAdbDevices(), isEmulator).map((entry) => ({ id: entry.serial })),
874
+ noCandidates: async () => {
875
+ const resolved = platform === "ios" ? iosPoolNoCandidatesRefusal(await d.listIosDevices()) : androidPoolNoCandidatesRefusal(d.listAdbDevices(), isEmulator);
876
+ return {
877
+ message: resolved.error,
878
+ remedy: resolved.remedy
879
+ };
880
+ },
881
+ waitSeconds,
882
+ deadline,
883
+ now: d.now,
884
+ sleep: d.sleep,
885
+ warn: (line) => d.note(chalk.yellow(line)),
886
+ io: d.io
887
+ });
888
+ if (pooled.status === "refused") return {
889
+ code: pooled.refusal.code,
890
+ message: pooled.refusal.message,
891
+ remedy: pooled.refusal.remedy,
892
+ ...pooled.refusal.lease === null ? {} : { lease: pooled.refusal.lease }
893
+ };
894
+ const name = pooled.candidate.name ?? (platform === "ios" ? pooled.candidate.id : d.physicalDeviceModel(pooled.candidate.id));
895
+ return {
896
+ id: pooled.candidate.id,
897
+ deviceName: name ?? pooled.candidate.id
898
+ };
899
+ }
900
+ async function runLock(platformArg, idArg, opts = {}, overrides = {}) {
901
+ const d = {
902
+ ...DEFAULT_DEPS,
903
+ ...overrides
904
+ };
905
+ const json = Boolean(opts.json);
906
+ const report = (failure) => {
907
+ d.note(chalk.red(`${failure.code}: ${failure.message}`));
908
+ if (failure.remedy) d.note(chalk.dim(failure.remedy));
909
+ if (json) d.out(JSON.stringify({
910
+ code: failure.code,
911
+ message: failure.message,
912
+ remedy: failure.remedy,
913
+ ...failure.lease === void 0 ? {} : { lease: failure.lease }
914
+ }));
915
+ return failure;
916
+ };
917
+ const platform = readPlatform(platformArg);
918
+ if (!platform) return report({
919
+ code: "STIM_BAD_ARG",
920
+ message: `\`stim device lock\` takes ios or android, not ${JSON.stringify(platformArg)}.`,
921
+ remedy: "Run `stim device lock ios` or `stim device lock android`."
922
+ });
923
+ const root = d.findProjectRoot(process.cwd());
924
+ if (!root) return report(NO_PROJECT_REFUSAL);
925
+ const durationText = String(opts.for ?? DEFAULT_FOR).trim();
926
+ const duration = parseLeaseDuration(durationText);
927
+ if ("error" in duration) return report({
928
+ code: "STIM_BAD_ARG",
929
+ message: duration.error,
930
+ remedy: "Pass a whole number of seconds or minutes from 10s to 30m, e.g. --for 90s or --for 10m."
931
+ });
932
+ const wait = parseDeviceWait(opts.wait);
933
+ if ("error" in wait) return report({
934
+ code: "STIM_BAD_ARG",
935
+ message: wait.error,
936
+ remedy: "Pass a whole number of seconds, e.g. --wait 90. `--wait 0` refuses a leased device at once."
937
+ });
938
+ const idLabel = platform === "ios" ? "udid" : "serial";
939
+ const deadline = d.now() + wait.seconds * 1e3;
940
+ const lastLine = { at: null };
941
+ const owned = idArg ? await findOwnedVirtualDevice(d.getProject(root), platform, idArg, d.avdNameOf) : null;
942
+ if (owned && opts.slot && opts.slot !== owned.slot) return report({
943
+ code: "STIM_BAD_ARG",
944
+ message: `${owned.id} is this workspace's ${platform} device in slot ${owned.slot}, not ${opts.slot}.`,
945
+ remedy: `Run \`stim device lock ${platform} ${owned.id}\` without --slot, or with --slot ${owned.slot}.`
946
+ });
947
+ const held = owned ? d.io.readHolder(root)[deviceSlotKey(platform, owned.slot)] : void 0;
948
+ if (owned && held && held.id !== owned.id) return report({
949
+ code: "STIM_DEVICE_BUSY",
950
+ message: `This workspace already leases ${held.id} for ${platform} in slot ${owned.slot}, and a workspace holds one lease per platform and slot.`,
951
+ remedy: `Release it with \`stim device unlock ${platform}${owned.slot === "default" ? "" : ` --slot ${owned.slot}`}\` once nothing depends on it, then lock ${owned.id}.`
952
+ });
953
+ const slot = owned ? owned.slot === "default" ? void 0 : owned.slot : opts.slot;
954
+ const device = owned ? owned : idArg ? await resolveDevice(platform, idArg, d) : await poolDevice(platform, idLabel, wait.seconds, deadline, root, d, opts.slot);
955
+ if (isFailure(device)) return report(device);
956
+ for (;;) {
957
+ const outcome = await d.waitForDevice({
958
+ ...slot ? { slot } : {},
959
+ root,
960
+ platform,
961
+ id: device.id,
962
+ idLabel,
963
+ waitSeconds: wait.seconds,
964
+ now: d.now,
965
+ sleep: d.sleep,
966
+ warn: (line) => d.note(chalk.yellow(line)),
967
+ io: d.io,
968
+ deadline,
969
+ lastLine
970
+ });
971
+ if (outcome.status === "refused") return report({
972
+ ...outcome.refusal,
973
+ lease: outcome.refusal.lease
974
+ });
975
+ const taken = d.takeLease({
976
+ ...slot ? { slot } : {},
977
+ root,
978
+ platform,
979
+ id: device.id,
980
+ deviceName: device.deviceName,
981
+ kind: "declared",
982
+ durationMs: duration.ms
983
+ }, d.io);
984
+ if (taken.status === "unreadable") return report({
985
+ code: "STIM_DEVICE_BUSY",
986
+ message: `The lease file at ${taken.path} does not parse, so Stim cannot tell who holds that device.`,
987
+ remedy: `Read ${taken.path} and, once you know no run depends on it, delete it. \`stim gc\` reports it on every run.`,
988
+ lease: {
989
+ platform: null,
990
+ id: null,
991
+ deviceName: null,
992
+ holder: null,
993
+ expiresAt: null
994
+ }
995
+ });
996
+ if (taken.status === "held") continue;
997
+ const facts = {
998
+ ...taken.lease.slot ? { slot: taken.lease.slot } : {},
999
+ platform,
1000
+ id: taken.lease.id,
1001
+ deviceName: taken.lease.deviceName,
1002
+ holder: taken.lease.holder,
1003
+ kind: "declared",
1004
+ grantedAt: taken.lease.grantedAt,
1005
+ expiresAt: taken.lease.expiresAt,
1006
+ leaseSeconds: Math.round(duration.ms / 1e3)
1007
+ };
1008
+ if (json) d.out(JSON.stringify(facts));
1009
+ else d.out(chalk.green(grantLine(facts, durationText)));
1010
+ return facts;
1011
+ }
1012
+ }
1013
+ async function runUnlock(platformArg, opts = {}, overrides = {}) {
1014
+ const d = {
1015
+ ...DEFAULT_DEPS,
1016
+ ...overrides
1017
+ };
1018
+ const json = Boolean(opts.json);
1019
+ const report = (failure) => {
1020
+ d.note(chalk.red(`${failure.code}: ${failure.message}`));
1021
+ if (failure.remedy) d.note(chalk.dim(failure.remedy));
1022
+ if (json) d.out(JSON.stringify({
1023
+ code: failure.code,
1024
+ message: failure.message,
1025
+ remedy: failure.remedy
1026
+ }));
1027
+ return failure;
1028
+ };
1029
+ const platform = platformArg === void 0 ? null : readPlatform(platformArg);
1030
+ if (platformArg !== void 0 && !platform) return report({
1031
+ code: "STIM_BAD_ARG",
1032
+ message: `\`stim device unlock\` takes ios or android, not ${JSON.stringify(platformArg)}.`,
1033
+ remedy: "Run `stim device unlock` for every lease this workspace holds, or name one platform."
1034
+ });
1035
+ const root = d.findProjectRoot(process.cwd());
1036
+ if (!root) return report(NO_PROJECT_REFUSAL);
1037
+ const released = d.releaseLeases(root, {
1038
+ platform,
1039
+ ...opts.slot ? { slot: opts.slot } : {}
1040
+ }, d.io);
1041
+ if (released.length === 0) d.note(chalk.dim(`No ${platform ? `${platform} ` : ""}device lease to release for ${root}. \`stim status\` lists every lease on this machine.`));
1042
+ if (json) d.out(JSON.stringify(released.map((lease) => ({
1043
+ ...lease,
1044
+ holder: root
1045
+ }))));
1046
+ else for (const lease of released) d.out(chalk.green(releasedLine(lease)));
1047
+ return released;
1048
+ }
1049
+ function registerDevice(program, deps = {}) {
1050
+ const device = program.command("device").description("Lease a connected physical device to this workspace, give it back, or provision iOS signing for it");
1051
+ device.command("lock").argument("<platform>", "ios or android").argument("[id]", "the UDID or serial to lease, a connected device or this workspace's own simulator or emulator; without one, the first free connected device is used").description("Lease a connected physical device, or this workspace's own simulator or emulator, to this workspace for a declared time, so another workspace's `--device` run waits and agents see the device as driven. Nothing but this command and a `--device` run moves the expiry.").option("--for <duration>", `How long to hold it: a whole number of seconds or minutes from 10s to 30m (default ${DEFAULT_FOR})`).option("--wait <seconds>", "How long to wait for a device another workspace holds, before refusing with STIM_DEVICE_BUSY (default 60, 0 refuses at once)").option("--slot <name>", "Use this workspace device slot", parseDeviceSlotOption).option("--json", "print the lease as JSON on stdout").action(async (platform, id, opts) => {
1052
+ if ("code" in await runLock(platform, id, opts, deps)) process.exit(1);
1053
+ });
1054
+ device.command("unlock").argument("[platform]", "ios or android; without one, every lease this workspace holds is released").description("Release the device lease or leases this workspace holds. Releasing nothing is not an error.").option("--slot <name>", "Use this workspace device slot", parseDeviceSlotOption).option("--json", "print the released leases as JSON on stdout").action(async (platform, opts) => {
1055
+ const result = await runUnlock(platform, opts, deps);
1056
+ if (!Array.isArray(result)) process.exit(1);
1057
+ });
1058
+ device.command("provision").argument("<platform>", "ios").argument("[udid]", "the connected iPhone or iPad to provision for; required when more than one is connected").description("Register a connected iPhone with the project's Apple Developer team and create or refresh its iOS App Development profile, through xcodebuild -allowProvisioningUpdates. It lists the account changes and asks first; no build ever does this.").option("--scheme <name>", "Xcode scheme whose signed targets to provision (default: ios.scheme, then detected)").option("--configuration <name>", "build configuration to provision (default: ios.configuration, then Debug)").option("--yes", "confirm the listed Apple Developer account changes without a prompt").option("--json", "print the plan and result, or the refusal, as JSON on stdout").action(async (platform, udid, opts) => {
1059
+ const root = findProjectRoot$1(process.cwd()) ?? process.cwd();
1060
+ const { value: result, interrupted } = await runProcessOperation(root, (operation) => runProvision(platform, udid, opts, { operation }));
1061
+ if ("code" in result) process.exit(interrupted ?? 1);
1062
+ });
1063
+ }
1064
+ function deviceCommand(program) {
1065
+ registerDevice(program);
1066
+ }
1067
+ //#endregion
1068
+ export { deviceCommand as default, grantLine, registerDevice, releasedLine, runLock, runUnlock };