stim 1.19.0 → 1.20.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 (172) hide show
  1. package/README.md +20 -3
  2. package/dist/{activity-BSw0x84R.mjs → activity-BrGh9EAI.mjs} +3 -3
  3. package/dist/{agent-device-usage-output-Bjxu_VmX.mjs → agent-device-usage-output-DVyv_hWj.mjs} +7 -7
  4. package/dist/android-0vv-OruW.mjs +6 -0
  5. package/dist/android-Brx6EKvJ.mjs +1258 -0
  6. package/dist/{android-BdQC6zs1.mjs → android-BsYNR81W.mjs} +103 -45
  7. package/dist/{android-DE5g_5gv.mjs → android-CK3fpEyZ.mjs} +2 -2
  8. package/dist/{android-cas-C87geAg-.mjs → android-cas-DCAuXryb.mjs} +9 -5
  9. package/dist/android-cas-compiler.mjs +1 -1
  10. package/dist/api-run.mjs +159 -8
  11. package/dist/{api-DIf_s86s.mjs → api-zuX3ynYN.mjs} +5 -1
  12. package/dist/api.d.mts +98 -4
  13. package/dist/api.mjs +1 -1
  14. package/dist/{app-install-CFI4ptUL.mjs → app-install-0LgaQRT7.mjs} +9 -8
  15. package/dist/artifact-CnC0mfov.mjs +807 -0
  16. package/dist/artifact-DClXJNWx.mjs +772 -0
  17. package/dist/artifact-lifecycle-DzPpBYvK.mjs +30 -0
  18. package/dist/{browser-web-CDhqQxhv.mjs → browser-web-CzBmhmgs.mjs} +19 -19
  19. package/dist/{budget-Dz-kyXLv.mjs → budget-BONq9vXY.mjs} +3101 -3062
  20. package/dist/build-BixRI6sB.mjs +462 -0
  21. package/dist/{build-facts-B87vo-Im.d.mts → build-facts-DcLv2HSd.d.mts} +1 -32
  22. package/dist/build-plan-CP0tckNU.mjs +553 -0
  23. package/dist/{cache-manifest-DiwKSyv9.mjs → cache-manifest-qA3dT2x2.mjs} +1 -1
  24. package/dist/cache-manifest.mjs +1 -1
  25. package/dist/{manifest-CX0d1IkZ.mjs → ccache-CjHQ5F8g.mjs} +13 -19
  26. package/dist/{chrome-BTUOo8p2.mjs → chrome-Dp2lMLBe.mjs} +1 -1
  27. package/dist/{cli-C9Zvgbdc.mjs → cli-B2R8wH1F.mjs} +20 -20
  28. package/dist/cli.mjs +1 -1
  29. package/dist/{client-CuUo8Nvg.mjs → client-BLy1Pw2V.mjs} +89 -307
  30. package/dist/collector-run.d.mts +4 -0
  31. package/dist/collector-run.mjs +7 -7
  32. package/dist/{command-output-Bt8ygHE7.mjs → command-output-BjPN99ZB.mjs} +2 -2
  33. package/dist/{config-DC5jEtd1.mjs → config-D0LhTY3S.mjs} +8 -12
  34. package/dist/{created-devices-D0s26Hx5.mjs → created-devices-HU_DAb_Q.mjs} +7 -6
  35. package/dist/{dependency-state-DKaS1OM9.mjs → dependency-state-B2m8eqsS.mjs} +2 -2
  36. package/dist/{detached-entry-pB_IPfV4.mjs → detached-entry-BXq4fIl-.mjs} +1 -1
  37. package/dist/{dev-client-CDY_zAmD.mjs → dev-client-ClATaJoH.mjs} +2 -2
  38. package/dist/{device-BHw0WlHl.mjs → device-B7lsC0K1.mjs} +8 -8
  39. package/dist/device-DVCwAFlX.mjs +727 -0
  40. package/dist/{device-capacity-BoN1jQkO.mjs → device-capacity-Ca_ke1LE.mjs} +13 -14
  41. package/dist/device-host-worker.mjs +38 -18
  42. package/dist/device-ios-CRUqPxKw.mjs +413 -0
  43. package/dist/{device-lease-Cu7PtQpZ.mjs → device-lease-DHOBXGgS.mjs} +135 -22
  44. package/dist/{device-lease-run-D38SnZtt.mjs → device-lease-run-CvDeHTkG.mjs} +4 -4
  45. package/dist/{device-pool-CP4rAsHx.mjs → device-pool-L2-5GxMy.mjs} +3 -3
  46. package/dist/{device-remote-B5GiUifk.mjs → device-remote-Bmg42063.mjs} +17 -68
  47. package/dist/doctor-C2MSuMMg.mjs +515 -0
  48. package/dist/{doctor-PRJH94v6.mjs → doctor-DPAqhVCK.mjs} +333 -744
  49. package/dist/{doctor-watchman-wZhyTdp4.mjs → doctor-watchman-CB4NnVZm.mjs} +13 -12
  50. package/dist/{simslim-D9LkJxIO.mjs → eas-build-Dw_ec2Km.mjs} +6 -152
  51. package/dist/{stim-installations-DbmUjsLv.mjs → eas-session-ledger-DZlC-Fq3.mjs} +57 -4
  52. package/dist/{error-diagnostics--v5HGOU0.mjs → error-diagnostics-Du7I_GuY.mjs} +17 -12
  53. package/dist/errors-DzkMYh8Q.mjs +205 -0
  54. package/dist/{exec-i8GDPwq9.mjs → exec-B81Qodjn.mjs} +40 -10
  55. package/dist/{gc-CyHMcKcn.mjs → gc-PWp34zGb.mjs} +21 -20
  56. package/dist/{device-ios-B2ruohE-.mjs → gradle-C2OEKCCI.mjs} +21 -650
  57. package/dist/{guide-dRaQJJk9.mjs → guide-Dai9lphv.mjs} +504 -117
  58. package/dist/{hosted-android-Bs7fytj7.mjs → hosted-android-DQ-Agsd8.mjs} +5 -5
  59. package/dist/{hosted-client-C5G4h_OJ.mjs → hosted-client-Io01s5By.mjs} +9 -3
  60. package/dist/{hosted-ios-Bik035eL.mjs → hosted-ios-DIxFnYp-.mjs} +3 -3
  61. package/dist/{hosted-logs-CI9Az9a4.mjs → hosted-logs-DP-ABU_M.mjs} +8 -8
  62. package/dist/{hosted-macos-DABm3ScO.mjs → hosted-macos-Aziw9T_9.mjs} +7 -6
  63. package/dist/{hosted-native-DbV2aUVw.mjs → hosted-native-BdAQi21M.mjs} +17 -14
  64. package/dist/{idle-shutdown-DYiMBn34.mjs → idle-shutdown-DdMqoSUB.mjs} +7 -7
  65. package/dist/{ios-DYUr9LGc.mjs → ios-BZ9dg2Fr.mjs} +8 -7
  66. package/dist/{ios-BWbHFZVz.mjs → ios-CXLIUxXJ.mjs} +180 -24
  67. package/dist/ios-CZuv6gRv.mjs +1112 -0
  68. package/dist/{ios-DCrkgXEM.mjs → ios-VGvNdsv2.mjs} +1 -1
  69. package/dist/{ios-device-CrW8aMb5.mjs → ios-device-B1B14xND.mjs} +1 -1
  70. package/dist/{ios-device-CJ_ZOGky.mjs → ios-device-BreZOpM0.mjs} +3 -3
  71. package/dist/ios-project-B9drt_aY.mjs +197 -0
  72. package/dist/{ios-state-By0XuLTQ.mjs → ios-state-CjrL2qv-.mjs} +1 -1
  73. package/dist/js-swap-C8icNYIY.mjs +432 -0
  74. package/dist/launch-DxH0THLP.mjs +745 -0
  75. package/dist/{react-native-ios-CSpIxexZ.mjs → launch-YrbqaIFj.mjs} +59 -612
  76. package/dist/{launch-verify-DCveSE6d.mjs → launch-verify-BzRdbuO1.mjs} +26 -7
  77. package/dist/{logs-Bao1HOMy.mjs → logs-DHaRNfg7.mjs} +11 -11
  78. package/dist/macos-C0iljhsN.mjs +2 -0
  79. package/dist/{macos-ciyBdTY5.mjs → macos-V_3Ui7g4.mjs} +42 -50
  80. package/dist/macos-run.mjs +1 -1
  81. package/dist/maintenance-run.mjs +13 -10
  82. package/dist/{metro-DI2AjPF_.mjs → metro-D_j9sN-B.mjs} +18 -5
  83. package/dist/{metro-gateway-BxpJLIwh.mjs → metro-gateway-BBJldOdi.mjs} +2 -2
  84. package/dist/miss-reason-BPAzZS0r.mjs +238 -0
  85. package/dist/{named-ports-B4iWH4E-.mjs → named-ports-C2lvoPsb.mjs} +3 -3
  86. package/dist/native-android-lHzaew7r.mjs +226 -0
  87. package/dist/native-gradle-inputs-BQnIdAlL.mjs +356 -0
  88. package/dist/{native-runtime-BGxsbfny.mjs → native-runtime-BRmWL7ru.mjs} +9 -8
  89. package/dist/native-xcode-inputs-eutS19jU.mjs +307 -0
  90. package/dist/native-xcode-ios-DP3cHLwu.mjs +434 -0
  91. package/dist/{ndjson-LGZAMsqa.mjs → ndjson-GOnbyvso.mjs} +6 -6
  92. package/dist/offload-worker.d.mts +41 -7
  93. package/dist/offload-worker.mjs +409 -74
  94. package/dist/{ownership-EB0ySdGK.mjs → ownership-DXzMMSx6.mjs} +2 -2
  95. package/dist/{placement-log-Dxe34rJm.mjs → placement-log-reFbjqEr.mjs} +1 -1
  96. package/dist/plan-BDk2AeY_.mjs +260 -0
  97. package/dist/plan-placement-D_YYlE6N.mjs +73 -0
  98. package/dist/{ports-ByPc0_97.mjs → ports-jzvAJXXL.mjs} +3 -3
  99. package/dist/{prebuild-BKJ3wJI1.mjs → prebuild-Bw44QxVh.mjs} +4 -4
  100. package/dist/{preview-BbSottmO.mjs → preview-BaBJ72Al.mjs} +50 -32
  101. package/dist/project-B8_duAzW.mjs +1067 -0
  102. package/dist/project-plan-BPwbLwQu.mjs +10 -0
  103. package/dist/{pull-request-IHNdMnG8.mjs → pull-request-vOhLNmIb.mjs} +1 -1
  104. package/dist/pull-requests.mjs +1 -1
  105. package/dist/react-native-android-chm1DMlA.mjs +921 -0
  106. package/dist/react-native-build-oAw6gQeW.mjs +239 -0
  107. package/dist/react-native-doctor-DYXoh-6I.mjs +78 -0
  108. package/dist/react-native-ios-SCTK_CT5.mjs +595 -0
  109. package/dist/{recordings-Bv8McZN4.mjs → recordings-Bk-VivUV.mjs} +1 -1
  110. package/dist/{reload-rAPdqeRR.mjs → reload-D34GkY78.mjs} +17 -13
  111. package/dist/remote-BU9UyhCJ.mjs +192 -0
  112. package/dist/{remote-cache-N3hAEaAL.mjs → remote-cache-Bsts6f6Y.mjs} +5 -5
  113. package/dist/result-BDoRk1k3.mjs +186 -0
  114. package/dist/{run-lAkle1-c.mjs → run-qvff78hv.mjs} +3 -3
  115. package/dist/{server-bare-CkIjnegq.mjs → server-bare-dWjAarki.mjs} +5 -5
  116. package/dist/server-command-7XIKlWtO.mjs +69 -0
  117. package/dist/{server-expo-n3tUi3Em.mjs → server-expo-CsYu2aKL.mjs} +107 -82
  118. package/dist/server-expo-DV43vep4.mjs +2 -0
  119. package/dist/{settings-CxgngUZk.mjs → settings-Bbztm4oV.mjs} +179 -129
  120. package/dist/{settings-2am7TSxp.mjs → settings-CT3E71-i.mjs} +6 -6
  121. package/dist/{state-lKh8shBp.d.mts → settings-vk3mJGSm.d.mts} +4 -2
  122. package/dist/settings.schema.json +84 -0
  123. package/dist/simslim-ChMfe8rg.mjs +149 -0
  124. package/dist/{slot-launch-HV_KtPIw.mjs → slot-launch-B9hn96wd.mjs} +6 -6
  125. package/dist/{stage-BgaZJVZY.mjs → stage-BNKKdDNc.mjs} +6 -3
  126. package/dist/start-CbQCSnXQ.mjs +2 -0
  127. package/dist/{start-DtxthMfI.mjs → start-CdvcTaXr.mjs} +97 -68
  128. package/dist/{state-Tp4uImDS.mjs → state-Boo0IMJq.mjs} +1 -1
  129. package/dist/{state-D2KtnALG.mjs → state-DI84A-uu.mjs} +29 -10
  130. package/dist/{stats-8EJ5ivFT.mjs → stats-BIIDrjA8.mjs} +5 -5
  131. package/dist/{status-BEeL6IKz.mjs → status-BDGHwkCs.mjs} +3 -1
  132. package/dist/{status-BOyp0CUl.mjs → status-DARMkc9q.mjs} +81 -293
  133. package/dist/status-watch-DvmI3SmN.mjs +296 -0
  134. package/dist/{stim-desktop-C65jk_q2.mjs → stim-desktop-8GQX2OV8.mjs} +1 -1
  135. package/dist/{stop-DRipAg-e.mjs → stop-CCZS-C9m.mjs} +2 -2
  136. package/dist/{stop-D0UIpZuh.mjs → stop-WQPQHLuy.mjs} +5 -5
  137. package/dist/{stop-zDlknFoz.mjs → stop-iNemHsJC.mjs} +21 -20
  138. package/dist/supervisor-run.d.mts +7 -2
  139. package/dist/supervisor-run.mjs +32 -20
  140. package/dist/{support-DsNaFg6D.mjs → support-CnE_SIjt.mjs} +17 -7
  141. package/dist/{support-CNqYkV0y.mjs → support-DCco5fut.mjs} +61 -8
  142. package/dist/{swiftpm-macos-aJzPAj1t.mjs → swiftpm-macos-afeNUExW.mjs} +5 -5
  143. package/dist/tailnet-CoJUb2tB.mjs +265 -0
  144. package/dist/toolchain-BIupIro1.mjs +2 -0
  145. package/dist/{toolchain-C3xSoyUl.mjs → toolchain-DqyDXs30.mjs} +206 -53
  146. package/dist/{trigger-CAhmG-4s.mjs → trigger-C0MWBi8O.mjs} +2 -2
  147. package/dist/trigger-SHLOWJ4O.mjs +2 -0
  148. package/dist/{warm-progress-pFLB2e6P.mjs → warm-progress-C3ASD6z5.mjs} +6 -6
  149. package/dist/{watchman-BL1IdOtq.mjs → watchman-CQl51YZu.mjs} +6 -6
  150. package/dist/{web-DbbrPUn0.mjs → web-B3eldHFH.mjs} +9 -13
  151. package/dist/web-BLXpOe6b.mjs +2 -0
  152. package/dist/web-run.mjs +10 -10
  153. package/dist/{workspace-state-CR9X86xJ.mjs → workspace-state-CSIFuuDa.mjs} +33 -33
  154. package/dist/{workspaces-Djb2gfi9.mjs → workspaces-D2xSHBDY.mjs} +1260 -1258
  155. package/dist/{worktree-BL4M1rhB.mjs → worktree-BxDqaTev.mjs} +84 -48
  156. package/dist/worktree-q7_OZNY0.mjs +2 -0
  157. package/package.json +8 -6
  158. package/shim/native-android.gradle +101 -0
  159. package/dist/android-BXc6BmEi.mjs +0 -3732
  160. package/dist/android-BvV1gGdN.mjs +0 -4
  161. package/dist/ios-ITYno9rO.mjs +0 -2241
  162. package/dist/macos-nIOATJmR.mjs +0 -2
  163. package/dist/metro-store-BAHFcJm6.mjs +0 -106
  164. package/dist/miss-reason-CjQCOKqq.mjs +0 -1743
  165. package/dist/plan-placement-BVY59qa8.mjs +0 -290
  166. package/dist/project-B5eQ1USs.mjs +0 -612
  167. package/dist/react-native-ios-BMh2i25I.mjs +0 -2
  168. package/dist/server-expo-DGsdUGX2.mjs +0 -2
  169. package/dist/start-D8NbxNBb.mjs +0 -2
  170. package/dist/trigger-CoalsMTV.mjs +0 -2
  171. package/dist/web-DFpFeZu5.mjs +0 -2
  172. package/dist/worktree-DV-A0LNL.mjs +0 -2
