stim 1.20.2 → 1.22.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 (231) hide show
  1. package/README.md +141 -8
  2. package/dist/acquire-artifact-Co2k4f7O.mjs +171 -0
  3. package/dist/act-DsuqEkiM.mjs +2801 -0
  4. package/dist/activity-CMLmzp7o.mjs +1397 -0
  5. package/dist/{agent-device-usage-output-Dzgv6lgR.mjs → agent-device-usage-output-DUFaTMdd.mjs} +23 -67
  6. package/dist/{android-DCKZrNvV.mjs → android-ByRMUoiJ.mjs} +157 -93
  7. package/dist/{android-Mz_Inn3w.mjs → android-C7sVmyI9.mjs} +299 -116
  8. package/dist/{android-Dlzxshgp.mjs → android-DGkNqa_c.mjs} +5 -5
  9. package/dist/{android-HqP1_vB_.mjs → android-DR5lZ2Az.mjs} +2 -2
  10. package/dist/android-cas-compiler.mjs +2 -2
  11. package/dist/{android-cas-Bl-cLJBk.mjs → android-cas-ngferVq2.mjs} +7 -6
  12. package/dist/android-project-BCAAmMwz.mjs +12 -0
  13. package/dist/android-project-DVWyCbWU.d.mts +391 -0
  14. package/dist/api.d.mts +810 -64
  15. package/dist/api.mjs +782 -1
  16. package/dist/{artifact-HSwkIWEQ.mjs → artifact-8EY8SAPw.mjs} +240 -213
  17. package/dist/{artifact-D3czlUvT.mjs → artifact-DJPy518Y.mjs} +203 -164
  18. package/dist/artifact-platform-CXcmX_j3.mjs +27 -0
  19. package/dist/{browser-web-CA-F1GQo.mjs → browser-web-Bcv99PBM.mjs} +194 -64
  20. package/dist/budget-Cvwn54qV.mjs +9285 -0
  21. package/dist/{build-DYDbfkk5.mjs → build-CabC3G23.mjs} +153 -98
  22. package/dist/build-cache-Dv7RN200.mjs +602 -0
  23. package/dist/build-plan-DUC8Ka9y.mjs +248 -0
  24. package/dist/{cache-manifest-XH-fvDUj.mjs → cache-manifest-DSeQjt-Q.mjs} +2 -2
  25. package/dist/cache-manifest.mjs +1 -1
  26. package/dist/{ccache-zdKiYQ3E.mjs → ccache-Btwc36-I.mjs} +10 -9
  27. package/dist/{cdp-Dq_ypAlJ.mjs → cdp-3fwICjzi.mjs} +18 -5
  28. package/dist/{chrome-BnzZMqsR.mjs → chrome-Bbw689-6.mjs} +7 -4
  29. package/dist/{cli-CLP3diBm.mjs → cli-CHZa9Lvk.mjs} +20 -20
  30. package/dist/cli.mjs +1 -1
  31. package/dist/client-OyRoEuSr.mjs +1577 -0
  32. package/dist/collector-run.d.mts +4 -40
  33. package/dist/collector-run.mjs +9 -8
  34. package/dist/{command-output-BTy8hZ0F.mjs → command-output-gwpm28fu.mjs} +1 -1
  35. package/dist/config-DlrsduwG.mjs +955 -0
  36. package/dist/{created-devices-Dzd5C2LF.mjs → created-devices-x9rN6bhs.mjs} +14 -4
  37. package/dist/{dependency-state-DG0p31-y.mjs → dependency-state-1I9KZT1Y.mjs} +20 -5
  38. package/dist/{detached-entry-B9C_zeQC.mjs → detached-entry-BwNEtIrP.mjs} +1 -1
  39. package/dist/{dev-client-DAWVIGBu.mjs → dev-client-BSDR5h3F.mjs} +2 -2
  40. package/dist/{device-BECOiufl.mjs → device-B-L3Fbxy.mjs} +91 -34
  41. package/dist/device-DklWZLqb.mjs +1068 -0
  42. package/dist/{device-capacity-CBUAcRyQ.mjs → device-capacity-DK5_EwRS.mjs} +42 -13
  43. package/dist/device-host-worker.mjs +76 -79
  44. package/dist/{device-ios-COSi6oS1.mjs → device-ios-DoB6KFCO.mjs} +77 -51
  45. package/dist/{device-lease-run-Cw2M4z51.mjs → device-lease-run-xJPQ_QQG.mjs} +14 -26
  46. package/dist/{device-pool-Bf-pOhSw.mjs → device-pool-BMkdsefq.mjs} +5 -5
  47. package/dist/{doctor-kfMBnUw6.mjs → doctor-Bo7BXLux.mjs} +271 -320
  48. package/dist/{doctor-CB8oZKCk.mjs → doctor-D5_EzwfA.mjs} +186 -91
  49. package/dist/doctor-fingerprint-DZLoYFVb.mjs +16 -0
  50. package/dist/doctor-fingerprint.mjs +19 -0
  51. package/dist/doctor-thjWAV0z.mjs +2 -0
  52. package/dist/{doctor-watchman-BRyV2adA.mjs → doctor-watchman-N2wDotwQ.mjs} +40 -21
  53. package/dist/{eas-build-JyfqyHfC.mjs → eas-build-CwknaQYj.mjs} +53 -20
  54. package/dist/{build-plan-B7SZsc2N.mjs → eas-fallback-CSgAulr5.mjs} +41 -214
  55. package/dist/{watchman-CPQFZLXY.mjs → eas-session-ledger-DlY_serQ.mjs} +230 -11
  56. package/dist/{error-diagnostics-BR677IDM.mjs → error-diagnostics-DBWO2rxE.mjs} +172 -82
  57. package/dist/exec-DEoAhcze.mjs +858 -0
  58. package/dist/exec-DnYc4HGH.d.mts +78 -0
  59. package/dist/{gc-qWOMdnER.mjs → gc-CXxN-4SE.mjs} +1091 -1029
  60. package/dist/gc-DnNDQyaP.mjs +3 -0
  61. package/dist/{guide-G9cAbVAc.mjs → guide-Br0LEfKy.mjs} +1165 -297
  62. package/dist/{hosted-android-C3MHGld1.mjs → hosted-android-B-9f-jkd.mjs} +10 -8
  63. package/dist/{hosted-client-ZEKpS9vw.mjs → hosted-client-DZAPrcro.mjs} +34 -19
  64. package/dist/{hosted-ios-l-o3iM9J.mjs → hosted-ios-CsCS21hn.mjs} +1 -1
  65. package/dist/{hosted-logs-EzQM7-3Z.mjs → hosted-logs-FIRx9X2f.mjs} +77 -43
  66. package/dist/{hosted-macos-BACVAVqE.mjs → hosted-macos-_awYSpfv.mjs} +35 -22
  67. package/dist/{hosted-native-A4s1QPXe.mjs → hosted-native-BdzE668-.mjs} +49 -27
  68. package/dist/idle-C4cZ757K.d.mts +24 -0
  69. package/dist/{idle-shutdown-DtxhPTKe.mjs → idle-shutdown-DDg3SdvY.mjs} +11 -10
  70. package/dist/ios-CDvlmUs7.d.mts +24 -0
  71. package/dist/{ios-BwVCF7oA.mjs → ios-Ds1clbIt.mjs} +8 -8
  72. package/dist/{ios-device-D7pi2Sca.mjs → ios-device--h_-8ENa.mjs} +51 -26
  73. package/dist/{ios-device-B5veSDDQ.mjs → ios-device-BXZ0c7Kb.mjs} +1 -1
  74. package/dist/{ios-RkW4pnQf.mjs → ios-hcBpnEGk.mjs} +200 -93
  75. package/dist/{ios-Ce7bc7br.mjs → ios-pm4nL6By.mjs} +160 -108
  76. package/dist/{ios-project-K9hY_Jab.mjs → ios-project-CZhKODF3.mjs} +13 -13
  77. package/dist/ios-signing-BtAffm0A.mjs +489 -0
  78. package/dist/{ios-state-CBLe1xYw.mjs → ios-state-Dtw5K2aI.mjs} +1 -1
  79. package/dist/{ios-nOLJGVdy.mjs → ios-xtlRHaeB.mjs} +1 -1
  80. package/dist/{js-swap-Bb3nsS07.mjs → js-swap-Bwhh4S7V.mjs} +30 -25
  81. package/dist/{launch-C3qJ6Agb.mjs → launch-BjxemNpK.mjs} +85 -543
  82. package/dist/{launch-F4sOqAhB.mjs → launch-CCzXlc8r.mjs} +91 -90
  83. package/dist/logs-BfwwadsZ.mjs +479 -0
  84. package/dist/logs-jTS_lHSo.mjs +248 -0
  85. package/dist/{macos-D7uDvO-z.mjs → macos-DVlSSyUg.mjs} +1 -1
  86. package/dist/macos-NfV2D7dO.mjs +694 -0
  87. package/dist/macos-run.mjs +1 -1
  88. package/dist/maintenance-run.mjs +123 -248
  89. package/dist/{metro-DnCEjCxs.mjs → metro-DiXtA3Ty.mjs} +107 -30
  90. package/dist/metro-gateway-CKZqKqx7.mjs +209 -0
  91. package/dist/{named-ports-4WopRPg-.mjs → named-ports-DkEzOfzO.mjs} +42 -90
  92. package/dist/native-android-CqgnOgsT.mjs +645 -0
  93. package/dist/native-gradle-artifact-inputs-DHFnmrVf.mjs +270 -0
  94. package/dist/native-gradle-inputs-Dnh84Zbd.mjs +211 -0
  95. package/dist/native-inputs-TRTTTRxt.mjs +91 -0
  96. package/dist/native-run-NwUuxbkg.mjs +276 -0
  97. package/dist/{native-runtime-k8dTIrbK.mjs → native-runtime-B7KIafpM.mjs} +11 -9
  98. package/dist/native-source-Y0yLhFyQ.mjs +133 -0
  99. package/dist/{native-xcode-inputs-CykopJdM.mjs → native-xcode-inputs-D_VOfFL7.mjs} +26 -18
  100. package/dist/{native-xcode-ios-Bbf30Gm0.mjs → native-xcode-ios-BYDOHWLN.mjs} +104 -60
  101. package/dist/{ndjson-GOnbyvso.mjs → ndjson-CgE_o3Dl.mjs} +3 -3
  102. package/dist/offload-worker.d.mts +59 -41
  103. package/dist/offload-worker.mjs +478 -115
  104. package/dist/operation-exec-BW0em61G.mjs +171 -0
  105. package/dist/{ownership-DCtw_2IA.mjs → ownership-C5fo5Edb.mjs} +3 -3
  106. package/dist/{page-DRHy329N.mjs → page-DBL2PiUU.mjs} +12 -5
  107. package/dist/paths-BsJMoPP2.mjs +12 -0
  108. package/dist/paths-DQtTgsBf.mjs +42 -0
  109. package/dist/{placement-log-MOfPVgaM.mjs → placement-log-BnGgJRRZ.mjs} +5 -5
  110. package/dist/{plan-placement-V0TdzYXp.mjs → plan-placement-DIsMtN-d.mjs} +15 -11
  111. package/dist/{ports-DdSUicVo.mjs → ports-Cy1RkmuQ.mjs} +3 -3
  112. package/dist/prebuild-D4JoEAEs.mjs +484 -0
  113. package/dist/{project-_phNIJfs.mjs → project-wM_XFTvE.mjs} +65 -37
  114. package/dist/{pull-request-CQ8sPbZa.mjs → pull-request-W1IuLXDt.mjs} +102 -20
  115. package/dist/pull-request-eODyIqZF.d.mts +124 -0
  116. package/dist/pull-requests.d.mts +2 -120
  117. package/dist/pull-requests.mjs +2 -2
  118. package/dist/{react-native-android-CM1XRDwv.mjs → react-native-android-6OZ7MSM7.mjs} +121 -72
  119. package/dist/{gradle-DSrXb6lG.mjs → react-native-build-Dvd8hC21.mjs} +292 -15
  120. package/dist/react-native-doctor-DsoSF1Y9.mjs +234 -0
  121. package/dist/{react-native-ios-Dhq_eiYQ.mjs → react-native-ios-4u1Rn-GR.mjs} +71 -39
  122. package/dist/{recordings-D0d5aQt0.mjs → recordings-CQmfzAh7.mjs} +12 -9
  123. package/dist/registration-BcRgF6Ox.mjs +51 -0
  124. package/dist/{reload-C8JhxjSh.mjs → reload-BzkYabB-.mjs} +94 -47
  125. package/dist/reload-D7AVlb8N.mjs +2 -0
  126. package/dist/{remote-BiR6aYjy.mjs → remote-DjGDmlTZ.mjs} +26 -18
  127. package/dist/result-D2Ykz_iD.mjs +435 -0
  128. package/dist/{run-BtPgpKks.mjs → run-DidHghD1.mjs} +5 -5
  129. package/dist/selection-DSMbWEX2.mjs +620 -0
  130. package/dist/{server-bare-R2m_gsH8.mjs → server-bare-CafzuyFB.mjs} +3 -4
  131. package/dist/{server-command-CTtCkmcQ.mjs → server-command-C6QjeupS.mjs} +14 -7
  132. package/dist/{server-expo-DsguMGUF.mjs → server-expo-CrjB29pq.mjs} +54 -11
  133. package/dist/server-expo-D58c9L0M.mjs +2 -0
  134. package/dist/{settings-B717odTD.mjs → settings-C7O8A6ZI.mjs} +309 -119
  135. package/dist/settings-DpR-1Wnn.mjs +114 -0
  136. package/dist/settings-write-CzD60rOD.mjs +360 -0
  137. package/dist/settings.schema.json +176 -16
  138. package/dist/{simslim-CCr3rE-A.mjs → simslim-BNckWpXi.mjs} +14 -12
  139. package/dist/{slot-launch-DPt0m9Nd.mjs → slot-launch-CbfmNgM6.mjs} +15 -20
  140. package/dist/{spawn-entry-Dcb7sDUz.mjs → spawn-entry-jw19m8Nl.mjs} +0 -1
  141. package/dist/stage-Bps5oius.mjs +200 -0
  142. package/dist/{start-TXeMmmO2.mjs → start-Br-oxqMD.mjs} +1 -1
  143. package/dist/start-EojN4WFz.mjs +1364 -0
  144. package/dist/{state-D1Icyjlu.mjs → state--smjFSTL.mjs} +55 -13
  145. package/dist/{state-CG_OFoaA.mjs → state-08I3hw3_.mjs} +3 -2
  146. package/dist/{stats-BoY5sEPU.mjs → stats-D3GerlFW.mjs} +7 -13
  147. package/dist/stats-report-BzaPrRm6.mjs +16 -0
  148. package/dist/{status-BTA7kKiQ.mjs → status-BRROykC8.mjs} +7 -5
  149. package/dist/status-EvrK72L2.mjs +2 -0
  150. package/dist/{status-WgFbkRzj.mjs → status-WBDajew2.mjs} +554 -452
  151. package/dist/{status-watch-C23ILtf9.mjs → status-watch-Bg_u-pwR.mjs} +222 -25
  152. package/dist/status-worker.d.mts +1 -0
  153. package/dist/status-worker.mjs +19 -0
  154. package/dist/stim-desktop-D5OGpYZM.mjs +112 -0
  155. package/dist/{stop-DR6VoDIk.mjs → stop-DTZ9uTw3.mjs} +2 -2
  156. package/dist/{stop-DPEhunlw.mjs → stop-Dky10AEJ.mjs} +33 -23
  157. package/dist/{stop-BNmBMi1F.mjs → stop-Mv6W9FAk.mjs} +137 -98
  158. package/dist/supervisor-run.d.mts +8 -17
  159. package/dist/supervisor-run.mjs +56 -28
  160. package/dist/{support-CwjbFs9H.mjs → support-CNioewsd.mjs} +14 -14
  161. package/dist/{support-DMTqj3Hm.mjs → support-yUiOjnVY.mjs} +20 -13
  162. package/dist/swiftpm-artifact-inputs-DFcGsXDP.mjs +153 -0
  163. package/dist/swiftpm-macos-BIak-Rxv.mjs +532 -0
  164. package/dist/toolchain-BY8X8HXx.mjs +2 -0
  165. package/dist/toolchain-DpJMHq0n.mjs +876 -0
  166. package/dist/{trigger-CigAYjeL.mjs → trigger-Cbyc41d_.mjs} +21 -6
  167. package/dist/trigger-yb_nFzPF.mjs +2 -0
  168. package/dist/{warm-progress-BuY-Fvfn.mjs → warm-progress-CA3TgT3-.mjs} +263 -156
  169. package/dist/web-CZ07gd77.mjs +2 -0
  170. package/dist/{web-BDBMxDpx.mjs → web-DKx6yoFA.mjs} +20 -14
  171. package/dist/web-run.mjs +13 -12
  172. package/dist/windows-report.d.mts +1 -0
  173. package/dist/windows-report.mjs +18 -0
  174. package/dist/windows-report.ps1 +43 -0
  175. package/dist/{workspace-state-DJt_MMvf.mjs → workspace-state-U08dEzhG.mjs} +62 -58
  176. package/dist/{workspaces-C36s2jb9.mjs → workspaces-CpANmBlq.mjs} +1723 -1286
  177. package/dist/{worktree-CgvJYJ-Q.mjs → worktree-CHecZ4Oq.mjs} +1097 -547
  178. package/dist/worktree-ZqZVjD9K.mjs +2 -0
  179. package/dist/xcode-BXn9-xYx.mjs +896 -0
  180. package/package.json +7 -5
  181. package/shim/android-audio/Main.java +24 -0
  182. package/shim/android-audio/README.md +17 -0
  183. package/shim/android-audio.jar +0 -0
  184. package/shim/android-optimizations.gradle +7 -5
  185. package/dist/activity-BSV745Kz.mjs +0 -629
  186. package/dist/api-qvOzWyfO.mjs +0 -154
  187. package/dist/api-run.mjs +0 -294
  188. package/dist/app-install-BIKtMfPq.mjs +0 -896
  189. package/dist/artifact-lifecycle-DzPpBYvK.mjs +0 -30
  190. package/dist/budget-f7516Zk0.mjs +0 -4106
  191. package/dist/build-facts-DcLv2HSd.d.mts +0 -111
  192. package/dist/cancellation-C_22VQTH.mjs +0 -24
  193. package/dist/client-qQAaXSUs.mjs +0 -1771
  194. package/dist/config-BYDxlMY3.mjs +0 -562
  195. package/dist/device-DuiPJQSn.mjs +0 -290
  196. package/dist/device-lease-BX1sJQ2E.mjs +0 -1224
  197. package/dist/device-remote-xRT0Vmo9.mjs +0 -1272
  198. package/dist/eas-session-ledger-Cm0Gwq2F.mjs +0 -207
  199. package/dist/exec-DqKo9GKJ.mjs +0 -268
  200. package/dist/git-DDfFzkMm.mjs +0 -45
  201. package/dist/launch-verify-B6Q8hZP-.mjs +0 -428
  202. package/dist/logs-BGrEUaEs.mjs +0 -383
  203. package/dist/macos-Ch5VJmcS.mjs +0 -584
  204. package/dist/metro-gateway-CX9Xlkq2.mjs +0 -103
  205. package/dist/miss-reason-mKzZ4VvN.mjs +0 -238
  206. package/dist/native-android-D5qc3-_v.mjs +0 -226
  207. package/dist/native-gradle-inputs-BsLRrl7u.mjs +0 -349
  208. package/dist/ndjson-Du3j0AQ9.d.mts +0 -17
  209. package/dist/plan-C4TjdPsR.mjs +0 -260
  210. package/dist/prebuild-DYbucPXy.mjs +0 -212
  211. package/dist/preview-78a9kR_-.mjs +0 -1011
  212. package/dist/react-native-build-CtCccJtb.mjs +0 -239
  213. package/dist/react-native-doctor-CfRnFfjs.mjs +0 -78
  214. package/dist/remote-cache-OyOyVIS3.mjs +0 -526
  215. package/dist/result-CaJg5Iat.mjs +0 -186
  216. package/dist/server-expo-Bigriskl.mjs +0 -2
  217. package/dist/settings-BORNaaCO.mjs +0 -402
  218. package/dist/settings-vk3mJGSm.d.mts +0 -13
  219. package/dist/spawn-claims-DVS5RkMS.mjs +0 -72
  220. package/dist/stage-KNTQopqv.mjs +0 -157
  221. package/dist/start-BWqzODpb.mjs +0 -1116
  222. package/dist/stim-desktop-BzBN2Em2.mjs +0 -63
  223. package/dist/swiftpm-macos-DRNVJWlb.mjs +0 -119
  224. package/dist/tailnet-Dh-qiYzG.mjs +0 -265
  225. package/dist/toolchain-BFkAcstd.mjs +0 -2
  226. package/dist/toolchain-DxSsNssu.mjs +0 -1657
  227. package/dist/trigger-9_USgmxz.mjs +0 -2
  228. package/dist/web-Dq-i3wzJ.mjs +0 -2
  229. package/dist/worktree-DpLdCHga.mjs +0 -2
  230. package/dist/{api-run.d.mts → doctor-fingerprint.d.mts} +0 -0
  231. package/dist/{errors-DzkMYh8Q.mjs → errors-ccaPc4ox.mjs} +2 -2