@@ -1,27 +1,27 @@
1
- import { n as debugLog } from "./exec-i8GDPwq9.mjs";
2
- import { y as refuseRelativeStimPaths } from "./config-DC5jEtd1.mjs";
1
+ import { n as debugLog } from "./exec-B81Qodjn.mjs";
2
+ import { y as refuseRelativeStimPaths } from "./config-D0LhTY3S.mjs";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { runId } from "@stim-cli/core/state";
5
5
  import { Command } from "commander";
6
6
  //#region bin/cli.ts
7
7
  const commands = /* @__PURE__ */ new Map([
8
- ["doctor", () => import("./doctor-PRJH94v6.mjs")],
9
- ["worktree", () => import("./worktree-DV-A0LNL.mjs")],
10
- ["start", () => import("./start-D8NbxNBb.mjs")],
11
- ["ports", () => import("./ports-ByPc0_97.mjs")],
12
- ["stop", () => import("./stop-DRipAg-e.mjs")],
13
- ["ios", () => import("./ios-DYUr9LGc.mjs")],
14
- ["android", () => import("./android-BvV1gGdN.mjs")],
15
- ["web", () => import("./web-DFpFeZu5.mjs")],
16
- ["macos", () => import("./macos-nIOATJmR.mjs")],
17
- ["reload", () => import("./reload-rAPdqeRR.mjs")],
18
- ["device", () => import("./device-BHw0WlHl.mjs")],
19
- ["logs", () => import("./logs-Bao1HOMy.mjs")],
20
- ["settings", () => import("./settings-2am7TSxp.mjs")],
21
- ["status", () => import("./status-BOyp0CUl.mjs")],
22
- ["stats", () => import("./stats-8EJ5ivFT.mjs")],
23
- ["gc", () => import("./gc-CyHMcKcn.mjs")],
24
- ["guide", () => import("./guide-dRaQJJk9.mjs")]
8
+ ["doctor", () => import("./doctor-C2MSuMMg.mjs")],
9
+ ["worktree", () => import("./worktree-q7_OZNY0.mjs")],
10
+ ["start", () => import("./start-CbQCSnXQ.mjs")],
11
+ ["ports", () => import("./ports-jzvAJXXL.mjs")],
12
+ ["stop", () => import("./stop-CCZS-C9m.mjs")],
13
+ ["ios", () => import("./ios-BZ9dg2Fr.mjs")],
14
+ ["android", () => import("./android-0vv-OruW.mjs")],
15
+ ["web", () => import("./web-BLXpOe6b.mjs")],
16
+ ["macos", () => import("./macos-C0iljhsN.mjs")],
17
+ ["reload", () => import("./reload-D34GkY78.mjs")],
18
+ ["device", () => import("./device-B7lsC0K1.mjs")],
19
+ ["logs", () => import("./logs-DHaRNfg7.mjs")],
20
+ ["settings", () => import("./settings-CT3E71-i.mjs")],
21
+ ["status", () => import("./status-DARMkc9q.mjs")],
22
+ ["stats", () => import("./stats-BIIDrjA8.mjs")],
23
+ ["gc", () => import("./gc-PWp34zGb.mjs")],
24
+ ["guide", () => import("./guide-Dai9lphv.mjs")]
25
25
  ]);
26
26
  const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
27
27
  const program = new Command();
@@ -52,7 +52,7 @@ try {
52
52
  }));
53
53
  }
54
54
  try {
55
- const { triggerMaintenance } = await import("./trigger-CoalsMTV.mjs");
55
+ const { triggerMaintenance } = await import("./trigger-SHLOWJ4O.mjs");
56
56
  triggerMaintenance(action.name());
57
57
  } catch {}
58
58
  });
package/dist/cli.mjs CHANGED
@@ -4,6 +4,6 @@ const encode = (version) => version.split(".", 3).reduce((sum, part) => sum * 1e
4
4
  if (encode(process.versions.node) < encode("22.12.0")) {
5
5
  process.stderr.write(`STIM_NODE_UNSUPPORTED: Stim needs Node 22.12.0 or later; this is Node ${process.versions.node} at ${process.execPath}. To run Stim where a project pins an older Node, see https://stim.appandflow.com/docs/requirements#older-node-pins\n`);
6
6
  process.exitCode = 1;
7
- } else import("./cli-C9Zvgbdc.mjs");
7
+ } else import("./cli-B2R8wH1F.mjs");
8
8
  //#endregion
9
9
  export {};
@@ -1,18 +1,17 @@
1
- import { n as debugLog, t as getExecutor } from "./exec-i8GDPwq9.mjs";
2
- import { a as formatElapsed } from "./command-output-Bt8ygHE7.mjs";
3
- import { _ as loadConfig$1, d as getConfigDir } from "./config-DC5jEtd1.mjs";
4
- import { a as updateWorkspaceState, r as readWorkspaceState$1, t as clearWorkspaceStateKey, v as workspaceDir, w as withDirLock } from "./workspace-state-CR9X86xJ.mjs";
5
- import { i as signalDeclaredSpawns, r as refuseDeclaredSpawns, t as allowDeclaredSpawns } from "./spawn-claims-Kqiifsk2.mjs";
6
- import { a as withWorkspaceProcessLock } from "./named-ports-B4iWH4E-.mjs";
7
- import { r as CCACHE_UNAVAILABLE, t as manifestDigest } from "./manifest-CX0d1IkZ.mjs";
8
- import { a as toolchainMismatches, m as COMPILATION_CACHE_UNAVAILABLE, u as readRubyVersion } from "./toolchain-C3xSoyUl.mjs";
1
+ import { n as debugLog, t as getExecutor } from "./exec-B81Qodjn.mjs";
2
+ import { _ as loadConfig$1, d as getConfigDir } from "./config-D0LhTY3S.mjs";
3
+ import { D as withDirLock } from "./workspace-state-CSIFuuDa.mjs";
4
+ import { a as markApproved, c as pinnedEndpoint$1, i as lapsedLine, l as realIo, n as findPeer$1, o as parseMachine$1, r as isLapsed, s as pendingReport, t as endpoint, u as requestedLine, x as runCancellationSignal } from "./tailnet-CoJUb2tB.mjs";
5
+ import { n as CCACHE_UNAVAILABLE } from "./ccache-CjHQ5F8g.mjs";
6
+ import { l as toolchainMismatches, m as readRubyVersion, v as COMPILATION_CACHE_UNAVAILABLE } from "./toolchain-DqyDXs30.mjs";
7
+ import { c as gitVisiblePaths, i as nativeGradleTransfer, l as nativeTransferManifest, m as manifestDigest, s as checkNativeTransferMembership, u as sourceManifest } from "./native-gradle-inputs-BQnIdAlL.mjs";
9
8
  import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readlinkSync, realpathSync, renameSync, rmSync, writeFileSync, writeSync } from "node:fs";