@@ -0,0 +1,1577 @@
1
+ import { _ as reportSignal, b as debugLog, m as operationState, t as getExecutor } from "./exec-DEoAhcze.mjs";
2
+ import { Q as openAuthenticated, _ as loadConfig$1, it as sayHello, rt as resolveEndpoint } from "./config-DlrsduwG.mjs";
3
+ import { f as throwIfCancelled, o as git, s as gitAsync } from "./operation-exec-BW0em61G.mjs";
4
+ import { r as createNdjsonWriter } from "./ndjson-CgE_o3Dl.mjs";
5
+ import { X as emptyCaches, br as discoverCaches, nt as planCacheEmptying } from "./budget-Cvwn54qV.mjs";
6
+ import { t as canonicalPath } from "./paths-BsJMoPP2.mjs";
7
+ import { d as runCancellationSignal } from "./native-run-NwUuxbkg.mjs";
8
+ import { n as CCACHE_UNAVAILABLE, t as CCACHE_NOT_RUN } from "./ccache-Btwc36-I.mjs";
9
+ import { n as COMPILATION_CACHE_UNAVAILABLE, t as COMPILATION_CACHE_NOT_RUN } from "./xcode-BXn9-xYx.mjs";
10
+ import { t as resolveMaintenanceSettings } from "./settings-Djl-mpjX.mjs";
11
+ import { a as iosToolchainAsync, d as toolchainMismatches, f as workerToolchain, g as readRubyVersion } from "./toolchain-DpJMHq0n.mjs";
12
+ import { l as pairedMachines, r as namedBuildMachine, t as OffloadRefusal } from "./selection-DSMbWEX2.mjs";
13
+ import { r as manifestDigest } from "./native-inputs-TRTTTRxt.mjs";
14
+ import { a as sourceManifest, i as nativeTransferManifest, n as gitVisiblePaths, t as checkNativeTransferMembership } from "./native-source-Y0yLhFyQ.mjs";
15
+ import { a as nativeGradleTransfer } from "./native-gradle-inputs-Dnh84Zbd.mjs";
16
+ import { createHash, webcrypto } from "node:crypto";
17
+ import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, unlinkSync, writeSync } from "node:fs";
18
+ import { isDeepStrictEqual } from "node:util";
19
+ import { basename, dirname, isAbsolute, join, relative, sep } from "node:path";
20
+ import { LOG_ROTATE_BYTES, ambientContext, reactNativeArtifactIdentity, reactNativeArtifactQuery, rotatedLogPath, swiftpmArtifactQuery, withDirLock } from "@stim-cli/core";
21
+ import { MAINTENANCE_LOG_NAME, OFFLOAD_MODES, automaticMachineEnabled, isJsonObject, maintenanceDir, maintenanceNdjsonFile, requireAutomaticMachine, runId, saturation, workspaceLogsDir } from "@stim-cli/core/state";
22
+ import { WebSocket } from "ws";
23
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
24
+ import { readFile, readlink } from "node:fs/promises";
25
+ //#region src/maintenance/log.ts
26
+ function maintenanceLogger(settings, pass, trigger) {
27
+ const file = maintenanceNdjsonFile();
28
+ const rotated = rotatedLogPath(file);
29
+ withDirLock(`${file}.lock`, () => {
30
+ try {
31
+ if (Date.now() - statSync(rotated).mtimeMs > settings.logRetentionDays * 864e5) unlinkSync(rotated);
32
+ } catch {}
33
+ });
34
+ const writer = createNdjsonWriter(file, { maxBytes: settings.logMaxMb * 1024 ** 2 });
35
+ return {
36
+ write(event, level, msg, fields = {}) {
37
+ if (level === "debug" && !settings.logChecks) return null;
38
+ const record = {
39
+ ...fields,
40
+ ts: Date.now(),
41
+ src: "maintenance",
42
+ level,
43
+ msg,
44
+ event,
45
+ pass,
46
+ trigger,
47
+ mode: settings.mode === "on" ? "on" : "report"
48
+ };
49
+ if (!writer.write(record)) throw writer.lastError ?? /* @__PURE__ */ new Error(`Could not append ${file}`);
50
+ if (typeof fields.workspace === "string" && [
51
+ "maintenance_action",
52
+ "maintenance_failure",
53
+ "maintenance_skip"
54
+ ].includes(event)) {
55
+ const workspace = createNdjsonWriter(join(workspaceLogsDir(fields.workspace), MAINTENANCE_LOG_NAME), { maxBytes: LOG_ROTATE_BYTES });
56
+ try {
57
+ if (!workspace.write(record)) throw workspace.lastError ?? /* @__PURE__ */ new Error("Could not append workspace maintenance log");
58
+ } finally {
59
+ workspace.close();
60
+ }
61
+ }
62
+ return record;
63
+ },
64
+ close: () => writer.close()
65
+ };
66
+ }
67
+ //#endregion
68
+ //#region src/cache/compilation-cache-corruption.ts
69
+ const COMPILATION_CACHE_CORRUPT = "STIM_COMPILATION_CACHE_CORRUPT";
70
+ async function emptyCompilationCache(dir) {
71
+ const results = [];
72
+ const cache = discoverCaches().find((candidate) => canonicalPath(candidate.dir) === canonicalPath(dir));
73
+ if (cache?.source !== "registered" || cache.prune !== "atomic") return `${dir} is not a registered cache Stim empties whole`;
74
+ const [planned] = planCacheEmptying([cache], true);
75
+ await emptyCaches(planned ? [planned] : [], {
76
+ log: () => {},
77
+ error: () => {},
78
+ record: (kind, status, label, { id = null, bytes = null, detail = null } = {}) => {
79
+ results.push({
80
+ kind,
81
+ status,
82
+ label,
83
+ id,
84
+ bytes,
85
+ detail
86
+ });
87
+ },
88
+ fail: () => {}
89
+ });
90
+ const refused = results.find((result) => result.status !== "done");
91
+ return refused ? refused.detail ?? `could not empty ${dir}` : null;
92
+ }
93
+ function recordInMaintenanceLog(workspace, msg, fields, cleared) {
94
+ try {
95
+ mkdirSync(maintenanceDir(), { recursive: true });
96
+ const logger = maintenanceLogger(resolveMaintenanceSettings(), `build-${process.pid}`, "ios build");
97
+ try {
98
+ logger.write(cleared ? "maintenance_action" : "maintenance_failure", cleared ? "info" : "warn", msg, {
99
+ ...fields,
100
+ workspace
101
+ });
102
+ } finally {
103
+ logger.close();
104
+ }
105
+ } catch {}
106
+ }
107
+ /**
108
+ * Empties the shared compilation cache a failed build reported corrupt, through the path `gc --delete --cache` uses,
109
+ * unless another build may be using it. It never retries the build; the result is the refusal to print.
110
+ */
111
+ async function clearCorruptCompilationCache(corruption, workspace) {
112
+ const { dir, line } = corruption;
113
+ let blocked;
114
+ try {
115
+ blocked = await emptyCompilationCache(dir);
116
+ } catch (error) {
117
+ blocked = error instanceof Error ? error.message : String(error);
118
+ }
119
+ const fields = {
120
+ target: dir,
121
+ category: "compilation-cache",
122
+ reason: line,
123
+ ...blocked ? { blocked } : {}
124
+ };
125
+ if (blocked) {
126
+ const message = `The shared compilation cache at ${dir} is corrupt (the compiler reported: ${line}). Stim did not empty it: ${blocked}.`;
127
+ recordInMaintenanceLog(workspace, message, fields, false);
128
+ return {
129
+ code: COMPILATION_CACHE_CORRUPT,
130
+ message,
131
+ remedy: "Once the reason above is resolved, run `stim gc --delete --cache \"compilation cache\"`, then run the same command again."
132
+ };
133
+ }
134
+ const message = `The shared compilation cache at ${dir} was corrupt (the compiler reported: ${line}). Stim emptied it.`;
135
+ recordInMaintenanceLog(workspace, message, fields, true);
136
+ return {
137
+ code: COMPILATION_CACHE_CORRUPT,
138
+ message,
139
+ remedy: "Run the same command again. Its build starts with an empty compilation cache."
140
+ };
141
+ }
142
+ //#endregion
143
+ //#region src/remote-log.ts
144
+ const sinks = /* @__PURE__ */ new WeakMap();
145
+ /** Where this operation's failed remote requests are written for `stim logs`. */
146
+ function setRemoteLogSink(context, next) {
147
+ if (next) sinks.set(context, next);
148
+ else sinks.delete(context);
149
+ }
150
+ function reportRemoteFailure(context, event, fields) {
151
+ try {
152
+ if (context) sinks.get(context)?.({
153
+ src: "build",
154
+ level: "warn",
155
+ event,
156
+ ...fields
157
+ });
158
+ } catch {}
159
+ }
160
+ //#endregion
161
+ //#region src/offload/react-native-artifact.ts
162
+ function reactNativeBuildQuery(build, fingerprint, project, toolchain) {
163
+ if (build.native) return null;
164
+ return reactNativeArtifactQuery({
165
+ project,
166
+ fingerprint,
167
+ toolchain,
168
+ isExpo: build.isExpo === true,
169
+ platform: build.platform,
170
+ ...build.platform === "ios" ? { ios: {
171
+ runtime: build.runtime,
172
+ configuration: build.configuration ?? "Debug",
173
+ scheme: build.scheme ?? null,
174
+ projectPath: build.iosProjectPath ?? "ios",
175
+ optimizations: build.optimizations
176
+ } } : { android: build.android }
177
+ });
178
+ }
179
+ async function reactNativeArtifactToolchain(platform, root) {
180
+ if (platform === "ios") {
181
+ const tools = await iosToolchainAsync(root);
182
+ return tools.stimBuild && tools.xcode && tools.simulatorSdk && tools.cocoapods ? {
183
+ ...tools,
184
+ os: process.platform
185
+ } : null;
186
+ }
187
+ const { stimBuild, os, arch, jdk, androidSdk } = workerToolchain(null, "gradle");
188
+ return stimBuild && jdk && androidSdk ? {
189
+ stimBuild,
190
+ os,
191
+ arch,
192
+ jdk,
193
+ androidSdk
194
+ } : null;
195
+ }
196
+ function reactNativeArtifactReceipt(query, toolchain) {
197
+ if (!toolchain || !isDeepStrictEqual(reactNativeArtifactQuery({
198
+ ...query,
199
+ toolchain
200
+ })?.toolchain, query.toolchain)) return null;
201
+ const receipt = {
202
+ query,
203
+ toolchain
204
+ };
205
+ const identity = reactNativeArtifactIdentity(receipt);
206
+ return {
207
+ identity: {
208
+ hash: identity.fingerprint,
209
+ key: identity.cacheKey
210
+ },
211
+ receipt
212
+ };
213
+ }
214
+ //#endregion
215
+ //#region src/offload/client.ts
216
+ const CONNECT_TIMEOUT_MS = 1e4;
217
+ const OFFER_TIMEOUT_MS = 2e4;
218
+ const REQUEST_TIMEOUT_MS = 12e4;
219
+ const JOB_TIMEOUT_MS = 39e5;
220
+ const PING_MS = 15e3;
221
+ const SILENT_MS = 6e4;
222
+ const CLOSE_TIMEOUT_MS = 2e3;
223
+ const CLOSE_ABNORMAL = 1006;
224
+ const TURNED_AWAY = /* @__PURE__ */ new Set([
225
+ "unauthorized",
226
+ "forbidden",
227
+ "protocol-unsupported",
228
+ "server-key-mismatch"
229
+ ]);
230
+ const RESUME_WINDOW_MS = 18e4;
231
+ const RESUME_DELAY_MS = 2e3;
232
+ const RESUME_MAX_DELAY_MS = 15e3;
233
+ const PAGE_BYTES = 49152;
234
+ const CHUNK_BYTES = 61440;
235
+ const MAX_BUFFERED = 8388608;
236
+ const DIGEST_BYTES = 32;
237
+ function offloadMode(env = ambientContext()?.env ?? process.env) {
238
+ const raw = env.STIM_REMOTE_BUILD_MODE || loadConfig$1()?.remote?.buildMode;
239
+ return OFFLOAD_MODES.includes(raw) ? raw : "auto";
240
+ }
241
+ function buildPlacementCandidates(selected) {
242
+ const config = loadConfig$1();
243
+ return {
244
+ localEnabled: selected !== "auto" || automaticMachineEnabled("build", "local", config),
245
+ mode: selected === "local" ? "off" : offloadMode(),
246
+ machines: selected === "local" ? [] : namedBuildMachine(selected) ? pairedMachines([selected]).slice(0, 1) : pairedMachines().filter((entry) => automaticMachineEnabled("build", entry.machine, config))
247
+ };
248
+ }
249
+ function remotePhaseText(name, msg, machine) {
250
+ const text = msg.trim();
251
+ return `${text.startsWith(`${name} `) ? text.slice(name.length).trimStart() : text} (on ${machine})`;
252
+ }
253
+ const plural = (count, word) => `${count} ${word}${count === 1 ? "" : "s"}`;
254
+ function capacityText(capacity) {
255
+ const builds = capacity.maxBuilds > 0 ? `${capacity.builds} of ${capacity.maxBuilds} build slots busy` : plural(capacity.builds, "build");
256
+ return `load ${capacity.loadPerCore}/core, ${builds}`;
257
+ }
258
+ function placementLoad(choice) {
259
+ const { loadPerCore, builds, maxBuilds } = choice.offer.capacity;
260
+ if (typeof loadPerCore !== "number" || typeof builds !== "number") return "";
261
+ return `; ${capacityText({
262
+ loadPerCore,
263
+ builds,
264
+ maxBuilds: maxBuilds ?? 0
265
+ })} there`;
266
+ }
267
+ /**
268
+ * Whether a build may go to a remote machine at all, before any machine is asked. `auto` keeps the build here while
269
+ * this machine has a free `concurrency.maxBuilds` slot and its load per core is under `server.maxLoadPerCore`.
270
+ * A named selection ignores local capacity and mode, and refuses an unsupported build or missing pairing.
271
+ */
272
+ function offloadPlacement({ mode, machines, here, unsupported, localEnabled = true, selected = "auto" }) {
273
+ if (namedBuildMachine(selected)) {
274
+ if (unsupported) throw new OffloadRefusal(selected, `${unsupported}, so a named remote machine cannot take it`);
275
+ if (machines === 0) throw new OffloadRefusal(selected, "no remote machine is paired");
276
+ return {
277
+ offload: true,
278
+ code: "named",
279
+ reason: `selected with --remote-build ${selected}`
280
+ };
281
+ }
282
+ if (selected === "local") return {
283
+ offload: false,
284
+ code: "local-selected",
285
+ reason: "--remote-build local"
286
+ };
287
+ if (!localEnabled) {
288
+ const reason = mode === "off" ? "remote.buildMode is off" : unsupported ?? (machines === 0 ? "no enabled remote machine is paired" : null);
289
+ if (reason) throw new OffloadRefusal("auto", `${reason}; local is disabled in remote.buildPoolDisabled`);
290
+ return {
291
+ offload: true,
292
+ code: "local-disabled",
293
+ reason: "local is disabled in remote.buildPoolDisabled"
294
+ };
295
+ }
296
+ if (mode === "off") return {
297
+ offload: false,
298
+ code: "mode-off",
299
+ reason: "remote.buildMode is off"
300
+ };
301
+ if (machines === 0) return {
302
+ offload: false,
303
+ code: "no-remote-mac",
304
+ reason: "no remote machine is paired"
305
+ };
306
+ if (unsupported) return {
307
+ offload: false,
308
+ code: "unsupported",
309
+ reason: unsupported
310
+ };
311
+ if (mode === "force") return {
312
+ offload: true,
313
+ code: "forced",
314
+ reason: "remote.buildMode is force"
315
+ };
316
+ const busy = saturation(here);
317
+ return busy ? {
318
+ offload: true,
319
+ code: "this-mac-busy",
320
+ reason: `this machine is busy: ${busy} (${capacityText(here)})`
321
+ } : {
322
+ offload: false,
323
+ code: "this-mac-free",
324
+ reason: `${capacityText(here)} here`
325
+ };
326
+ }
327
+ function matchingArtifact(offer, identity) {
328
+ const cached = offer.artifact;
329
+ if (!identity || !cached || typeof cached.name !== "string" || basename(cached.name) !== cached.name || !Number.isSafeInteger(cached.size) || cached.size <= 0 || typeof cached.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(cached.sha256)) return null;
330
+ if (identity === "react-native-ios" || identity === "react-native-android") {
331
+ const platform = identity === "react-native-ios" ? "ios" : "android";
332
+ return ARTIFACT_NAME[platform].test(cached.name) && /^[a-f0-9]{64}$/.test(cached.fingerprint) && cached.cacheKey === `react-native-${platform}-${cached.fingerprint}` && (platform === "ios" || ANDROID_PACKAGE.test(String(cached.androidPackage ?? ""))) ? cached : null;
333
+ }
334
+ if (identity === "swiftpm") return cached.name.endsWith(".app") && typeof cached.fingerprint === "string" && /^[a-f0-9]{64}$/.test(cached.fingerprint) && cached.cacheKey === `swiftpm-peer-${cached.fingerprint}` ? cached : null;
335
+ if (identity === "native-gradle") return cached.name.endsWith(".apk") && typeof cached.fingerprint === "string" && /^[a-f0-9]{64}$/.test(cached.fingerprint) && typeof cached.cacheKey === "string" && cached.cacheKey.length > 0 && cached.cacheKey.length <= 1024 && typeof cached.androidPackage === "string" && ANDROID_PACKAGE.test(cached.androidPackage) ? cached : null;
336
+ return cached.name.endsWith(".app") && cached.cacheKey === identity.cacheKey && cached.fingerprint === identity.fingerprint ? cached : null;
337
+ }
338
+ const gb = (bytes) => (bytes / 1024 ** 3).toFixed(1);
339
+ /** Every reason a machine's offer cannot take this build, in either mode; empty when it can. */
340
+ function offerProblems(offer, target) {
341
+ const problems = toolchainMismatches(target, offer.toolchain);
342
+ const capacity = offer.capacity;
343
+ const { running, max, diskFreeBytes, minDiskFreeBytes, declined } = capacity;
344
+ if (diskFreeBytes !== null && diskFreeBytes < minDiskFreeBytes) problems.push({
345
+ code: "disk",
346
+ reason: `${gb(diskFreeBytes)} GB free, needs ${gb(minDiskFreeBytes)} GB`
347
+ });
348
+ else if (declined || declined === void 0 && running >= max) {
349
+ const why = declined ?? `already running ${running} offloaded build(s), its limit`;
350
+ const load = typeof capacity.loadPerCore === "number" && typeof capacity.builds === "number" ? `; ${capacityText({
351
+ loadPerCore: capacity.loadPerCore,
352
+ builds: capacity.builds,
353
+ maxBuilds: capacity.maxBuilds ?? 0
354
+ })}` : "";
355
+ problems.push({
356
+ code: "busy",
357
+ reason: `busy (${why}${load})`
358
+ });
359
+ }
360
+ return problems;
361
+ }
362
+ /**
363
+ * The asked machines that can take the build, best first, and one reason per machine that cannot. A machine with any
364
+ * `offerProblems` never takes it. In `auto`, a machine must also be expected to build faster than this machine: while
365
+ * every local build slot is busy any machine that accepts will do, otherwise its load per core must be lower than
366
+ * this machine's. A named selection considers only that machine and skips this local-capacity gating.
367
+ * A machine that reports no load (a stim-server older than capacity) takes an `auto` build only while
368
+ * every local slot is busy. Matching dependency installations rank first, then load.
369
+ */
370
+ function pickOffer({ mode, here, offers, target, localEnabled = true, selected = "auto", artifact, cacheOnly = false }) {
371
+ const slotsFull = here.maxBuilds > 0 && here.builds >= here.maxBuilds;
372
+ const reasons = [];
373
+ const candidates = [];
374
+ const skip = (machine, code, msg, detail) => {
375
+ reasons.push(`${machine}: ${msg}`);
376
+ candidates.push({
377
+ machine,
378
+ code,
379
+ msg,
380
+ ...detail ? { detail } : {}
381
+ });
382
+ };
383
+ const ranked = [];
384
+ offers.forEach(({ machine, offer, failure }, index) => {
385
+ if (namedBuildMachine(selected) && machine !== selected) return;
386
+ if (!offer) return skip(machine, "unreachable", failure ?? "no offer");
387
+ if (matchingArtifact(offer, artifact) && (artifact !== "swiftpm" || toolchainMismatches(target, offer.toolchain).length === 0)) {
388
+ ranked.push({
389
+ index,
390
+ score: Number.POSITIVE_INFINITY,
391
+ load: 0
392
+ });
393
+ candidates.push({
394
+ machine,
395
+ code: "artifact-hit",
396
+ msg: "has the exact completed artifact"
397
+ });
398
+ return;
399
+ }
400
+ if (cacheOnly) return skip(machine, "artifact-miss", "no matching completed artifact");
401
+ const problems = offerProblems(offer, target);
402
+ if (problems.length) return skip(machine, problemCode(problems[0]), problems.map((problem) => problem.reason).join("; "), problems.map((problem) => problem.code));
403
+ const load = offer.capacity.loadPerCore;
404
+ if (selected === "auto" && localEnabled && mode === "auto" && !slotsFull) {
405
+ if (typeof load !== "number") return skip(machine, "load", "capacity unknown (older stim-server) while this machine has a free slot");
406
+ if (load >= here.loadPerCore) return skip(machine, "load", `no less loaded (load ${load}/core there, ${here.loadPerCore}/core here)`);
407
+ }
408
+ const score = Number(offer.warm.dependencies);
409
+ ranked.push({
410
+ index,
411
+ score,
412
+ load: typeof load === "number" ? load : Number.POSITIVE_INFINITY
413
+ });
414
+ candidates.push({
415
+ machine,
416
+ code: "accepted",
417
+ msg: typeof load === "number" ? `can take the build (load ${load}/core there)` : "can take the build"
418
+ });
419
+ });
420
+ ranked.sort((a, b) => b.score - a.score || a.load - b.load);
421
+ return {
422
+ order: ranked.map((each) => each.index),
423
+ reasons,
424
+ candidates
425
+ };
426
+ }
427
+ function problemCode({ code }) {
428
+ return code === "disk" || code === "busy" || code === "unreachable" ? code : code === "runtime" ? "no-matching-device" : "version-mismatch";
429
+ }
430
+ function closeOnExit(socket, context) {
431
+ if (!context) return;
432
+ const state = operationState(context);
433
+ if (!state.takesSignals) return;
434
+ const close = () => socket.close(1e3);
435
+ const unregister = () => state.exitCleanups.delete(close);
436
+ state.exitCleanups.add(close);
437
+ socket.once("close", unregister);
438
+ return unregister;
439
+ }
440
+ /**
441
+ * One authenticated connection to another machine's stim-server, for build offload or device hosting. Closing it cancels
442
+ * its build jobs there; a connection that drops, or that stays silent for `SILENT_MS` after `watch()`, leaves them
443
+ * running for a while so a new connection can `build.attach` to them.
444
+ */
445
+ var BuildConnection = class BuildConnection {
446
+ nextId = 2;
447
+ pending = /* @__PURE__ */ new Map();
448
+ writeWaiters = /* @__PURE__ */ new Set();
449
+ progress = null;
450
+ binary = null;
451
+ closed = null;
452
+ heard = Date.now();
453
+ watched = false;
454
+ ended = false;
455
+ keepalive;
456
+ socket;
457
+ features;
458
+ context;
459
+ unregisterExitCleanup;
460
+ constructor(socket, features, context) {
461
+ this.socket = socket;
462
+ this.features = features;
463
+ this.context = context;
464
+ this.unregisterExitCleanup = closeOnExit(socket, context);
465
+ this.keepalive = setInterval(() => {
466
+ if (this.watched && Date.now() - this.heard > SILENT_MS) return void socket.terminate();
467
+ socket.ping();
468
+ }, PING_MS);
469
+ this.keepalive.unref();
470
+ socket.on("pong", () => this.heard = Date.now());
471
+ socket.on("message", (data, isBinary) => {
472
+ this.heard = Date.now();
473
+ const frame = Buffer.isBuffer(data) ? data : Buffer.concat(Array.isArray(data) ? data : [Buffer.from(data)]);
474
+ if (isBinary) return this.binary?.(frame);
475
+ let message;
476
+ try {
477
+ message = JSON.parse(frame.toString("utf8"));
478
+ } catch {
479
+ return;
480
+ }
481
+ if (!isJsonObject(message)) return;
482
+ if (message.event === "build.progress") return this.progress?.(message);
483
+ const resolve = typeof message.id === "number" ? this.pending.get(message.id) : void 0;
484
+ if (resolve) {
485
+ this.pending.delete(message.id);
486
+ resolve(message);
487
+ }
488
+ });
489
+ socket.on("close", (code, reason) => {
490
+ clearInterval(this.keepalive);
491
+ this.closed = `the connection closed (${code}${reason.length ? ` ${reason.toString()}` : ""})`;
492
+ for (const wake of this.writeWaiters) wake();
493
+ for (const resolve of this.pending.values()) resolve({ error: {
494
+ code: "closed",
495
+ message: this.closed
496
+ } });
497
+ this.pending.clear();
498
+ const dropped = code === CLOSE_ABNORMAL;
499
+ this.progress?.({
500
+ job: "",
501
+ outcome: {
502
+ ok: false,
503
+ code: dropped ? "dropped" : "closed",
504
+ message: this.closed
505
+ }
506
+ });
507
+ });
508
+ socket.on("error", () => {});
509
+ }
510
+ /** Resolves why it could not connect; `refused` when the machine answered and turned this machine away. */
511
+ static async open(target, serverKey, timeoutMs, capability = "build", signal) {
512
+ const context = ambientContext();
513
+ const started = performance.now();
514
+ const result = await BuildConnection.connect(target, serverKey, timeoutMs, capability, signal, context);
515
+ const ms = Math.round(performance.now() - started);
516
+ debugLog.log("remote_connect", {
517
+ host: target.host,
518
+ capability,
519
+ ms,
520
+ timeoutMs,
521
+ ...result instanceof BuildConnection ? { ok: true } : {
522
+ ok: false,
523
+ failure: result.failure,
524
+ code: result.code
525
+ }
526
+ });
527
+ if (!(result instanceof BuildConnection)) reportRemoteFailure(context, "remote_connect_failed", {
528
+ host: target.host,
529
+ capability,
530
+ ms,
531
+ timeoutMs,
532
+ msg: result.failure,
533
+ code: result.code
534
+ });
535
+ return result;
536
+ }
537
+ static async connect(target, serverKey, timeoutMs, capability, signal, context) {
538
+ const started = Date.now();
539
+ const socket = await openAuthenticated(target, {
540
+ access: capability,
541
+ serverKey
542
+ }, timeoutMs, signal, context);
543
+ if (!("send" in socket)) return socket;
544
+ const reply = await sayHello(socket, {
545
+ client: {
546
+ name: "stim",
547
+ version: "1",
548
+ runId: runId()
549
+ },
550
+ access: capability
551
+ }, Math.max(1, timeoutMs - (Date.now() - started)));
552
+ if ("error" in reply) {
553
+ socket.terminate();
554
+ return {
555
+ failure: reply.error.message,
556
+ refused: TURNED_AWAY.has(reply.error.code),
557
+ code: reply.error.code
558
+ };
559
+ }
560
+ const capabilities = reply.result.capabilities;
561
+ if (!Array.isArray(capabilities) || !capabilities.includes(capability)) {
562
+ socket.terminate();
563
+ return {
564
+ failure: `it has not granted this machine ${capability === "build" ? "build" : "hosting"} access`,
565
+ refused: true,
566
+ code: "forbidden"
567
+ };
568
+ }
569
+ const features = reply.result.features;
570
+ return new BuildConnection(socket, Array.isArray(features) ? features.filter((each) => typeof each === "string") : [], context);
571
+ }
572
+ supports(feature) {
573
+ return this.features.includes(feature);
574
+ }
575
+ request(method, params, timeoutMs = REQUEST_TIMEOUT_MS) {
576
+ if (this.closed) return Promise.resolve({ error: {
577
+ code: "closed",
578
+ message: this.closed
579
+ } });
580
+ const id = this.nextId++;
581
+ const started = performance.now();
582
+ const done = (reply) => {
583
+ const ms = Math.round(performance.now() - started);
584
+ debugLog.log("remote_request", {
585
+ method,
586
+ ms,
587
+ ..."error" in reply ? {
588
+ ok: false,
589
+ code: reply.error.code
590
+ } : { ok: true }
591
+ });
592
+ if ("error" in reply) reportRemoteFailure(this.context, "remote_request_failed", {
593
+ method,
594
+ ms,
595
+ code: reply.error.code,
596
+ msg: reply.error.message
597
+ });
598
+ return reply;
599
+ };
600
+ return new Promise((resolve) => {
601
+ const timer = setTimeout(() => {
602
+ this.pending.delete(id);
603
+ resolve(done({ error: {
604
+ code: "timeout",
605
+ message: `${method} got no reply in ${timeoutMs / 1e3} s`
606
+ } }));
607
+ }, timeoutMs);
608
+ this.pending.set(id, (reply) => {
609
+ clearTimeout(timer);
610
+ resolve(done(reply));
611
+ });
612
+ this.socket.send(JSON.stringify({
613
+ id,
614
+ method,
615
+ params
616
+ }));
617
+ });
618
+ }
619
+ async sendBinary(frame) {
620
+ if (this.closed) return;
621
+ let completed = false;
622
+ let resume;
623
+ this.socket.send(frame, { binary: true }, () => {
624
+ completed = true;
625
+ resume?.();
626
+ });
627
+ if (completed || this.closed || this.socket.bufferedAmount <= MAX_BUFFERED) return;
628
+ await new Promise((resolve) => {
629
+ const wake = () => {
630
+ this.writeWaiters.delete(wake);
631
+ resolve();
632
+ };
633
+ resume = wake;
634
+ this.writeWaiters.add(wake);
635
+ });
636
+ }
637
+ onProgress(listener) {
638
+ this.progress = listener;
639
+ }
640
+ onBinary(listener) {
641
+ this.binary = listener;
642
+ }
643
+ get failure() {
644
+ return this.closed;
645
+ }
646
+ /**
647
+ * Treats `SILENT_MS` without a frame as a dropped connection from now on. Only while waiting on a job: a long
648
+ * upload or this process hashing the checkout can delay pongs without anything being wrong.
649
+ */
650
+ watch() {
651
+ this.heard = Date.now();
652
+ this.watched = true;
653
+ }
654
+ /** Closes with a close frame, which tells the machine to cancel this connection's jobs. */
655
+ close() {
656
+ if (this.ended) return;
657
+ this.forget();
658
+ if (this.socket.readyState === WebSocket.CLOSED) return;
659
+ const timer = setTimeout(() => this.socket.terminate(), CLOSE_TIMEOUT_MS);
660
+ this.socket.once("close", () => clearTimeout(timer));
661
+ this.socket.close(1e3);
662
+ }
663
+ /** Ends the connection without a close frame, which leaves its jobs on the machine for a new connection. */
664
+ drop() {
665
+ if (this.ended) return;
666
+ this.forget();
667
+ this.socket.terminate();
668
+ }
669
+ forget() {
670
+ this.ended = true;
671
+ this.closed ??= "the connection was closed by this client";
672
+ for (const wake of this.writeWaiters) wake();
673
+ for (const resolve of this.pending.values()) resolve({ error: {
674
+ code: "closed",
675
+ message: this.closed
676
+ } });
677
+ this.pending.clear();
678
+ this.progress = null;
679
+ this.binary = null;
680
+ clearInterval(this.keepalive);
681
+ this.socket.removeAllListeners("close");
682
+ this.unregisterExitCleanup?.();
683
+ }
684
+ };
685
+ function replyError(reply) {
686
+ return "error" in reply ? `${reply.error.code}: ${reply.error.message}` : null;
687
+ }
688
+ /** The repository on the remote machine: one area per git common dir, shared by all its worktrees. */
689
+ function repoIdentity(projectRoot) {
690
+ const repoRoot = realpathSync(git(projectRoot, ["rev-parse", "--show-toplevel"]));
691
+ return repositoryIdentity(projectRoot, repoRoot, realpathSync(git(repoRoot, [
692
+ "rev-parse",
693
+ "--path-format=absolute",
694
+ "--git-common-dir"
695
+ ])));
696
+ }
697
+ function repositoryIdentity(projectRoot, repoRoot, common) {
698
+ const digest = createHash("sha256").update(common).digest("hex").slice(0, 12);
699
+ const name = (basename(dirname(common)) || "repo").replace(/[^A-Za-z0-9._-]/g, "_").replace(/^[^A-Za-z0-9]+/, "");
700
+ const lock = [
701
+ "pnpm-lock.yaml",
702
+ "yarn.lock",
703
+ "package-lock.json"
704
+ ].find((file) => existsSync(join(repoRoot, file)));
705
+ return {
706
+ repoRoot,
707
+ project: relative(repoRoot, realpathSync(projectRoot)),
708
+ repo: `${name.slice(0, 60) || "repo"}-${digest}`,
709
+ lockfile: lock ? createHash("sha256").update(readFileSync(join(repoRoot, lock))).digest("hex") : null
710
+ };
711
+ }
712
+ function dependencySelection(identity, target) {
713
+ if (!identity.lockfile || target.platform === "macos" || target.native) return void 0;
714
+ return {
715
+ project: identity.project.split(sep).join("/"),
716
+ packageName: packageName(join(identity.repoRoot, identity.project))
717
+ };
718
+ }
719
+ /** Why the native inputs cannot be sent to a remote machine, such as an ignored file among them, or null when they can. */
720
+ async function nativeTransferRefusal(repoRoot, snapshot, signal) {
721
+ try {
722
+ checkNativeTransferMembership(snapshot, await gitVisiblePaths(repoRoot, signal));
723
+ return null;
724
+ } catch (error) {
725
+ signal?.throwIfAborted();
726
+ return error.message.split("\n")[0];
727
+ }
728
+ }
729
+ async function blobContent(repoRoot, file, signal) {
730
+ const absolute = join(repoRoot, file.path);
731
+ return file.kind === "directory" ? Buffer.alloc(0) : file.kind === "link" ? Buffer.from(await readlink(absolute)) : await readFile(absolute, { signal });
732
+ }
733
+ /** Closes every connection the choice still holds. */
734
+ function closeOffload(choice) {
735
+ choice.connection.close();
736
+ for (const each of choice.runnersUp.splice(0)) each.connection.close();
737
+ }
738
+ const NATIVE_BUILD_FEATURE = {
739
+ xcode: "native-xcode-build",
740
+ gradle: "native-gradle-build"
741
+ };
742
+ const unsupportedNative = (provider) => `This worker does not support native ${provider === "xcode" ? "Xcode" : "Gradle"} builds.`;
743
+ const ANDROID_PACKAGE = /^[A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)+$/;
744
+ function nativeTransferInput(native) {
745
+ return native?.provider === "xcode" ? native.snapshot : native?.transfer;
746
+ }
747
+ /** Connects to one paired machine and asks it for an offer; the caller closes the connection it returns. */
748
+ async function probeMachine(credential, identity, { connectMs = CONNECT_TIMEOUT_MS, offerMs = OFFER_TIMEOUT_MS } = {}) {
749
+ const target = await resolveEndpoint(credential, void 0, reportSignal(ambientContext()));
750
+ if (typeof target === "string") return {
751
+ credential,
752
+ failure: target
753
+ };
754
+ const connection = await BuildConnection.open(target, credential.serverKey, connectMs, "build", reportSignal(ambientContext()));
755
+ if (!(connection instanceof BuildConnection)) return {
756
+ credential,
757
+ failure: connection.failure
758
+ };
759
+ if (identity.native && !connection.supports(NATIVE_BUILD_FEATURE[identity.native])) {
760
+ connection.close();
761
+ return {
762
+ credential,
763
+ failure: unsupportedNative(identity.native)
764
+ };
765
+ }
766
+ const native = identity.native === "gradle" || identity.native && connection.supports("native-xcode-toolchain") ? identity.native : void 0;
767
+ const reply = await connection.request("build.offer", {
768
+ repo: identity.repo,
769
+ ...identity.lockfile ? { lockfile: identity.lockfile } : {},
770
+ ...identity.dependencies && connection.supports("build-dependency-cache") ? { dependencies: identity.dependencies } : {},
771
+ ...native ? { native } : identity.rubyVersion ? { rubyVersion: identity.rubyVersion } : {},
772
+ ...native === "xcode" && identity.artifact && connection.supports("build-artifact-cache") ? { artifact: identity.artifact } : {},
773
+ ...identity.swiftpmArtifact && connection.supports("swiftpm-peer-cache") ? { swiftpmArtifact: identity.swiftpmArtifact } : {},
774
+ ...identity.reactNativeArtifact && connection.supports("react-native-peer-cache") ? { reactNativeArtifact: identity.reactNativeArtifact } : {},
775
+ ...native === "gradle" && identity.gradleArtifact && connection.supports("native-gradle-peer-cache") ? { gradleArtifact: identity.gradleArtifact } : {}
776
+ }, offerMs);
777
+ const failure = replyError(reply);
778
+ if (failure || !("result" in reply)) {
779
+ connection.close();
780
+ return {
781
+ credential,
782
+ failure: failure ?? "no offer"
783
+ };
784
+ }
785
+ const reported = reply.result;
786
+ const offer = {
787
+ ...reported,
788
+ warm: {
789
+ ...reported.warm,
790
+ dependencies: identity.dependencies !== void 0 && connection.supports("build-dependency-cache") && reported.warm.dependencies
791
+ }
792
+ };
793
+ return {
794
+ credential,
795
+ connection,
796
+ offer: identity.swiftpmArtifact && !connection.supports("swiftpm-peer-cache") || identity.reactNativeArtifact && !connection.supports("react-native-peer-cache") || native === "gradle" && (!identity.gradleArtifact || !connection.supports("native-gradle-peer-cache")) ? {
797
+ ...offer,
798
+ artifact: null
799
+ } : offer
800
+ };
801
+ }
802
+ /**
803
+ * Asks every paired machine for an offer in parallel and keeps the connection to the one `pickOffer` chooses, or
804
+ * returns why none takes the build. A named selection probes only its credential and skips auto load gating.
805
+ */
806
+ async function chooseBuildMachine({ projectRoot, target, mode, here, note, machines = pairedMachines(), selected = "auto", onCandidates, artifact, gradleArtifact, reactNativeArtifact, swiftpmArtifact, cacheOnly = false }) {
807
+ const started = Date.now();
808
+ let identity;
809
+ try {
810
+ identity = repoIdentity(projectRoot);
811
+ } catch (error) {
812
+ return `this app is not in a git checkout (${error.message.split("\n")[0]})`;
813
+ }
814
+ if (namedBuildMachine(selected)) machines = machines.filter((each) => each.machine === selected).slice(0, 1);
815
+ else machines = machines.filter((each) => automaticMachineEnabled("build", each.machine));
816
+ const rubyVersion = readRubyVersion(projectRoot) ?? void 0;
817
+ const gradleQuery = gradleArtifact ? {
818
+ ...gradleArtifact,
819
+ project: identity.project
820
+ } : void 0;
821
+ const reactNativeQuery = reactNativeArtifact ? reactNativeBuildQuery(reactNativeArtifact.request, reactNativeArtifact.fingerprint, identity.project, target.local) : null;
822
+ const swiftpmQuery = swiftpmArtifact?.artifactInputs ? swiftpmArtifactQuery({
823
+ project: identity.project,
824
+ sourceDigest: manifestDigest(await sourceManifest(identity.repoRoot, true, runCancellationSignal(ambientContext()) ?? ambientContext()?.signal)),
825
+ stimBuild: target.local.stimBuild,
826
+ inputs: swiftpmArtifact.artifactInputs,
827
+ build: {
828
+ product: swiftpmArtifact.product,
829
+ infoPlist: swiftpmArtifact.infoPlist,
830
+ resources: swiftpmArtifact.resources ?? {},
831
+ assetCatalog: swiftpmArtifact.assetCatalog ?? null
832
+ }
833
+ }) : null;
834
+ const asked = await Promise.all(machines.map((credential) => probeMachine(credential, {
835
+ ...identity,
836
+ rubyVersion,
837
+ dependencies: dependencySelection(identity, target),
838
+ ...target.platform !== "macos" ? { native: target.native } : {},
839
+ ...artifact ? { artifact } : {},
840
+ ...gradleQuery ? { gradleArtifact: gradleQuery } : {},
841
+ ...reactNativeQuery ? { reactNativeArtifact: reactNativeQuery } : {},
842
+ ...swiftpmQuery ? { swiftpmArtifact: swiftpmQuery } : {}
843
+ })));
844
+ const { order, reasons, candidates } = pickOffer({
845
+ localEnabled: selected !== "auto" || automaticMachineEnabled("build", "local"),
846
+ selected,
847
+ mode,
848
+ here,
849
+ target,
850
+ artifact: swiftpmQuery ? "swiftpm" : reactNativeQuery ? `react-native-${reactNativeQuery.platform}` : gradleQuery ? "native-gradle" : artifact,
851
+ cacheOnly,
852
+ offers: asked.map((each) => ({
853
+ machine: each.credential.machine,
854
+ offer: "offer" in each ? each.offer : null,
855
+ ..."failure" in each ? { failure: each.failure } : {}
856
+ }))
857
+ });
858
+ asked.forEach((each, at) => {
859
+ if (!order.includes(at) && "connection" in each) each.connection.close();
860
+ });
861
+ onCandidates?.(candidates);
862
+ if (order.length === 0) return reasons.length ? reasons.join("; ") : "no remote machine is paired";
863
+ for (const reason of reasons) note(reason);
864
+ const [first, ...rest] = order.map((at) => {
865
+ const pick = asked[at];
866
+ return {
867
+ machine: pick.credential.machine,
868
+ credential: pick.credential,
869
+ connection: pick.connection,
870
+ offer: pick.offer
871
+ };
872
+ });
873
+ return {
874
+ ...first,
875
+ mode,
876
+ here,
877
+ target,
878
+ offerMs: Date.now() - started,
879
+ identity,
880
+ runnersUp: rest,
881
+ automatic: selected === "auto",
882
+ cacheOnly,
883
+ ...gradleQuery ? { gradleArtifact: gradleQuery } : {},
884
+ ...reactNativeQuery ? { reactNativeArtifact: reactNativeQuery } : {},
885
+ ...swiftpmQuery ? { swiftpmArtifact: swiftpmQuery } : {}
886
+ };
887
+ }
888
+ function macosBuildParameters(request, peerCache) {
889
+ return {
890
+ ...request.cacheArtifact === true && peerCache ? { cacheArtifact: true } : {},
891
+ macos: {
892
+ ...request.artifactInputs && peerCache ? { artifactInputs: request.artifactInputs } : {},
893
+ product: request.product,
894
+ infoPlist: request.infoPlist,
895
+ bundleId: request.bundleId,
896
+ ...request.resources !== void 0 ? { resources: request.resources } : {},
897
+ ...request.assetCatalog !== void 0 ? { assetCatalog: request.assetCatalog } : {}
898
+ }
899
+ };
900
+ }
901
+ function validArtifactIdentity(request, fingerprint) {
902
+ if (request.platform === "macos") return true;
903
+ return request.native?.provider === "gradle" ? fingerprint === null : typeof fingerprint === "string" && fingerprint.length > 0;
904
+ }
905
+ const ARTIFACT_NAME = {
906
+ ios: /^[^/]+\.app$/,
907
+ android: /^[^/]+\.apk$/,
908
+ macos: /^[^/]+\.app$/
909
+ };
910
+ async function peerArtifact(choice, request, fingerprint) {
911
+ const swiftpm = choice.swiftpmArtifact;
912
+ if (swiftpm) {
913
+ if (request.platform !== "macos" || !isDeepStrictEqual(swiftpm, swiftpmArtifactQuery({
914
+ ...swiftpm,
915
+ inputs: request.artifactInputs,
916
+ build: {
917
+ product: request.product,
918
+ infoPlist: request.infoPlist,
919
+ resources: request.resources ?? {},
920
+ assetCatalog: request.assetCatalog ?? null
921
+ }
922
+ }))) throw new Error("The SwiftPM artifact query does not match the build request.");
923
+ if (!choice.connection.supports("swiftpm-peer-cache") || manifestDigest(await sourceManifest(choice.identity.repoRoot, true, runCancellationSignal(ambientContext()) ?? ambientContext()?.signal)) !== swiftpm.sourceDigest) return null;
924
+ return matchingArtifact(choice.offer, "swiftpm");
925
+ }
926
+ const reactNative = choice.reactNativeArtifact;
927
+ if (reactNative) {
928
+ if (typeof fingerprint !== "string" || !isDeepStrictEqual(reactNative, reactNativeBuildQuery(request, fingerprint, choice.identity.project, choice.target.local))) throw new Error("The React Native artifact query does not match the build request.");
929
+ return choice.connection.supports("react-native-peer-cache") ? matchingArtifact(choice.offer, `react-native-${reactNative.platform}`) : null;
930
+ }
931
+ const query = choice.gradleArtifact;
932
+ if (query) {
933
+ if (request.platform !== "android" || request.native?.provider !== "gradle" || !request.native.cache?.read || !request.native.cache.remote || query.project !== choice.identity.project || query.sourceDigest !== request.native.transfer.digest || !isDeepStrictEqual(query.inputs, request.native.transfer.declaration) || !isDeepStrictEqual(query.android, request.android)) throw new Error("The Gradle artifact query does not match the build request.");
934
+ return choice.connection.supports("native-gradle-peer-cache") ? matchingArtifact(choice.offer, "native-gradle") : null;
935
+ }
936
+ if (request.platform === "ios" && request.native && typeof fingerprint === "string" && choice.connection.supports("build-artifact-cache")) return matchingArtifact(choice.offer, {
937
+ fingerprint,
938
+ cacheKey: request.native.cacheKey
939
+ });
940
+ return null;
941
+ }
942
+ /**
943
+ * Reconnects to the machine that runs `job` and takes the job over with `build.attach`, retrying until
944
+ * `RESUME_WINDOW_MS`, shorter than the machine's grace for a dropped connection, has passed. Returns why it
945
+ * could not.
946
+ */
947
+ async function resumeJob(credential, job, abandoned, signal, native) {
948
+ const deadline = Date.now() + RESUME_WINDOW_MS;
949
+ let delay = RESUME_DELAY_MS;
950
+ let last = "no attempt";
951
+ while (!abandoned()) {
952
+ const target = await resolveEndpoint(credential);
953
+ const connection = typeof target === "string" ? {
954
+ failure: target,
955
+ refused: false
956
+ } : await BuildConnection.open(target, credential.serverKey, CONNECT_TIMEOUT_MS);
957
+ if (!(connection instanceof BuildConnection)) {
958
+ if (connection.refused) return `it turned this machine away (${connection.failure})`;
959
+ last = connection.failure;
960
+ } else {
961
+ if (abandoned()) {
962
+ connection.close();
963
+ break;
964
+ }
965
+ if (native && !connection.supports(NATIVE_BUILD_FEATURE[native])) {
966
+ connection.close();
967
+ return unsupportedNative(native);
968
+ }
969
+ const early = [];
970
+ connection.onProgress((event) => early.push(event));
971
+ const cancel = () => connection.close();
972
+ signal?.addEventListener("abort", cancel, { once: true });
973
+ const reply = await connection.request("build.attach", { job }, OFFER_TIMEOUT_MS);
974
+ signal?.removeEventListener("abort", cancel);
975
+ if (abandoned()) {
976
+ connection.close();
977
+ break;
978
+ }
979
+ if ("result" in reply) {
980
+ const outcome = isJsonObject(reply.result) ? reply.result.outcome : null;
981
+ return {
982
+ connection,
983
+ outcome: isJsonObject(outcome) ? outcome : null,
984
+ early
985
+ };
986
+ }
987
+ last = replyError(reply);
988
+ if (reply.error.code !== "closed" && reply.error.code !== "timeout") {
989
+ connection.close();
990
+ return `the machine did not hand the build back (${last})`;
991
+ }
992
+ connection.drop();
993
+ }
994
+ if (abandoned() || Date.now() + delay > deadline) break;
995
+ await setTimeout$1(delay, void 0, { signal }).catch(() => {});
996
+ delay = Math.min(delay * 2, RESUME_MAX_DELAY_MS);
997
+ }
998
+ return `no connection to it within ${RESUME_WINDOW_MS / 6e4} min (${last})`;
999
+ }
1000
+ /** Mirrors the checkout on the machine: the manifest in pages, then every blob it lacks. */
1001
+ async function syncSource(connection, identity, onEnter, native, signal) {
1002
+ onEnter("sync");
1003
+ const syncStarted = Date.now();
1004
+ const manifest = native && "declaration" in native ? (await nativeGradleTransfer(join(identity.repoRoot, identity.project), native.declaration, signal)).files : native ? await nativeTransferManifest(identity.repoRoot, native, await sourceManifest(identity.repoRoot, false, signal), signal) : await sourceManifest(identity.repoRoot, false, signal);
1005
+ if (native && "declaration" in native && manifestDigest(manifest) !== native.digest) return { failure: "The native Gradle source changed before upload." };
1006
+ const bySha = new Map(manifest.map((file) => [file.sha256, file]));
1007
+ const missing = [];
1008
+ for (let index = 0; index < manifest.length || index === 0;) {
1009
+ const page = [];
1010
+ let bytes = 0;
1011
+ while (index < manifest.length && (page.length === 0 || bytes < PAGE_BYTES)) {
1012
+ const file = manifest[index++];
1013
+ bytes += Buffer.byteLength(JSON.stringify(file)) + 1;
1014
+ page.push(file);
1015
+ }
1016
+ const done = index >= manifest.length;
1017
+ const reply = await connection.request("build.sync", {
1018
+ repo: identity.repo,
1019
+ files: page,
1020
+ done
1021
+ });
1022
+ const failure = replyError(reply);
1023
+ if (failure || !("result" in reply)) return { failure: `sync: ${failure ?? "no reply"}` };
1024
+ missing.push(...reply.result.missing ?? []);
1025
+ if (done) break;
1026
+ }
1027
+ let uploadedBytes = 0;
1028
+ for (const digest of missing) {
1029
+ const file = bySha.get(digest);
1030
+ if (!file) return { failure: `sync: the machine asked for an unknown blob ${digest}` };
1031
+ const content = await blobContent(identity.repoRoot, file, signal);
1032
+ if (Buffer.from(await webcrypto.subtle.digest("SHA-256", content)).toString("hex") !== digest) return { failure: `sync: ${file.path} changed while syncing` };
1033
+ const header = Buffer.from(digest, "hex");
1034
+ for (let offset = 0; offset < content.length || offset === 0; offset += CHUNK_BYTES) {
1035
+ await connection.sendBinary(Buffer.concat([header, content.subarray(offset, offset + CHUNK_BYTES)]));
1036
+ if (content.length === 0) break;
1037
+ }
1038
+ uploadedBytes += content.length;
1039
+ if (connection.failure) return { failure: `sync: ${connection.failure}` };
1040
+ }
1041
+ return {
1042
+ files: manifest.length,
1043
+ uploaded: missing.length,
1044
+ uploadedBytes,
1045
+ syncMs: Date.now() - syncStarted,
1046
+ digest: manifestDigest(manifest)
1047
+ };
1048
+ }
1049
+ function stagedArtifactEscapes(stagingDir, artifactPath, hasContents) {
1050
+ if ((hasContents ? [artifactPath, join(artifactPath, "Contents")] : [artifactPath]).some((path) => lstatSync(path, { throwIfNoEntry: false })?.isSymbolicLink())) return true;
1051
+ if (!existsSync(artifactPath)) return false;
1052
+ const inside = relative(realpathSync(stagingDir), realpathSync(artifactPath));
1053
+ return inside === ".." || inside.startsWith(`..${sep}`) || isAbsolute(inside);
1054
+ }
1055
+ async function fetchArtifact({ connection, method, params, artifact, request, stagingDir, machine, cacheHit, checkCancelled }) {
1056
+ const name = artifact.name;
1057
+ const archive = join(stagingDir, "app.tgz");
1058
+ const fd = openSync(archive, "w");
1059
+ const hash = createHash("sha256");
1060
+ let received = 0;
1061
+ let badFrame = false;
1062
+ connection.onBinary((frame) => {
1063
+ if (frame.length < DIGEST_BYTES || frame.subarray(0, DIGEST_BYTES).toString("hex") !== artifact.sha256) {
1064
+ badFrame = true;
1065
+ return;
1066
+ }
1067
+ const data = frame.subarray(DIGEST_BYTES);
1068
+ if (cacheHit && received + data.length > Number(artifact.size)) {
1069
+ badFrame = true;
1070
+ return;
1071
+ }
1072
+ try {
1073
+ writeSync(fd, data);
1074
+ } catch {
1075
+ badFrame = true;
1076
+ return;
1077
+ }
1078
+ hash.update(data);
1079
+ received += data.length;
1080
+ });
1081
+ const fetched = await connection.request(method, params, 9e5);
1082
+ connection.onBinary(null);
1083
+ closeSync(fd);
1084
+ checkCancelled();
1085
+ const fetchFailure = replyError(fetched);
1086
+ if (fetchFailure || !("result" in fetched) || fetched.result === null) return { failure: `fetch: ${fetchFailure ?? "the cached artifact was removed"}` };
1087
+ const digest = hash.digest("hex");
1088
+ const declared = fetched.result;
1089
+ if (badFrame) return { failure: "fetch: an artifact frame had another digest or could not be written here" };
1090
+ if (digest !== artifact.sha256 || declared.sha256 !== digest || declared.size !== received) return { failure: `fetch: the artifact's sha256 ${digest.slice(0, 12)} does not match ${String(artifact.sha256).slice(0, 12)}` };
1091
+ if (cacheHit) {
1092
+ if ((await getExecutor().runFileAsync("tar", ["-tf", archive], { timeoutMs: 6e5 })).split("\n").filter(Boolean).some((member) => {
1093
+ const clean = member.replace(/^\.\//, "");
1094
+ return !(clean === name || clean.startsWith(`${name}/`)) || clean.split("/").includes("..");
1095
+ })) return { failure: "fetch: the archive contains paths outside its app" };
1096
+ }
1097
+ await getExecutor().runFileAsync("tar", [
1098
+ "-xf",
1099
+ archive,
1100
+ "-C",
1101
+ stagingDir
1102
+ ], { timeoutMs: 6e5 });
1103
+ checkCancelled();
1104
+ rmSync(archive, { force: true });
1105
+ const artifactPath = join(stagingDir, name);
1106
+ if (stagedArtifactEscapes(stagingDir, artifactPath, request.platform === "macos")) return { failure: `fetch: ${machine} returned ${name} as or through a symbolic link or outside the staging directory` };
1107
+ if (cacheHit) {
1108
+ const pending = [artifactPath];
1109
+ const root = realpathSync(artifactPath);
1110
+ for (const path of pending) {
1111
+ const stat = lstatSync(path);
1112
+ if (stat.isDirectory()) pending.push(...readdirSync(path).map((entry) => join(path, entry)));
1113
+ else if (stat.isSymbolicLink()) {
1114
+ const inside = relative(root, realpathSync(path));
1115
+ if (inside === ".." || inside.startsWith(`..${sep}`) || isAbsolute(inside)) return { failure: "fetch: an app link resolves outside its bundle" };
1116
+ }
1117
+ }
1118
+ }
1119
+ if (request.platform === "ios" && !existsSync(join(artifactPath, "Info.plist"))) return { failure: `fetch: ${name} has no Info.plist` };
1120
+ if (request.platform === "android" && !(lstatSync(artifactPath, { throwIfNoEntry: false })?.isFile() ?? false)) return { failure: `fetch: ${name} is not a file` };
1121
+ if (request.platform === "macos") {
1122
+ for (const segments of [["Contents", "Info.plist"], [
1123
+ "Contents",
1124
+ "MacOS",
1125
+ request.product
1126
+ ]]) if (!lstatSync(join(artifactPath, ...segments), { throwIfNoEntry: false })?.isFile()) return { failure: `fetch: ${name} has no regular ${segments.join("/")}` };
1127
+ }
1128
+ return {
1129
+ artifactPath,
1130
+ digest,
1131
+ received,
1132
+ declared
1133
+ };
1134
+ }
1135
+ function keepCorruptCacheMessage(failed, result) {
1136
+ return result.code === "STIM_COMPILATION_CACHE_CORRUPT" ? {
1137
+ ...failed,
1138
+ compilationCacheCorrupt: String(result.message ?? "")
1139
+ } : failed;
1140
+ }
1141
+ /**
1142
+ * Builds on the chosen machine and brings the `.app` or `.apk` back into `stagingDir`, verified against the
1143
+ * sha256 the machine reports. The caller re-fingerprints and stores it.
1144
+ */
1145
+ async function offloadBuild(input) {
1146
+ const { choice, expectedFingerprint, request, stagingDir, onPhase, onEnter, onRecord, note } = input;
1147
+ let cacheHit = false;
1148
+ let keepStaging = false;
1149
+ const { identity } = choice;
1150
+ const native = request.platform !== "macos" ? request.native : void 0;
1151
+ let sourceDigest = null;
1152
+ const started = Date.now();
1153
+ const fail = (reason) => {
1154
+ closeOffload(choice);
1155
+ return {
1156
+ ok: false,
1157
+ machine: choice.machine,
1158
+ reason: reason.split("\n")[0].slice(0, 300)
1159
+ };
1160
+ };
1161
+ const signal = runCancellationSignal(ambientContext());
1162
+ const checkCancelled = () => throwIfCancelled(signal, "offloaded build");
1163
+ let settle;
1164
+ let settled = false;
1165
+ const outcome = new Promise((resolve) => {
1166
+ settle = (value) => {
1167
+ settled = true;
1168
+ resolve(value);
1169
+ };
1170
+ });
1171
+ const cancel = () => {
1172
+ settle({
1173
+ ok: false,
1174
+ code: "cancelled"
1175
+ });
1176
+ closeOffload(choice);
1177
+ };
1178
+ signal?.addEventListener("abort", cancel, { once: true });
1179
+ const retryCachedMiss = async (reason) => {
1180
+ checkCancelled();
1181
+ note(`cache: ${choice.machine}: ${reason}`);
1182
+ const next = choice.runnersUp.shift();
1183
+ if (next) {
1184
+ choice.connection.close();
1185
+ Object.assign(choice, next);
1186
+ } else {
1187
+ if (choice.cacheOnly) return fail(reason);
1188
+ choice.connection.close();
1189
+ const probe = await probeMachine(choice.credential, {
1190
+ ...identity,
1191
+ dependencies: dependencySelection(identity, choice.target),
1192
+ ...native ? { native: native.provider } : {}
1193
+ });
1194
+ if ("failure" in probe) return fail(probe.failure);
1195
+ const placement = pickOffer({
1196
+ mode: choice.mode,
1197
+ here: choice.here,
1198
+ target: choice.target,
1199
+ selected: choice.automatic ? "auto" : choice.machine,
1200
+ localEnabled: !choice.automatic || automaticMachineEnabled("build", "local"),
1201
+ offers: [{
1202
+ machine: choice.machine,
1203
+ offer: probe.offer
1204
+ }]
1205
+ });
1206
+ if (placement.order.length === 0) {
1207
+ probe.connection.close();
1208
+ return fail(placement.reasons.join("; "));
1209
+ }
1210
+ Object.assign(choice, probe, { offer: {
1211
+ ...probe.offer,
1212
+ artifact: null
1213
+ } });
1214
+ }
1215
+ const retried = await offloadBuild(input);
1216
+ keepStaging = retried.ok;
1217
+ return retried;
1218
+ };
1219
+ try {
1220
+ checkCancelled();
1221
+ if (!validArtifactIdentity(request, expectedFingerprint)) return fail("The build request has an invalid reusable artifact identity.");
1222
+ if (native?.provider === "xcode" && native.snapshot.hash !== expectedFingerprint) return fail("The native source snapshot does not match the requested artifact identity.");
1223
+ let cached = null;
1224
+ let job = null;
1225
+ let early = [];
1226
+ let resuming = false;
1227
+ const reattach = async (why) => {
1228
+ if (resuming || settled) return;
1229
+ resuming = true;
1230
+ note(`offload: the connection to ${choice.machine} dropped (${why}); reattaching to the build there`);
1231
+ const resumed = await resumeJob(choice.credential, job, () => settled, signal, native?.provider);
1232
+ resuming = false;
1233
+ if (settled) {
1234
+ if (typeof resumed !== "string") resumed.connection.close();
1235
+ return;
1236
+ }
1237
+ if (typeof resumed === "string") return settle({
1238
+ ok: false,
1239
+ code: "closed",
1240
+ message: `${why}; ${resumed}`
1241
+ });
1242
+ choice.connection = resumed.connection;
1243
+ resumed.connection.watch();
1244
+ note(`offload: reattached to the build on ${choice.machine}`);
1245
+ resumed.connection.onProgress(handle);
1246
+ if (resumed.outcome) settle(resumed.outcome);
1247
+ for (const event of resumed.early.splice(0)) handle(event);
1248
+ };
1249
+ const handle = (event) => {
1250
+ if (event.job && event.job !== job) return;
1251
+ if (event.outcome?.code === "dropped" && !event.job && job !== null) {
1252
+ reattach(String(event.outcome.message));
1253
+ return;
1254
+ }
1255
+ if (event.outcome) return settle(event.outcome);
1256
+ if (event.record) onRecord(event.record);
1257
+ if (event.phase && typeof event.msg === "string") {
1258
+ onEnter(event.phase);
1259
+ onPhase(event.phase, event.msg);
1260
+ }
1261
+ };
1262
+ let syncMs = 0;
1263
+ let uploadedBytes = 0;
1264
+ let workerStarted = 0;
1265
+ const moveOn = (why) => {
1266
+ const next = choice.runnersUp.shift();
1267
+ if (!next) return false;
1268
+ note(`placement: ${next.machine} (${choice.machine} could not take the build: ${why})`);
1269
+ choice.connection.close();
1270
+ Object.assign(choice, next);
1271
+ return true;
1272
+ };
1273
+ for (;;) {
1274
+ if (choice.automatic && !automaticMachineEnabled("build", choice.machine)) {
1275
+ const reason = `${choice.machine} is disabled in remote.buildPoolDisabled`;
1276
+ if (moveOn(reason)) continue;
1277
+ return fail(reason);
1278
+ }
1279
+ if (native && !choice.connection.supports(NATIVE_BUILD_FEATURE[native.provider])) {
1280
+ const reason = unsupportedNative(native.provider);
1281
+ if (moveOn(reason)) continue;
1282
+ return fail(reason);
1283
+ }
1284
+ cached = await peerArtifact(choice, request, expectedFingerprint);
1285
+ if (cached) {
1286
+ cacheHit = true;
1287
+ settle({
1288
+ ok: true,
1289
+ artifact: cached,
1290
+ fingerprint: expectedFingerprint,
1291
+ androidPackage: cached.androidPackage
1292
+ });
1293
+ break;
1294
+ }
1295
+ if (choice.cacheOnly) return fail("The exact artifact is no longer available.");
1296
+ const synced = await syncSource(choice.connection, identity, onEnter, nativeTransferInput(native), signal);
1297
+ checkCancelled();
1298
+ if ("failure" in synced) {
1299
+ if (moveOn(synced.failure)) continue;
1300
+ return fail(synced.failure);
1301
+ }
1302
+ ({syncMs, uploadedBytes} = synced);
1303
+ sourceDigest = synced.digest;
1304
+ onPhase("sync", `${synced.files} files, uploaded ${synced.uploaded} (${mb(uploadedBytes)}) in ${seconds(syncMs)}`);
1305
+ workerStarted = Date.now();
1306
+ early = [];
1307
+ choice.connection.onProgress((event) => job === null ? early.push(event) : handle(event));
1308
+ if (choice.automatic) requireAutomaticMachine("build", choice.machine);
1309
+ checkCancelled();
1310
+ const reply = await choice.connection.request("build.start", {
1311
+ repo: identity.repo,
1312
+ project: identity.project,
1313
+ platform: request.platform,
1314
+ fingerprint: request.platform === "macos" ? synced.digest : expectedFingerprint,
1315
+ ...native ? { native: {
1316
+ provider: native.provider,
1317
+ sourceDigest: synced.digest,
1318
+ ...native.provider === "xcode" ? {
1319
+ cacheKey: native.cacheKey,
1320
+ arch: native.arch,
1321
+ parameters: native.snapshot.parameters
1322
+ } : {
1323
+ inputs: native.transfer.declaration,
1324
+ ...choice.connection.supports("native-gradle-artifact-cache") ? { cache: native.cache } : {}
1325
+ }
1326
+ } } : {},
1327
+ ...request.platform === "macos" ? macosBuildParameters(request, choice.connection.supports("swiftpm-peer-cache")) : {
1328
+ configuration: request.platform === "ios" ? request.configuration : null,
1329
+ scheme: request.platform === "ios" ? request.scheme : null,
1330
+ iosProjectPath: request.platform === "ios" ? request.iosProjectPath ?? null : null,
1331
+ runtime: request.platform === "ios" ? request.runtime : null,
1332
+ packageName: packageName(join(identity.repoRoot, identity.project)),
1333
+ isExpo: request.isExpo,
1334
+ ...!native && request.cacheArtifact === true && choice.connection.supports("react-native-peer-cache") ? { cacheArtifact: true } : {},
1335
+ optimizations: request.platform === "ios" && isJsonObject(request.optimizations) ? request.optimizations : null,
1336
+ ...request.platform === "android" ? { android: request.android } : {}
1337
+ },
1338
+ stimBuild: choice.target.local.stimBuild
1339
+ });
1340
+ checkCancelled();
1341
+ if ("result" in reply) {
1342
+ job = reply.result.job;
1343
+ break;
1344
+ }
1345
+ const refused = `start: ${replyError(reply)}`;
1346
+ if (reply.error.code === "bad-request" || !moveOn(refused)) return fail(refused);
1347
+ }
1348
+ if (!cacheHit) for (const each of choice.runnersUp.splice(0)) each.connection.close();
1349
+ choice.connection.watch();
1350
+ for (const event of early.splice(0)) handle(event);
1351
+ const timer = setTimeout(() => settle({
1352
+ ok: false,
1353
+ code: "timeout",
1354
+ message: "the build did not finish in time"
1355
+ }), JOB_TIMEOUT_MS);
1356
+ const result = await outcome;
1357
+ clearTimeout(timer);
1358
+ checkCancelled();
1359
+ const { connection, machine } = choice;
1360
+ connection.onProgress(null);
1361
+ const workerMs = cacheHit ? 0 : Date.now() - workerStarted;
1362
+ if (result.ok !== true) return keepCorruptCacheMessage(fail(`${String(result.code ?? "failed")}: ${String(result.message ?? "")}`), result);
1363
+ if (!cacheHit && native && (result.sourceDigest !== sourceDigest || result.fingerprint !== expectedFingerprint)) return fail("The worker returned a different native source or artifact identity.");
1364
+ if (native?.provider === "gradle" && !ANDROID_PACKAGE.test(String(result.androidPackage ?? ""))) return fail("The worker returned no verified native APK package.");
1365
+ const artifact = result.artifact;
1366
+ const name = typeof artifact?.name === "string" ? artifact.name : "";
1367
+ if (!ARTIFACT_NAME[request.platform].test(name) || typeof artifact.sha256 !== "string") return fail(`the machine reported no .${request.platform === "android" ? "apk" : "app"} artifact`);
1368
+ onEnter("fetch");
1369
+ checkCancelled();
1370
+ const fetchStarted = Date.now();
1371
+ rmSync(stagingDir, {
1372
+ recursive: true,
1373
+ force: true
1374
+ });
1375
+ mkdirSync(stagingDir, { recursive: true });
1376
+ const downloaded = await fetchArtifact({
1377
+ connection,
1378
+ artifact: {
1379
+ name,
1380
+ size: artifact.size,
1381
+ sha256: artifact.sha256
1382
+ },
1383
+ request,
1384
+ stagingDir,
1385
+ machine,
1386
+ cacheHit,
1387
+ checkCancelled,
1388
+ method: cacheHit ? "build.cached-artifact" : "build.artifact",
1389
+ params: cached ? {
1390
+ repo: identity.repo,
1391
+ artifact: {
1392
+ fingerprint: cached.fingerprint,
1393
+ cacheKey: cached.cacheKey
1394
+ },
1395
+ ...choice.gradleArtifact ? { gradleArtifact: choice.gradleArtifact } : {},
1396
+ ...choice.reactNativeArtifact ? { reactNativeArtifact: choice.reactNativeArtifact } : {},
1397
+ ...choice.swiftpmArtifact ? { swiftpmArtifact: choice.swiftpmArtifact } : {}
1398
+ } : { job }
1399
+ });
1400
+ if ("failure" in downloaded) return cacheHit ? await retryCachedMiss(downloaded.failure) : fail(downloaded.failure);
1401
+ if (cacheHit && choice.swiftpmArtifact && !await peerArtifact(choice, request, expectedFingerprint)) return await retryCachedMiss("The SwiftPM source changed during artifact transfer.");
1402
+ const { artifactPath, digest, received, declared } = downloaded;
1403
+ const fetchMs = Date.now() - fetchStarted;
1404
+ closeOffload(choice);
1405
+ keepStaging = true;
1406
+ return {
1407
+ ok: true,
1408
+ ...cacheHit ? { cacheHit: true } : {},
1409
+ machine,
1410
+ artifactPath,
1411
+ ...typeof declared.handoff === "string" && /^[0-9a-f]{64}$/.test(declared.handoff) ? { handoff: {
1412
+ serverKey: choice.credential.serverKey,
1413
+ token: declared.handoff,
1414
+ sha256: digest
1415
+ } } : {},
1416
+ ...offloadCacheActivity(request.platform, result.compilationCache, cacheHit),
1417
+ ...native?.provider === "gradle" ? { androidPackage: result.androidPackage } : {},
1418
+ timings: {
1419
+ offerMs: choice.offerMs,
1420
+ syncMs,
1421
+ workerMs,
1422
+ fetchMs,
1423
+ totalMs: Date.now() - started + choice.offerMs,
1424
+ worker: isJsonObject(result.timings) ? result.timings : {},
1425
+ uploadedBytes,
1426
+ artifactBytes: received
1427
+ }
1428
+ };
1429
+ } catch (error) {
1430
+ checkCancelled();
1431
+ const reason = error.message.split("\n")[0] ?? String(error);
1432
+ return cacheHit ? await retryCachedMiss(reason) : fail(reason);
1433
+ } finally {
1434
+ signal?.removeEventListener("abort", cancel);
1435
+ if (!keepStaging) rmSync(stagingDir, {
1436
+ recursive: true,
1437
+ force: true
1438
+ });
1439
+ }
1440
+ }
1441
+ function offloadCacheActivity(platform, activity, cacheHit) {
1442
+ if (cacheHit) return {
1443
+ compilationCache: COMPILATION_CACHE_NOT_RUN,
1444
+ ccache: CCACHE_NOT_RUN
1445
+ };
1446
+ return {
1447
+ compilationCache: platform === "ios" ? compilationActivity(activity) : COMPILATION_CACHE_UNAVAILABLE,
1448
+ ccache: platform === "android" ? ccacheActivity(activity) : CCACHE_UNAVAILABLE
1449
+ };
1450
+ }
1451
+ function packageName(projectRoot) {
1452
+ try {
1453
+ const name = JSON.parse(readFileSync(join(projectRoot, "package.json"), "utf8")).name;
1454
+ return typeof name === "string" ? name : null;
1455
+ } catch {
1456
+ return null;
1457
+ }
1458
+ }
1459
+ const numberOrNull = (field) => typeof field === "number" ? field : null;
1460
+ function compilationActivity(value) {
1461
+ if (!isJsonObject(value) || value.status !== "reported") return COMPILATION_CACHE_UNAVAILABLE;
1462
+ return {
1463
+ status: "reported",
1464
+ hits: numberOrNull(value.hits),
1465
+ cacheableTasks: numberOrNull(value.cacheableTasks),
1466
+ hitRatePercent: numberOrNull(value.hitRatePercent)
1467
+ };
1468
+ }
1469
+ function ccacheActivity(value) {
1470
+ if (!isJsonObject(value) || value.status !== "reported") return CCACHE_UNAVAILABLE;
1471
+ return {
1472
+ status: "reported",
1473
+ hits: numberOrNull(value.hits),
1474
+ misses: numberOrNull(value.misses),
1475
+ hitRatePercent: numberOrNull(value.hitRatePercent)
1476
+ };
1477
+ }
1478
+ const mb = (bytes) => `${(bytes / 1024 ** 2).toFixed(1)} MB`;
1479
+ const seconds = (ms) => `${(ms / 1e3).toFixed(1)}s`;
1480
+ function simulatorRuntime(udid) {
1481
+ try {
1482
+ const listed = JSON.parse(getExecutor().runFileQuiet("xcrun", [
1483
+ "simctl",
1484
+ "list",
1485
+ "devices",
1486
+ "-j"
1487
+ ], { timeoutMs: 2e4 }) ?? "{}");
1488
+ for (const [runtime, devices] of Object.entries(listed.devices ?? {})) if (devices.some((device) => device.udid === udid)) return runtime;
1489
+ } catch {}
1490
+ return null;
1491
+ }
1492
+ /**
1493
+ * Every reason a machine's offer would not take this app's builds, judged for each of `targets`. A target the machine
1494
+ * cannot build at all, such as iOS on Linux, is left out while it can build another of them. With several targets, a
1495
+ * reason only one platform has names it.
1496
+ */
1497
+ function machineProblems(offer, targets) {
1498
+ const judged = targets.map((target) => ({
1499
+ target,
1500
+ found: offerProblems(offer, target)
1501
+ }));
1502
+ const buildable = judged.filter(({ found }) => !found.some((problem) => problem.code === "platform"));
1503
+ const problems = [];
1504
+ for (const { target, found } of buildable.length ? buildable : judged) for (const problem of found) {
1505
+ const labeled = targets.length > 1 && !SHARED_PROBLEMS.has(problem.code) ? {
1506
+ ...problem,
1507
+ reason: `${PLATFORM_LABEL[target.platform]}: ${problem.reason}`
1508
+ } : problem;
1509
+ if (!problems.some((each) => each.code === labeled.code && each.reason === labeled.reason)) problems.push(labeled);
1510
+ }
1511
+ return problems;
1512
+ }
1513
+ const DOCTOR_CONNECT_MS = 5e3;
1514
+ const DOCTOR_OFFER_MS = 8e3;
1515
+ const PLATFORM_LABEL = {
1516
+ ios: "iOS",
1517
+ android: "Android",
1518
+ macos: "macOS"
1519
+ };
1520
+ const SHARED_PROBLEMS = /* @__PURE__ */ new Set([
1521
+ "stim-build",
1522
+ "disk",
1523
+ "busy"
1524
+ ]);
1525
+ /**
1526
+ * Doctor's offload check for `projectRoot`: one bounded offer per machine, judged by `machineProblems` for the builds
1527
+ * `targets` returns. No build runs.
1528
+ */
1529
+ function offloadCheck(projectRoot, targets) {
1530
+ let identity;
1531
+ const readIdentity = async () => {
1532
+ try {
1533
+ const repoRoot = realpathSync(await gitAsync(projectRoot, ["rev-parse", "--show-toplevel"]));
1534
+ return repositoryIdentity(projectRoot, repoRoot, realpathSync(await gitAsync(repoRoot, [
1535
+ "rev-parse",
1536
+ "--path-format=absolute",
1537
+ "--git-common-dir"
1538
+ ])));
1539
+ } catch (error) {
1540
+ reportSignal(ambientContext())?.throwIfAborted();
1541
+ return error instanceof Error ? error.message.split("\n")[0] ?? "git failed" : "git failed";
1542
+ }
1543
+ };
1544
+ let resolved = null;
1545
+ return async (credential) => {
1546
+ const repository = await (identity ??= readIdentity());
1547
+ if (typeof repository === "string") return {
1548
+ capacity: null,
1549
+ problems: [{
1550
+ code: "checkout",
1551
+ reason: `this app is not in a git checkout (${repository})`
1552
+ }]
1553
+ };
1554
+ const probe = await probeMachine(credential, {
1555
+ ...repository,
1556
+ rubyVersion: readRubyVersion(projectRoot) ?? void 0
1557
+ }, {
1558
+ connectMs: DOCTOR_CONNECT_MS,
1559
+ offerMs: DOCTOR_OFFER_MS
1560
+ });
1561
+ if ("failure" in probe) return {
1562
+ capacity: null,
1563
+ problems: [{
1564
+ code: "unreachable",
1565
+ reason: probe.failure
1566
+ }]
1567
+ };
1568
+ probe.connection.close();
1569
+ resolved ??= targets();
1570
+ return {
1571
+ capacity: probe.offer.capacity,
1572
+ problems: machineProblems(probe.offer, await resolved)
1573
+ };
1574
+ };
1575
+ }
1576
+ //#endregion
1577
+ export { clearCorruptCompilationCache as _, nativeTransferRefusal as a, offloadPlacement as c, simulatorRuntime as d, reactNativeArtifactReceipt as f, COMPILATION_CACHE_CORRUPT as g, setRemoteLogSink as h, closeOffload as i, placementLoad as l, reactNativeBuildQuery as m, buildPlacementCandidates as n, offloadBuild as o, reactNativeArtifactToolchain as p, chooseBuildMachine as r, offloadCheck as s, BuildConnection as t, remotePhaseText as u, maintenanceLogger as v };