10
- import { basename, dirname, join, relative } from "node:path";
11
- import { OFFLOAD_MODES, acceptsShape, automaticMachineEnabled, buildMachinesFile, buildMachinesLock, deviceHostMachinesClaims, deviceHostMachinesFile, deviceHostMachinesLock, endpoint, findPeer as findPeer$1, isJsonObject, parseMachine, parseMachine as parseMachine$1, pinnedEndpoint, readBuildMachines, readDeviceHostMachines, requireAutomaticMachine, runId, saturation, settingDefinition } from "@stim-cli/core/state";
12
- import { hostname } from "node:os";
9
+ import { basename, dirname, isAbsolute, join, relative, sep } from "node:path";
10
+ import { OFFLOAD_MODES, acceptsShape, automaticMachineEnabled, buildMachinesFile, buildMachinesLock, deviceHostMachinesClaims, deviceHostMachinesFile, deviceHostMachinesLock, isJsonObject, parseMachine, readBuildMachines, readDeviceHostMachines, requireAutomaticMachine, runId, saturation, settingDefinition } from "@stim-cli/core/state";
13
11
  import { InvalidArgumentError } from "commander";
14
12
  import { releaseClaim, tryAcquireClaim } from "@stim-cli/core/ownership-claim";
15
13
  import { createHash } from "node:crypto";
14
+ import { hostname } from "node:os";
16
15
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
17
16
  import { WebSocket } from "ws";
18
17
  //#region src/offload/access-ticket.ts
@@ -32,95 +31,6 @@ function readHostPermissions(value) {
32
31
  } : void 0;
33
32
  }
34
33
  //#endregion
35
- //#region src/offload/tailnet.ts
36
- const MAC_APP_TAILSCALE = "/Applications/Tailscale.app/Contents/MacOS/Tailscale";
37
- const HELLO_TIMEOUT_MS = 1e4;
38
- function isLapsed(credential) {
39
- return credential.state === "pending" && !!credential.expiresAt && Date.parse(credential.expiresAt) <= Date.now();
40
- }
41
- function markApproved(credential) {
42
- const { expiresAt: _expiresAt, ...rest } = credential;
43
- return {
44
- ...rest,
45
- state: "approved"
46
- };
47
- }
48
- function pendingReport(credential) {
49
- return {
50
- state: "pending",
51
- ...credential.expiresAt ? { expiresAt: credential.expiresAt } : {}
52
- };
53
- }
54
- function lapsedLine(machine, credential) {
55
- return `${machine} did not approve this Mac before the request lapsed at ${credential.expiresAt}.`;
56
- }
57
- function requestedLine(credential) {
58
- return `Requested at ${credential.requestedAt}${credential.expiresAt ? `; the request lapses at ${credential.expiresAt}` : ""}.`;
59
- }
60
- function tailscaleStatus() {
61
- for (const binary of ["tailscale", MAC_APP_TAILSCALE]) {
62
- const output = getExecutor().runFileQuiet(binary, ["status", "--json"], { timeoutMs: 5e3 });
63
- if (output === null) continue;
64
- try {
65
- const status = JSON.parse(output);
66
- return isJsonObject(status) && status.BackendState === "Running" ? status : null;
67
- } catch {
68
- return null;
69
- }
70
- }
71
- return null;
72
- }
73
- function hello({ url, servername, host }, auth) {
74
- return new Promise((resolve) => {
75
- const socket = new WebSocket(url, {
76
- handshakeTimeout: HELLO_TIMEOUT_MS,
77
- servername,
78
- headers: { Host: host }
79
- });
80
- const done = (reply) => {
81
- clearTimeout(timer);
82
- socket.removeAllListeners();
83
- socket.on("error", () => {});
84
- socket.close();
85
- resolve(reply);
86
- };
87
- const timer = setTimeout(() => done({ failed: "no reply in time" }), HELLO_TIMEOUT_MS);
88
- socket.on("open", () => {
89
- socket.send(JSON.stringify({
90
- id: 1,
91
- method: "hello",
92
- params: {
93
- protocol: 1,
94
- client: {
95
- name: "stim",
96
- version: "1"
97
- },
98
- auth
99
- }
100
- }));
101
- });
102
- socket.on("message", (data) => {
103
- let message;
104
- try {
105
- message = JSON.parse(String(data));
106
- } catch {
107
- message = null;
108
- }
109
- if (isJsonObject(message) && ("result" in message || "error" in message)) return done(message);
110
- done({ failed: "the reply was not a hello result" });
111
- });
112
- socket.on("error", (error) => done({ failed: error.message }));
113
- socket.on("close", () => done({ failed: "the connection closed before a reply" }));
114
- });
115
- }
116
- const realIo = {
117
- status: tailscaleStatus,
118
- hello
119
- };
120
- function pinnedEndpoint$1(credential, status = tailscaleStatus) {
121
- return pinnedEndpoint(credential, parseMachine(credential.machine) ? status() : null);
122
- }
123
- //#endregion
124
34
  //#region src/device-host/machines.ts
125
35
  function note$1(title, detail, fix = null) {
126
36
  return {
@@ -766,159 +676,6 @@ function reportRemoteFailure(event, fields) {
766
676
  } catch {}
767
677
  }
768
678
  //#endregion
769
- //#region src/engine/native-run.ts
770
- const NATIVE_RUN_LOCK = "native-run";
771
- const NATIVE_RUN_WAIT_MS = 18e5;
772
- const WAIT_HEARTBEAT_MS = 3e4;
773
- const CANCEL_REQUEST_KEY = "nativeRunCancel";
774
- function nativeRunHolder(holder) {
775
- const { command, platform, slot } = holder.details;
776
- return {
777
- command: typeof command === "string" ? command : null,
778
- platform: typeof platform === "string" ? platform : null,
779
- slot: typeof slot === "string" ? slot : "default"
780
- };
781
- }
782
- function describeNativeRunHolder(holder, now) {
783
- const { command, slot } = nativeRunHolder(holder);
784
- const started = Date.parse(holder.startedAt);
785
- const facts = [`pid ${holder.owner.pid}`];
786
- if (Number.isFinite(started)) facts.push(`running for ${formatElapsed(now - started)}`);
787
- if (command === null) return `another Stim run (${facts.join(", ")})`;
788
- return `\`stim ${command}${slot === "default" ? "" : ` --slot ${slot}`}\` (${facts.join(", ")})`;
789
- }
790
- /**
791
- * Report a wait on the native-run lock: one line as soon as a holder is seen, a line for each new
792
- * holder, and a heartbeat every WAIT_HEARTBEAT_MS while the same holder keeps it.
793
- */
794
- function nativeRunWaitNotice({ write, now = Date.now, describe = describeNativeRunHolder }) {
795
- let claimId = null;
796
- let lastAt = 0;
797
- return (holder, firstLine) => {
798
- const at = now();
799
- if (holder.claimId !== claimId) {
800
- claimId = holder.claimId;
801
- lastAt = at;
802
- write(firstLine ?? `waiting for ${describe(holder, at)} in this workspace to finish`);
803
- return;
804
- }
805
- if (at - lastAt < WAIT_HEARTBEAT_MS) return;
806
- lastAt = at;
807
- write(`still waiting for ${describe(holder, at)}`);
808
- };
809
- }
810
- /**
811
- * What `stop` does about the run holding the native-run lock. A build is interrupted only when the stop
812
- * leaves it nothing to deploy to: the whole workspace stops, the build targets the stopped slot, or the
813
- * stopped slot is the workspace's only device. A build for another slot that stays is left running and
814
- * the stop proceeds without the lock. Only an owner whose identity is proven live is signalled.
815
- */
816
- function decideStopAction({ stopSlot, holder, ownerIdentity, deviceSlots }) {
817
- if (holder.command === "macos") {
818
- if (stopSlot !== void 0) return { action: "proceed" };
819
- return ownerIdentity === "same" ? { action: "interrupt" } : { action: "refuse" };
820
- }
821
- if (holder.command !== "ios" && holder.command !== "android") return { action: "wait" };
822
- if (!(stopSlot === void 0 || holder.slot === stopSlot || deviceSlots.includes(stopSlot) && deviceSlots.every((slot) => slot === stopSlot))) return { action: "proceed" };
823
- return ownerIdentity === "same" ? { action: "interrupt" } : { action: "refuse" };
824
- }
825
- function requestNativeRunCancel(root, claimId, pid = process.pid) {
826
- updateWorkspaceState(root, (state) => ({
827
- ...state,
828
- [CANCEL_REQUEST_KEY]: {
829
- claimId,
830
- pid,
831
- at: (/* @__PURE__ */ new Date()).toISOString()
832
- }
833
- }));
834
- }
835
- function cancelRequestFor(root, claimId) {
836
- const request = readWorkspaceState$1(root)?.[CANCEL_REQUEST_KEY];
837
- if (request?.claimId !== claimId || typeof request.pid !== "number") return null;
838
- return { pid: request.pid };
839
- }
840
- function clearNativeRunCancel(root, claimId) {
841
- try {
842
- clearWorkspaceStateKey(root, CANCEL_REQUEST_KEY, (value) => value?.claimId === claimId);
843
- } catch {}
844
- }
845
- let cancelled = null;
846
- let cancellationSignal;
847
- /** The active native run aborts this signal when it is cancelled. */
848
- function runCancellationSignal() {
849
- return cancellationSignal;
850
- }
851
- /** Why this process's native run was cancelled, or null while it was not. */
852
- function runCancellation() {
853
- return cancelled;
854
- }
855
- function cancelledFailure(platform, { code, message }) {
856
- if (cancelled === null) return null;
857
- return {
858
- code: "STIM_CANCELLED",
859
- message: `The ${platform} run was ${cancelled} before it finished.`,
860
- remedy: `Run \`stim ${platform}\` again when you want the app on a device.`,
861
- lines: [`it stopped at ${code ?? "an unfinished step"}${message ? `: ${message}` : ""}`]
862
- };
863
- }
864
- function cancelOnInterrupt({ root, claim, write, exit, controller }) {
865
- let signals = 0;
866
- const onInterrupt = () => {
867
- signals += 1;
868
- if (signals > 1) {
869
- exit(130);
870
- return;
871
- }
872
- const request = cancelRequestFor(root, claim.claimId);
873
- cancelled = request ? `cancelled by \`stim stop\` (pid ${request.pid})` : "cancelled by an interrupt";
874
- controller.abort();
875
- refuseDeclaredSpawns(`the run was ${cancelled}`);
876
- const signalled = signalDeclaredSpawns("SIGINT");
877
- write(signalled > 0 ? `${cancelled}; stopping the running build tool` : request ? `${cancelled}; stopping before the next build tool or install` : cancelled);
878
- if (signalled === 0 && !request) exit(130);
879
- };
880
- process.on("SIGINT", onInterrupt);
881
- return () => {
882
- process.off("SIGINT", onInterrupt);
883
- allowDeclaredSpawns();
884
- clearNativeRunCancel(root, claim.claimId);
885
- };
886
- }
887
- /**
888
- * Run an `ios`, `android` or `macos` invocation under the workspace's native-run lock: the claim records the
889
- * command, platform and slot, and a wait for another holder is reported on `write`. The first SIGINT
890
- * forwards the interrupt to the running declared build tool; a run `stim stop` cancelled between build
891
- * tools starts no further one; a terminal interrupt with no build tool running, or a second SIGINT, exits
892
- * with 130 at once.
893
- */
894
- function withNativeBuildRun(root, details, fn, { write, exit = (code) => process.exit(code) }) {
895
- const notice = nativeRunWaitNotice({ write });
896
- return withWorkspaceProcessLock(workspaceDir(root), NATIVE_RUN_LOCK, async (claim) => {
897
- cancelled = null;
898
- const controller = new AbortController();
899
- cancellationSignal = controller.signal;
900
- const dispose = cancelOnInterrupt({
901
- root,
902
- claim,
903
- write,
904
- exit,
905
- controller
906
- });
907
- try {
908
- return await fn(claim);
909
- } finally {
910
- dispose();
911
- cancellationSignal = void 0;
912
- }
913
- }, {
914
- external: true,
915
- waitMs: NATIVE_RUN_WAIT_MS,
916
- declareSpawns: true,
917
- details: { ...details },
918
- onHeld: (holder) => notice(holder)
919
- });
920
- }
921
- //#endregion
922
679
  //#region src/offload/client.ts
923
680
  const CONNECT_TIMEOUT_MS = 1e4;
924
681
  const OFFER_TIMEOUT_MS = 2e4;
@@ -1402,61 +1159,37 @@ function repoIdentity(projectRoot) {
1402
1159
  lockfile: lock ? createHash("sha256").update(readFileSync(join(repoRoot, lock))).digest("hex") : null
1403
1160
  };
1404
1161
  }
1405
- /** The exact source a build needs: tracked and untracked, not ignored, as `git ls-files -co --exclude-standard`. */
1406
- function sourceManifest(repoRoot) {
1407
- const listed = getExecutor().runFile("git", [
1408
- "-C",
1409
- repoRoot,
1410
- "ls-files",
1411
- "-z",
1412
- "-co",
1413
- "--exclude-standard"
1414
- ], {
1415
- untrimmed: true,
1416
- timeoutMs: 12e4
1417
- });
1418
- const files = [];
1419
- for (const path of new Set(listed.split("\0").filter(Boolean))) {
1420
- const absolute = join(repoRoot, path);
1421
- let stat;
1422
- try {
1423
- stat = lstatSync(absolute);
1424
- } catch {
1425
- continue;
1426
- }
1427
- if (stat.isSymbolicLink()) {
1428
- const target = Buffer.from(readlinkSync(absolute));
1429
- files.push({
1430
- path,
1431
- kind: "link",
1432
- size: target.length,
1433
- sha256: sha256(target)
1434
- });
1435
- } else if (stat.isFile()) {
1436
- const content = readFileSync(absolute);
1437
- files.push({
1438
- path,
1439
- kind: stat.mode & 73 ? "exec" : "file",
1440
- size: content.length,
1441
- sha256: sha256(content)
1442
- });
1443
- }
1162
+ const sha256 = (content) => createHash("sha256").update(content).digest("hex");
1163
+ /** Why the native inputs cannot be sent to a remote Mac, such as an ignored file among them, or null when they can. */
1164
+ function nativeTransferRefusal(repoRoot, snapshot) {
1165
+ try {
1166
+ checkNativeTransferMembership(snapshot, gitVisiblePaths(repoRoot));
1167
+ return null;
1168
+ } catch (error) {
1169
+ return error.message.split("\n")[0];
1444
1170
  }
1445
- return files;
1446
1171
  }
1447
- const sha256 = (content) => createHash("sha256").update(content).digest("hex");
1448
1172
  function blobContent(repoRoot, file) {
1449
1173
  const absolute = join(repoRoot, file.path);
1450
- return file.kind === "link" ? Buffer.from(readlinkSync(absolute)) : readFileSync(absolute);
1174
+ return file.kind === "directory" ? Buffer.alloc(0) : file.kind === "link" ? Buffer.from(readlinkSync(absolute)) : readFileSync(absolute);
1451
1175
  }
1452
1176
  /** Closes every connection the choice still holds. */
1453
1177
  function closeOffload(choice) {
1454
1178
  choice.connection.close();
1455
1179
  for (const each of choice.runnersUp.splice(0)) each.connection.close();
1456
1180
  }
1181
+ const NATIVE_BUILD_FEATURE = {
1182
+ xcode: "native-xcode-build",
1183
+ gradle: "native-gradle-build"
1184
+ };
1185
+ const unsupportedNative = (provider) => `This worker does not support native ${provider === "xcode" ? "Xcode" : "Gradle"} builds.`;
1186
+ const ANDROID_PACKAGE = /^[A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)+$/;
1187
+ function nativeTransferInput(native) {
1188
+ return native?.provider === "xcode" ? native.snapshot : native?.transfer;
1189
+ }
1457
1190
  /** Connects to one paired machine and asks it for an offer; the caller closes the connection it returns. */
1458
1191
  async function probeMachine(credential, identity, { connectMs = CONNECT_TIMEOUT_MS, offerMs = OFFER_TIMEOUT_MS } = {}) {
1459
- const target = pinnedEndpoint$1(credential);
1192
+ const target = await pinnedEndpoint$1(credential);
1460
1193
  if (typeof target === "string") return {
1461
1194
  credential,
1462
1195
  failure: target
@@ -1466,10 +1199,18 @@ async function probeMachine(credential, identity, { connectMs = CONNECT_TIMEOUT_
1466
1199
  credential,
1467
1200
  failure: connection.failure
1468
1201
  };
1202
+ if (identity.native && !connection.supports(NATIVE_BUILD_FEATURE[identity.native])) {
1203
+ connection.close();
1204
+ return {
1205
+ credential,
1206
+ failure: unsupportedNative(identity.native)
1207
+ };
1208
+ }
1209
+ const native = identity.native === "gradle" || identity.native && connection.supports("native-xcode-toolchain") ? identity.native : void 0;
1469
1210
  const reply = await connection.request("build.offer", {
1470
1211
  repo: identity.repo,
1471
1212
  ...identity.lockfile ? { lockfile: identity.lockfile } : {},
1472
- ...identity.rubyVersion ? { rubyVersion: identity.rubyVersion } : {}
1213
+ ...native ? { native } : identity.rubyVersion ? { rubyVersion: identity.rubyVersion } : {}
1473
1214
  }, offerMs);
1474
1215
  const failure = replyError(reply);
1475
1216
  if (failure || !("result" in reply)) {
@@ -1502,7 +1243,8 @@ async function chooseBuildMachine({ projectRoot, target, mode, here, note, machi
1502
1243
  const rubyVersion = readRubyVersion(projectRoot) ?? void 0;
1503
1244
  const asked = await Promise.all(machines.map((credential) => probeMachine(credential, {
1504
1245
  ...identity,
1505
- rubyVersion
1246
+ rubyVersion,
1247
+ ...target.platform !== "macos" ? { native: target.native } : {}
1506
1248
  })));
1507
1249
  const { order, reasons, candidates } = pickOffer({
1508
1250
  localEnabled: selected !== "auto" || automaticMachineEnabled("build", "local"),
@@ -1540,6 +1282,10 @@ async function chooseBuildMachine({ projectRoot, target, mode, here, note, machi
1540
1282
  automatic: selected === "auto"
1541
1283
  };
1542
1284
  }
1285
+ function validArtifactIdentity(request, fingerprint) {
1286
+ if (request.platform === "macos") return true;
1287
+ return request.native?.provider === "gradle" ? fingerprint === null : typeof fingerprint === "string" && fingerprint.length > 0;
1288
+ }
1543
1289
  const ARTIFACT_NAME = {
1544
1290
  ios: /^[^/]+\.app$/,
1545
1291
  android: /^[^/]+\.apk$/,
@@ -1550,12 +1296,12 @@ const ARTIFACT_NAME = {
1550
1296
  * `RESUME_WINDOW_MS`, shorter than the machine's grace for a dropped connection, has passed. Returns why it
1551
1297
  * could not.
1552
1298
  */
1553
- async function resumeJob(credential, job, abandoned, signal) {
1299
+ async function resumeJob(credential, job, abandoned, signal, native) {
1554
1300
  const deadline = Date.now() + RESUME_WINDOW_MS;
1555
1301
  let delay = RESUME_DELAY_MS;
1556
1302
  let last = "no attempt";
1557
1303
  while (!abandoned()) {
1558
- const target = pinnedEndpoint$1(credential);
1304
+ const target = await pinnedEndpoint$1(credential);
1559
1305
  const connection = typeof target === "string" ? {
1560
1306
  failure: target,
1561
1307
  refused: false
@@ -1568,6 +1314,10 @@ async function resumeJob(credential, job, abandoned, signal) {
1568
1314
  connection.close();
1569
1315
  break;
1570
1316
  }
1317
+ if (native && !connection.supports(NATIVE_BUILD_FEATURE[native])) {
1318
+ connection.close();
1319
+ return unsupportedNative(native);
1320
+ }
1571
1321
  const early = [];
1572
1322
  connection.onProgress((event) => early.push(event));
1573
1323
  const cancel = () => connection.close();
@@ -1600,10 +1350,11 @@ async function resumeJob(credential, job, abandoned, signal) {
1600
1350
  return `no connection to it within ${RESUME_WINDOW_MS / 6e4} min (${last})`;
1601
1351
  }
1602
1352
  /** Mirrors the checkout on the machine: the manifest in pages, then every blob it lacks. */
1603
- async function syncSource(connection, identity, onEnter) {
1353
+ async function syncSource(connection, identity, onEnter, native) {
1604
1354
  onEnter("sync");
1605
1355
  const syncStarted = Date.now();
1606
- const manifest = sourceManifest(identity.repoRoot);
1356
+ const manifest = native && "declaration" in native ? nativeGradleTransfer(join(identity.repoRoot, identity.project), native.declaration).files : native ? nativeTransferManifest(identity.repoRoot, native, sourceManifest(identity.repoRoot)) : sourceManifest(identity.repoRoot);
1357
+ if (native && "declaration" in native && manifestDigest(manifest) !== native.digest) return { failure: "The native Gradle source changed before upload." };
1607
1358
  const bySha = new Map(manifest.map((file) => [file.sha256, file]));
1608
1359
  const missing = [];
1609
1360
  for (let index = 0; index < manifest.length || index === 0;) {
@@ -1647,12 +1398,20 @@ async function syncSource(connection, identity, onEnter) {
1647
1398
  digest: manifestDigest(manifest)
1648
1399
  };
1649
1400
  }
1401
+ function stagedArtifactEscapes(stagingDir, artifactPath, hasContents) {
1402
+ if ((hasContents ? [artifactPath, join(artifactPath, "Contents")] : [artifactPath]).some((path) => lstatSync(path, { throwIfNoEntry: false })?.isSymbolicLink())) return true;
1403
+ if (!existsSync(artifactPath)) return false;
1404
+ const inside = relative(realpathSync(stagingDir), realpathSync(artifactPath));
1405
+ return inside === ".." || inside.startsWith(`..${sep}`) || isAbsolute(inside);
1406
+ }
1650
1407
  /**
1651
1408
  * Builds on the chosen machine and brings the `.app` or `.apk` back into `stagingDir`, verified against the
1652
1409
  * sha256 the machine reports. The caller re-fingerprints and stores it.
1653
1410
  */
1654
1411
  async function offloadBuild({ choice, expectedFingerprint, request, stagingDir, onPhase, onEnter, onRecord, note }) {
1655
1412
  const { identity } = choice;
1413
+ const native = request.platform !== "macos" ? request.native : void 0;
1414
+ let sourceDigest = null;
1656
1415
  const started = Date.now();
1657
1416
  const fail = (reason) => {
1658
1417
  closeOffload(choice);
@@ -1684,6 +1443,8 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
1684
1443
  signal?.addEventListener("abort", cancel, { once: true });
1685
1444
  try {
1686
1445
  throwIfCancelled();
1446
+ if (!validArtifactIdentity(request, expectedFingerprint)) return fail("The build request has an invalid reusable artifact identity.");
1447
+ if (native?.provider === "xcode" && native.snapshot.hash !== expectedFingerprint) return fail("The native source snapshot does not match the requested artifact identity.");
1687
1448
  let job = null;
1688
1449
  let early = [];
1689
1450
  let resuming = false;
@@ -1691,7 +1452,7 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
1691
1452
  if (resuming || settled) return;
1692
1453
  resuming = true;
1693
1454
  note(`offload: the connection to ${choice.machine} dropped (${why}); reattaching to the build there`);
1694
- const resumed = await resumeJob(choice.credential, job, () => settled, signal);
1455
+ const resumed = await resumeJob(choice.credential, job, () => settled, signal, native?.provider);
1695
1456
  resuming = false;
1696
1457
  if (settled) {
1697
1458
  if (typeof resumed !== "string") resumed.connection.close();
@@ -1739,13 +1500,19 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
1739
1500
  if (moveOn(reason)) continue;
1740
1501
  return fail(reason);
1741
1502
  }
1742
- const synced = await syncSource(choice.connection, identity, onEnter);
1503
+ if (native && !choice.connection.supports(NATIVE_BUILD_FEATURE[native.provider])) {
1504
+ const reason = unsupportedNative(native.provider);
1505
+ if (moveOn(reason)) continue;
1506
+ return fail(reason);
1507
+ }
1508
+ const synced = await syncSource(choice.connection, identity, onEnter, nativeTransferInput(native));
1743
1509
  throwIfCancelled();
1744
1510
  if ("failure" in synced) {
1745
1511
  if (moveOn(synced.failure)) continue;
1746
1512
  return fail(synced.failure);
1747
1513
  }
1748
1514
  ({syncMs, uploadedBytes} = synced);
1515
+ sourceDigest = synced.digest;
1749
1516
  onPhase("sync", `${synced.files} files, uploaded ${synced.uploaded} (${mb(uploadedBytes)}) in ${seconds(syncMs)}`);
1750
1517
  workerStarted = Date.now();
1751
1518
  early = [];
@@ -1757,6 +1524,15 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
1757
1524
  project: identity.project,
1758
1525
  platform: request.platform,
1759
1526
  fingerprint: request.platform === "macos" ? synced.digest : expectedFingerprint,
1527
+ ...native ? { native: {
1528
+ provider: native.provider,
1529
+ sourceDigest: synced.digest,
1530
+ ...native.provider === "xcode" ? {
1531
+ cacheKey: native.cacheKey,
1532
+ arch: native.arch,
1533
+ parameters: native.snapshot.parameters
1534
+ } : { inputs: native.transfer.declaration }
1535
+ } } : {},
1760
1536
  ...request.platform === "macos" ? { macos: {
1761
1537
  product: request.product,
1762
1538
  infoPlist: request.infoPlist,
@@ -1766,6 +1542,7 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
1766
1542
  } } : {
1767
1543
  configuration: request.platform === "ios" ? request.configuration : null,
1768
1544
  scheme: request.platform === "ios" ? request.scheme : null,
1545
+ iosProjectPath: request.platform === "ios" ? request.iosProjectPath ?? null : null,
1769
1546
  runtime: request.platform === "ios" ? request.runtime : null,
1770
1547
  packageName: packageName(join(identity.repoRoot, identity.project)),
1771
1548
  isExpo: request.isExpo,
@@ -1797,6 +1574,8 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
1797
1574
  connection.onProgress(null);
1798
1575
  const workerMs = Date.now() - workerStarted;
1799
1576
  if (result.ok !== true) return fail(`${String(result.code ?? "failed")}: ${String(result.message ?? "")}`);
1577
+ if (native && (result.sourceDigest !== sourceDigest || result.fingerprint !== expectedFingerprint)) return fail("The worker returned a different native source or artifact identity.");
1578
+ if (native?.provider === "gradle" && !ANDROID_PACKAGE.test(String(result.androidPackage ?? ""))) return fail("The worker returned no verified native APK package.");
1800
1579
  const artifact = result.artifact;
1801
1580
  const name = typeof artifact?.name === "string" ? artifact.name : "";
1802
1581
  if (!ARTIFACT_NAME[request.platform].test(name) || typeof artifact.sha256 !== "string") return fail(`the machine reported no .${request.platform === "android" ? "apk" : "app"} artifact`);
@@ -1847,6 +1626,7 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
1847
1626
  throwIfCancelled();
1848
1627
  rmSync(archive, { force: true });
1849
1628
  const artifactPath = join(stagingDir, name);
1629
+ if (stagedArtifactEscapes(stagingDir, artifactPath, request.platform === "macos")) return fail(`fetch: ${machine} returned ${name} as or through a symbolic link or outside the staging directory`);
1850
1630
  if (request.platform === "ios" && !existsSync(join(artifactPath, "Info.plist"))) return fail(`fetch: ${name} has no Info.plist`);
1851
1631
  if (request.platform === "android" && !(lstatSync(artifactPath, { throwIfNoEntry: false })?.isFile() ?? false)) return fail(`fetch: ${name} is not a file`);
1852
1632
  if (request.platform === "macos") {
@@ -1868,6 +1648,7 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
1868
1648
  sha256: digest
1869
1649
  } } : {},
1870
1650
  compilationCache: request.platform === "ios" ? compilationActivity(result.compilationCache) : COMPILATION_CACHE_UNAVAILABLE,
1651
+ ...native?.provider === "gradle" ? { androidPackage: result.androidPackage } : {},
1871
1652
  ccache: request.platform === "android" ? ccacheActivity(result.compilationCache) : CCACHE_UNAVAILABLE,
1872
1653
  timings: {
1873
1654
  offerMs: choice.offerMs,
@@ -1979,9 +1760,10 @@ function offloadCheck(projectRoot, targets) {
1979
1760
  };
1980
1761
  probe.connection.close();
1981
1762
  resolved ??= targets();
1763
+ const resolvedTargets = await resolved;
1982
1764
  const problems = [];
1983
- for (const target of resolved) for (const problem of offerProblems(probe.offer, target)) {
1984
- const labeled = resolved.length > 1 && !SHARED_PROBLEMS.has(problem.code) ? {
1765
+ for (const target of resolvedTargets) for (const problem of offerProblems(probe.offer, target)) {
1766
+ const labeled = resolvedTargets.length > 1 && !SHARED_PROBLEMS.has(problem.code) ? {
1985
1767
  ...problem,
1986
1768
  reason: `${PLATFORM_LABEL[target.platform]}: ${problem.reason}`
1987
1769
  } : problem;
@@ -1994,4 +1776,4 @@ function offloadCheck(projectRoot, targets) {
1994
1776
  };
1995
1777
  }
1996
1778
  //#endregion
1997
- export { resolveBuildPlacement as A, setRemoteLogSink as C, parseBuildMachineOption as D, namedBuildMachine as E, parseMachine$1 as F, pinnedEndpoint$1 as I, realIo as L, configuredMachines as M, hostingMachines as N, requireLocalBuild as O, inspectDeviceHostMachines as P, withNativeBuildRun as S, buildPlacementSettings as T, nativeRunHolder as _, offloadBuild as a, runCancellation as b, placementLoad as c, NATIVE_RUN_LOCK as d, NATIVE_RUN_WAIT_MS as f, describeNativeRunHolder as g, decideStopAction as h, closeOffload as i, inspectBuildMachines as j, resolveBuildMachine as k, remotePhaseText as l, clearNativeRunCancel as m, buildPlacementCandidates as n, offloadCheck as o, cancelledFailure as p, chooseBuildMachine as r, offloadPlacement as s, BuildConnection as t, simulatorRuntime as u, nativeRunWaitNotice as v, OffloadRefusal as w, runCancellationSignal as x, requestNativeRunCancel as y };
1779
+ export { inspectDeviceHostMachines as C, hostingMachines as S, requireLocalBuild as _, nativeTransferRefusal as a, inspectBuildMachines as b, offloadPlacement as c, simulatorRuntime as d, setRemoteLogSink as f, parseBuildMachineOption as g, namedBuildMachine as h, closeOffload as i, placementLoad as l, buildPlacementSettings as m, buildPlacementCandidates as n, offloadBuild as o, OffloadRefusal as p, chooseBuildMachine as r, offloadCheck as s, BuildConnection as t, remotePhaseText as u, resolveBuildMachine as v, configuredMachines as x, resolveBuildPlacement as y };
@@ -18,6 +18,10 @@ interface ExecOptions {
18
18
  * `runFile` and `runFileAsync`: reject nonempty stderr even when the command exits successfully.
19
19
  */
20
20
  rejectStderr?: boolean;
21
+ /** `runFile` and `runFileAsync`: run in its own session with all stdio ignored and return ''; the child reports through a file, so a background process it leaves behind cannot hold a pipe open. */
22
+ detachedSilent?: boolean;
23
+ /** `runFile` and `runFileAsync`: exact values replaced with `***` in every string field of a thrown error, including message, stack, stdout, stderr and output. */
24
+ redact?: readonly string[];
21
25
  }
22
26
  interface Executor {
23
27
  run(cmd: string, opts?: ExecOptions): string;