stim 1.18.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 (178) hide show
  1. package/README.md +21 -4
  2. package/dist/{activity-BxnkDZDi.mjs → activity-BrGh9EAI.mjs} +3 -3
  3. package/dist/{agent-device-usage-output-CZ66YA4T.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-DC3ZLuw0.mjs → android-BsYNR81W.mjs} +103 -45
  7. package/dist/{android-CBwCNicO.mjs → android-CK3fpEyZ.mjs} +2 -2
  8. package/dist/{android-cas-BBFyr9CA.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-D0VtFhqk.mjs → api-zuX3ynYN.mjs} +9 -7
  12. package/dist/api.d.mts +104 -4
  13. package/dist/api.mjs +1 -1
  14. package/dist/{app-install-D-PUENug.mjs → app-install-0LgaQRT7.mjs} +39 -18
  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/{web-DTU0uiWj.mjs → browser-web-CzBmhmgs.mjs} +136 -174
  19. package/dist/{budget-DIhL2BKx.mjs → budget-BONq9vXY.mjs} +3096 -3244
  20. package/dist/build-BixRI6sB.mjs +462 -0
  21. package/dist/build-plan-CP0tckNU.mjs +553 -0
  22. package/dist/{cache-manifest-BI7ozfmH.mjs → cache-manifest-qA3dT2x2.mjs} +1 -1
  23. package/dist/cache-manifest.mjs +1 -1
  24. package/dist/ccache-CjHQ5F8g.mjs +142 -0
  25. package/dist/{chrome-DJnmnS5q.mjs → chrome-Dp2lMLBe.mjs} +1 -1
  26. package/dist/{cli-DNJMwADx.mjs → cli-B2R8wH1F.mjs} +20 -20
  27. package/dist/cli.mjs +1 -1
  28. package/dist/{client-DzhapoMB.mjs → client-BLy1Pw2V.mjs} +172 -165
  29. package/dist/collector-run.d.mts +10 -0
  30. package/dist/collector-run.mjs +7 -7
  31. package/dist/{command-output-CBtzyIgF.mjs → command-output-BjPN99ZB.mjs} +2 -2
  32. package/dist/{config-CgYMahz7.mjs → config-D0LhTY3S.mjs} +13 -13
  33. package/dist/{created-devices-DKbeWsiK.mjs → created-devices-HU_DAb_Q.mjs} +7 -6
  34. package/dist/{dependency-state-xKoOypBm.mjs → dependency-state-B2m8eqsS.mjs} +2 -2
  35. package/dist/{detached-entry-BLfcgc4O.mjs → detached-entry-BXq4fIl-.mjs} +1 -1
  36. package/dist/{dev-client-CLnIH02x.mjs → dev-client-ClATaJoH.mjs} +2 -2
  37. package/dist/{device-D7oCHGWi.mjs → device-B7lsC0K1.mjs} +8 -8
  38. package/dist/device-DVCwAFlX.mjs +727 -0
  39. package/dist/{device-capacity-EUOOYr8T.mjs → device-capacity-Ca_ke1LE.mjs} +25 -15
  40. package/dist/device-host-worker.mjs +38 -18
  41. package/dist/{device-ios-6MewKKnc.mjs → device-ios-CRUqPxKw.mjs} +12 -11
  42. package/dist/{device-lease-Co-EwifR.mjs → device-lease-DHOBXGgS.mjs} +135 -22
  43. package/dist/{device-lease-run-w1xhCqIW.mjs → device-lease-run-CvDeHTkG.mjs} +4 -4
  44. package/dist/{device-pool-F5WvRPtD.mjs → device-pool-L2-5GxMy.mjs} +3 -3
  45. package/dist/{device-remote-tHbd1fKy.mjs → device-remote-Bmg42063.mjs} +17 -69
  46. package/dist/doctor-C2MSuMMg.mjs +515 -0
  47. package/dist/{doctor-B9ckYUoU.mjs → doctor-DPAqhVCK.mjs} +334 -721
  48. package/dist/doctor-watchman-CB4NnVZm.mjs +260 -0
  49. package/dist/{simslim-EHBQ2R4G.mjs → eas-build-Dw_ec2Km.mjs} +6 -152
  50. package/dist/{stim-installations-aR_WsNOA.mjs → eas-session-ledger-DZlC-Fq3.mjs} +57 -4
  51. package/dist/{error-diagnostics-BRXOqDzG.mjs → error-diagnostics-Du7I_GuY.mjs} +17 -12
  52. package/dist/errors-DzkMYh8Q.mjs +205 -0
  53. package/dist/{exec-DWhCZDGm.mjs → exec-B81Qodjn.mjs} +69 -15
  54. package/dist/{gc-Dh6mXOKC.mjs → gc-PWp34zGb.mjs} +23 -22
  55. package/dist/{gradle-C6GKnunQ.mjs → gradle-C2OEKCCI.mjs} +50 -125
  56. package/dist/{guide-tm6igAaa.mjs → guide-Dai9lphv.mjs} +862 -538
  57. package/dist/{hosted-android-Bv285t_t.mjs → hosted-android-DQ-Agsd8.mjs} +5 -5
  58. package/dist/{hosted-client-C-rMtppF.mjs → hosted-client-Io01s5By.mjs} +9 -3
  59. package/dist/{hosted-ios-D5mNmtj4.mjs → hosted-ios-DIxFnYp-.mjs} +3 -3
  60. package/dist/{hosted-logs-DN8Iz4mJ.mjs → hosted-logs-DP-ABU_M.mjs} +8 -8
  61. package/dist/{hosted-macos-B-wEjlwG.mjs → hosted-macos-Aziw9T_9.mjs} +7 -6
  62. package/dist/{hosted-native-DeeOG8xZ.mjs → hosted-native-BdAQi21M.mjs} +23 -16
  63. package/dist/{idle-shutdown-jjtxAn5C.mjs → idle-shutdown-DdMqoSUB.mjs} +7 -7
  64. package/dist/ios-BZ9dg2Fr.mjs +9 -0
  65. package/dist/{ios-B3SbOWdw.mjs → ios-CXLIUxXJ.mjs} +180 -24
  66. package/dist/ios-CZuv6gRv.mjs +1112 -0
  67. package/dist/{ios-Bvbf0SGY.mjs → ios-VGvNdsv2.mjs} +1 -1
  68. package/dist/{ios-device-Bn7jWDB1.mjs → ios-device-B1B14xND.mjs} +1 -1
  69. package/dist/{ios-device-vOaByMhw.mjs → ios-device-BreZOpM0.mjs} +3 -3
  70. package/dist/ios-project-B9drt_aY.mjs +197 -0
  71. package/dist/{ios-state-B2pES6Vs.mjs → ios-state-CjrL2qv-.mjs} +1 -1
  72. package/dist/js-swap-C8icNYIY.mjs +432 -0
  73. package/dist/launch-DxH0THLP.mjs +745 -0
  74. package/dist/launch-YrbqaIFj.mjs +1530 -0
  75. package/dist/{launch-verify-hskNcWkZ.mjs → launch-verify-BzRdbuO1.mjs} +26 -7
  76. package/dist/{logs-1Ylc1Ri6.mjs → logs-DHaRNfg7.mjs} +11 -11
  77. package/dist/macos-C0iljhsN.mjs +2 -0
  78. package/dist/{macos-DF6C7wUU.mjs → macos-V_3Ui7g4.mjs} +125 -155
  79. package/dist/macos-run.mjs +1 -1
  80. package/dist/maintenance-run.mjs +13 -10
  81. package/dist/{metro-Dh8T4Ytd.mjs → metro-D_j9sN-B.mjs} +209 -43
  82. package/dist/{metro-gateway-CEUw9RMH.mjs → metro-gateway-BBJldOdi.mjs} +2 -2
  83. package/dist/miss-reason-BPAzZS0r.mjs +238 -0
  84. package/dist/{named-ports-BFIz1Shz.mjs → named-ports-C2lvoPsb.mjs} +12 -5
  85. package/dist/native-android-lHzaew7r.mjs +226 -0
  86. package/dist/native-gradle-inputs-BQnIdAlL.mjs +356 -0
  87. package/dist/{native-runtime-D_603JiC.mjs → native-runtime-BRmWL7ru.mjs} +9 -8
  88. package/dist/native-xcode-inputs-eutS19jU.mjs +307 -0
  89. package/dist/native-xcode-ios-DP3cHLwu.mjs +434 -0
  90. package/dist/{ndjson-LGZAMsqa.mjs → ndjson-GOnbyvso.mjs} +6 -6
  91. package/dist/offload-worker.d.mts +12 -6
  92. package/dist/offload-worker.mjs +409 -73
  93. package/dist/{ownership-D6Se7gg_.mjs → ownership-DXzMMSx6.mjs} +2 -2
  94. package/dist/{placement-log-BcPhRwR4.mjs → placement-log-reFbjqEr.mjs} +7 -3
  95. package/dist/plan-BDk2AeY_.mjs +260 -0
  96. package/dist/plan-placement-D_YYlE6N.mjs +73 -0
  97. package/dist/{ports-BLTtfBq-.mjs → ports-jzvAJXXL.mjs} +3 -3
  98. package/dist/{prebuild-juCyreaR.mjs → prebuild-Bw44QxVh.mjs} +4 -4
  99. package/dist/{preview-D5V07Nrl.mjs → preview-BaBJ72Al.mjs} +51 -34
  100. package/dist/project-B8_duAzW.mjs +1067 -0
  101. package/dist/project-plan-BPwbLwQu.mjs +10 -0
  102. package/dist/{pull-request-u4vp7zmK.mjs → pull-request-vOhLNmIb.mjs} +1 -1
  103. package/dist/pull-requests.mjs +1 -1
  104. package/dist/react-native-android-chm1DMlA.mjs +921 -0
  105. package/dist/react-native-build-oAw6gQeW.mjs +239 -0
  106. package/dist/react-native-doctor-DYXoh-6I.mjs +78 -0
  107. package/dist/react-native-ios-SCTK_CT5.mjs +595 -0
  108. package/dist/{recordings-BAWltsVt.mjs → recordings-Bk-VivUV.mjs} +1 -1
  109. package/dist/{reload-DvyWzVjJ.mjs → reload-D34GkY78.mjs} +17 -14
  110. package/dist/remote-BU9UyhCJ.mjs +192 -0
  111. package/dist/{remote-cache-BGg7aGIZ.mjs → remote-cache-Bsts6f6Y.mjs} +5 -5
  112. package/dist/result-BDoRk1k3.mjs +186 -0
  113. package/dist/{run-vhtDgpHO.mjs → run-qvff78hv.mjs} +3 -3
  114. package/dist/{server-bare-B9E7S1aR.mjs → server-bare-dWjAarki.mjs} +6 -6
  115. package/dist/server-command-7XIKlWtO.mjs +69 -0
  116. package/dist/{server-expo-DoBHhuzK.mjs → server-expo-CsYu2aKL.mjs} +107 -82
  117. package/dist/server-expo-DV43vep4.mjs +2 -0
  118. package/dist/settings-Bbztm4oV.mjs +1503 -0
  119. package/dist/{settings-Byp2AQ_E.mjs → settings-CT3E71-i.mjs} +6 -7
  120. package/dist/{state-lKh8shBp.d.mts → settings-vk3mJGSm.d.mts} +4 -2
  121. package/dist/settings.schema.json +156 -6
  122. package/dist/simslim-ChMfe8rg.mjs +149 -0
  123. package/dist/{slot-launch-C1nqh4mu.mjs → slot-launch-B9hn96wd.mjs} +6 -6
  124. package/dist/{stage-g_WqWeaE.mjs → stage-BNKKdDNc.mjs} +6 -3
  125. package/dist/start-CbQCSnXQ.mjs +2 -0
  126. package/dist/{start-D-IeOyoK.mjs → start-CdvcTaXr.mjs} +113 -77
  127. package/dist/{state-xnqpx3AD.mjs → state-Boo0IMJq.mjs} +4 -2
  128. package/dist/{state-BG-aAk4k.mjs → state-DI84A-uu.mjs} +29 -10
  129. package/dist/{stats-DY6ekz8F.mjs → stats-BIIDrjA8.mjs} +6 -6
  130. package/dist/{status-BEeL6IKz.mjs → status-BDGHwkCs.mjs} +3 -1
  131. package/dist/{status-DCjZeE-K.mjs → status-DARMkc9q.mjs} +80 -294
  132. package/dist/status-watch-DvmI3SmN.mjs +296 -0
  133. package/dist/{stim-desktop-Behws_fi.mjs → stim-desktop-8GQX2OV8.mjs} +1 -1
  134. package/dist/{stop-DIJtD2GL.mjs → stop-CCZS-C9m.mjs} +2 -2
  135. package/dist/stop-WQPQHLuy.mjs +72 -0
  136. package/dist/{stop-BTjs3xbc.mjs → stop-iNemHsJC.mjs} +81 -33
  137. package/dist/supervisor-run.d.mts +7 -2
  138. package/dist/supervisor-run.mjs +32 -20
  139. package/dist/{support-Bwvbrq5-.mjs → support-CnE_SIjt.mjs} +17 -7
  140. package/dist/{support-BaayeYCf.mjs → support-DCco5fut.mjs} +61 -8
  141. package/dist/swiftpm-macos-afeNUExW.mjs +119 -0
  142. package/dist/{native-run-B06FkmZx.mjs → tailnet-CoJUb2tB.mjs} +113 -6
  143. package/dist/toolchain-BIupIro1.mjs +2 -0
  144. package/dist/{toolchain-Bnyd_K3A.mjs → toolchain-DqyDXs30.mjs} +225 -315
  145. package/dist/{trigger-CJMhwXe8.mjs → trigger-C0MWBi8O.mjs} +3 -3
  146. package/dist/trigger-SHLOWJ4O.mjs +2 -0
  147. package/dist/{warm-progress-CjB-MP6H.mjs → warm-progress-C3ASD6z5.mjs} +134 -37
  148. package/dist/{status-Dcr0Qexd.mjs → watchman-CQl51YZu.mjs} +75 -7
  149. package/dist/web-B3eldHFH.mjs +123 -0
  150. package/dist/web-BLXpOe6b.mjs +2 -0
  151. package/dist/web-run.mjs +10 -10
  152. package/dist/{workspace-state-C59dqG-Z.mjs → workspace-state-CSIFuuDa.mjs} +33 -33
  153. package/dist/{workspaces-xXItD8Dj.mjs → workspaces-D2xSHBDY.mjs} +1297 -1211
  154. package/dist/{worktree-BSYlofkq.mjs → worktree-BxDqaTev.mjs} +102 -63
  155. package/dist/worktree-q7_OZNY0.mjs +2 -0
  156. package/package.json +8 -6
  157. package/shim/bundle-response.cjs +2 -1
  158. package/shim/bundle-response.d.cts +1 -1
  159. package/shim/expo-metro-config.cjs +1 -1
  160. package/shim/native-android.gradle +101 -0
  161. package/dist/android-BKA16DTf.mjs +0 -4
  162. package/dist/android-BvwuMUJ9.mjs +0 -3572
  163. package/dist/guide-status-Bhf9SJXc.mjs +0 -125
  164. package/dist/ios-CPdkoKaa.mjs +0 -3953
  165. package/dist/ios-DNpdygiv.mjs +0 -7
  166. package/dist/macos-Ck7OBMNY.mjs +0 -2
  167. package/dist/metro-store-BAHFcJm6.mjs +0 -106
  168. package/dist/plan-placement-CtxkCO54.mjs +0 -1859
  169. package/dist/project-C3ChRLZy.mjs +0 -212
  170. package/dist/projects-DD7dBTCj.mjs +0 -92
  171. package/dist/server-expo-Db9HBUoa.mjs +0 -2
  172. package/dist/settings-CQyMElEP.mjs +0 -701
  173. package/dist/start-NTc1Ifqs.mjs +0 -2
  174. package/dist/stop-BGhKMOuH.mjs +0 -48
  175. package/dist/trigger-DWAq9hkw.mjs +0 -2
  176. package/dist/web-B6_bFDn7.mjs +0 -2
  177. package/dist/worktree-BJfnU6r5.mjs +0 -2
  178. package/dist/worktree-CsKYexJM.mjs +0 -753
@@ -1,14 +1,18 @@
1
- import { n as debugLog, t as getExecutor } from "./exec-DWhCZDGm.mjs";
2
- import { _ as loadConfig$1, d as getConfigDir } from "./config-CgYMahz7.mjs";
3
- import { w as withDirLock } from "./workspace-state-C59dqG-Z.mjs";
4
- import { a as toolchainMismatches, d as readRubyVersion, h as COMPILATION_CACHE_UNAVAILABLE, j as CCACHE_UNAVAILABLE, s as manifestDigest } from "./toolchain-Bnyd_K3A.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";
5
8
  import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readlinkSync, realpathSync, renameSync, rmSync, writeFileSync, writeSync } from "node:fs";
6
- import { basename, dirname, join, relative } from "node:path";
7
- import { OFFLOAD_MODES, acceptsShape, buildMachinesFile, buildMachinesLock, deviceHostMachinesClaims, deviceHostMachinesFile, deviceHostMachinesLock, endpoint, findPeer as findPeer$1, isJsonObject, parseMachine, parseMachine as parseMachine$1, pinnedEndpoint, readBuildMachines, readDeviceHostMachines, runId, saturation, settingDefinition } from "@stim-cli/core/state";
8
- 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";
9
11
  import { InvalidArgumentError } from "commander";
10
12
  import { releaseClaim, tryAcquireClaim } from "@stim-cli/core/ownership-claim";
11
13
  import { createHash } from "node:crypto";
14
+ import { hostname } from "node:os";
15
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
12
16
  import { WebSocket } from "ws";
13
17
  //#region src/offload/access-ticket.ts
14
18
  function readAccessTicket() {
@@ -27,95 +31,6 @@ function readHostPermissions(value) {
27
31
  } : void 0;
28
32
  }
29
33
  //#endregion
30
- //#region src/offload/tailnet.ts
31
- const MAC_APP_TAILSCALE = "/Applications/Tailscale.app/Contents/MacOS/Tailscale";
32
- const HELLO_TIMEOUT_MS = 1e4;
33
- function isLapsed(credential) {
34
- return credential.state === "pending" && !!credential.expiresAt && Date.parse(credential.expiresAt) <= Date.now();
35
- }
36
- function markApproved(credential) {
37
- const { expiresAt: _expiresAt, ...rest } = credential;
38
- return {
39
- ...rest,
40
- state: "approved"
41
- };
42
- }
43
- function pendingReport(credential) {
44
- return {
45
- state: "pending",
46
- ...credential.expiresAt ? { expiresAt: credential.expiresAt } : {}
47
- };
48
- }
49
- function lapsedLine(machine, credential) {
50
- return `${machine} did not approve this Mac before the request lapsed at ${credential.expiresAt}.`;
51
- }
52
- function requestedLine(credential) {
53
- return `Requested at ${credential.requestedAt}${credential.expiresAt ? `; the request lapses at ${credential.expiresAt}` : ""}.`;
54
- }
55
- function tailscaleStatus() {
56
- for (const binary of ["tailscale", MAC_APP_TAILSCALE]) {
57
- const output = getExecutor().runFileQuiet(binary, ["status", "--json"], { timeoutMs: 5e3 });
58
- if (output === null) continue;
59
- try {
60
- const status = JSON.parse(output);
61
- return isJsonObject(status) && status.BackendState === "Running" ? status : null;
62
- } catch {
63
- return null;
64
- }
65
- }
66
- return null;
67
- }
68
- function hello({ url, servername, host }, auth) {
69
- return new Promise((resolve) => {
70
- const socket = new WebSocket(url, {
71
- handshakeTimeout: HELLO_TIMEOUT_MS,
72
- servername,
73
- headers: { Host: host }
74
- });
75
- const done = (reply) => {
76
- clearTimeout(timer);
77
- socket.removeAllListeners();
78
- socket.on("error", () => {});
79
- socket.close();
80
- resolve(reply);
81
- };
82
- const timer = setTimeout(() => done({ failed: "no reply in time" }), HELLO_TIMEOUT_MS);
83
- socket.on("open", () => {
84
- socket.send(JSON.stringify({
85
- id: 1,
86
- method: "hello",
87
- params: {
88
- protocol: 1,
89
- client: {
90
- name: "stim",
91
- version: "1"
92
- },
93
- auth
94
- }
95
- }));
96
- });
97
- socket.on("message", (data) => {
98
- let message;
99
- try {
100
- message = JSON.parse(String(data));
101
- } catch {
102
- message = null;
103
- }
104
- if (isJsonObject(message) && ("result" in message || "error" in message)) return done(message);
105
- done({ failed: "the reply was not a hello result" });
106
- });
107
- socket.on("error", (error) => done({ failed: error.message }));
108
- socket.on("close", () => done({ failed: "the connection closed before a reply" }));
109
- });
110
- }
111
- const realIo = {
112
- status: tailscaleStatus,
113
- hello
114
- };
115
- function pinnedEndpoint$1(credential, status = tailscaleStatus) {
116
- return pinnedEndpoint(credential, parseMachine(credential.machine) ? status() : null);
117
- }
118
- //#endregion
119
34
  //#region src/device-host/machines.ts
120
35
  function note$1(title, detail, fix = null) {
121
36
  return {
@@ -689,6 +604,9 @@ function parseBuildMachineOption(value) {
689
604
  throw new InvalidArgumentError(error.message);
690
605
  }
691
606
  }
607
+ function requireLocalBuild(selected) {
608
+ if (selected === "auto" && !automaticMachineEnabled("build", "local")) throw new OffloadRefusal("auto", "local is disabled in remote.buildPoolDisabled; enable it or select a machine explicitly");
609
+ }
692
610
  function namedBuildMachine(selected) {
693
611
  return selected !== "auto" && selected !== "local";
694
612
  }
@@ -785,9 +703,11 @@ function offloadMode(env = process.env) {
785
703
  return OFFLOAD_MODES.includes(raw) ? raw : "auto";
786
704
  }
787
705
  function buildPlacementCandidates(selected) {
706
+ const config = loadConfig$1();
788
707
  return {
708
+ localEnabled: selected !== "auto" || automaticMachineEnabled("build", "local", config),
789
709
  mode: selected === "local" ? "off" : offloadMode(),
790
- machines: selected === "local" ? [] : namedBuildMachine(selected) ? pairedMachines([selected]).slice(0, 1) : pairedMachines()
710
+ machines: selected === "local" ? [] : namedBuildMachine(selected) ? pairedMachines([selected]).slice(0, 1) : pairedMachines().filter((entry) => automaticMachineEnabled("build", entry.machine, config))
791
711
  };
792
712
  }
793
713
  function remotePhaseText(name, msg, machine) {
@@ -813,7 +733,7 @@ function placementLoad(choice) {
813
733
  * this Mac has a free `concurrency.maxBuilds` slot and its load per core is under `server.maxLoadPerCore`.
814
734
  * A named selection ignores local capacity and mode, and refuses an unsupported build or missing pairing.
815
735
  */
816
- function offloadPlacement({ mode, machines, here, unsupported, selected = "auto" }) {
736
+ function offloadPlacement({ mode, machines, here, unsupported, localEnabled = true, selected = "auto" }) {
817
737
  if (namedBuildMachine(selected)) {
818
738
  if (unsupported) throw new OffloadRefusal(selected, `${unsupported}, so a named remote Mac cannot take it`);
819
739
  if (machines === 0) throw new OffloadRefusal(selected, "no remote Mac is paired");
@@ -828,6 +748,15 @@ function offloadPlacement({ mode, machines, here, unsupported, selected = "auto"
828
748
  code: "local-selected",
829
749
  reason: "--remote-build local"
830
750
  };
751
+ if (!localEnabled) {
752
+ const reason = mode === "off" ? "remote.buildMode is off" : unsupported ?? (machines === 0 ? "no enabled remote Mac is paired" : null);
753
+ if (reason) throw new OffloadRefusal("auto", `${reason}; local is disabled in remote.buildPoolDisabled`);
754
+ return {
755
+ offload: true,
756
+ code: "local-disabled",
757
+ reason: "local is disabled in remote.buildPoolDisabled"
758
+ };
759
+ }
831
760
  if (mode === "off") return {
832
761
  offload: false,
833
762
  code: "mode-off",
@@ -891,7 +820,7 @@ function offerProblems(offer, target) {
891
820
  * A machine that reports no load (a stim-server older than capacity) takes an `auto` build only while
892
821
  * every local slot is busy. The rest rank warmest first, then least loaded.
893
822
  */
894
- function pickOffer({ mode, here, offers, target, selected = "auto" }) {
823
+ function pickOffer({ mode, here, offers, target, localEnabled = true, selected = "auto" }) {
895
824
  const slotsFull = here.maxBuilds > 0 && here.builds >= here.maxBuilds;
896
825
  const reasons = [];
897
826
  const candidates = [];
@@ -911,7 +840,7 @@ function pickOffer({ mode, here, offers, target, selected = "auto" }) {
911
840
  const problems = offerProblems(offer, target);
912
841
  if (problems.length) return skip(machine, problemCode(problems[0]), problems.map((problem) => problem.reason).join("; "), problems.map((problem) => problem.code));
913
842
  const load = offer.capacity.loadPerCore;
914
- if (selected === "auto" && mode === "auto" && !slotsFull) {
843
+ if (selected === "auto" && localEnabled && mode === "auto" && !slotsFull) {
915
844
  if (typeof load !== "number") return skip(machine, "load", "capacity unknown (older stim-server) while this Mac has a free slot");
916
845
  if (load >= here.loadPerCore) return skip(machine, "load", `no less loaded (load ${load}/core there, ${here.loadPerCore}/core here)`);
917
846
  }
@@ -1146,6 +1075,7 @@ var BuildConnection = class BuildConnection {
1146
1075
  });
1147
1076
  }
1148
1077
  async sendBinary(frame) {
1078
+ if (this.closed) return;
1149
1079
  this.socket.send(frame, { binary: true });
1150
1080
  while (this.socket.bufferedAmount > MAX_BUFFERED && !this.closed) await new Promise((resolve) => setTimeout(resolve, 5));
1151
1081
  }
@@ -1183,6 +1113,14 @@ var BuildConnection = class BuildConnection {
1183
1113
  }
1184
1114
  forget() {
1185
1115
  this.ended = true;
1116
+ this.closed ??= "the connection was closed by this client";
1117
+ for (const resolve of this.pending.values()) resolve({ error: {
1118
+ code: "closed",
1119
+ message: this.closed
1120
+ } });
1121
+ this.pending.clear();
1122
+ this.progress = null;
1123
+ this.binary = null;
1186
1124
  clearInterval(this.keepalive);
1187
1125
  this.socket.removeAllListeners("close");
1188
1126
  openSockets.delete(this.socket);
@@ -1221,61 +1159,37 @@ function repoIdentity(projectRoot) {
1221
1159
  lockfile: lock ? createHash("sha256").update(readFileSync(join(repoRoot, lock))).digest("hex") : null
1222
1160
  };
1223
1161
  }
1224
- /** The exact source a build needs: tracked and untracked, not ignored, as `git ls-files -co --exclude-standard`. */
1225
- function sourceManifest(repoRoot) {
1226
- const listed = getExecutor().runFile("git", [
1227
- "-C",
1228
- repoRoot,
1229
- "ls-files",
1230
- "-z",
1231
- "-co",
1232
- "--exclude-standard"
1233
- ], {
1234
- untrimmed: true,
1235
- timeoutMs: 12e4
1236
- });
1237
- const files = [];
1238
- for (const path of new Set(listed.split("\0").filter(Boolean))) {
1239
- const absolute = join(repoRoot, path);
1240
- let stat;
1241
- try {
1242
- stat = lstatSync(absolute);
1243
- } catch {
1244
- continue;
1245
- }
1246
- if (stat.isSymbolicLink()) {
1247
- const target = Buffer.from(readlinkSync(absolute));
1248
- files.push({
1249
- path,
1250
- kind: "link",
1251
- size: target.length,
1252
- sha256: sha256(target)
1253
- });
1254
- } else if (stat.isFile()) {
1255
- const content = readFileSync(absolute);
1256
- files.push({
1257
- path,
1258
- kind: stat.mode & 73 ? "exec" : "file",
1259
- size: content.length,
1260
- sha256: sha256(content)
1261
- });
1262
- }
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];
1263
1170
  }
1264
- return files;
1265
1171
  }
1266
- const sha256 = (content) => createHash("sha256").update(content).digest("hex");
1267
1172
  function blobContent(repoRoot, file) {
1268
1173
  const absolute = join(repoRoot, file.path);
1269
- 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);
1270
1175
  }
1271
1176
  /** Closes every connection the choice still holds. */
1272
1177
  function closeOffload(choice) {
1273
1178
  choice.connection.close();
1274
1179
  for (const each of choice.runnersUp.splice(0)) each.connection.close();
1275
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
+ }
1276
1190
  /** Connects to one paired machine and asks it for an offer; the caller closes the connection it returns. */
1277
1191
  async function probeMachine(credential, identity, { connectMs = CONNECT_TIMEOUT_MS, offerMs = OFFER_TIMEOUT_MS } = {}) {
1278
- const target = pinnedEndpoint$1(credential);
1192
+ const target = await pinnedEndpoint$1(credential);
1279
1193
  if (typeof target === "string") return {
1280
1194
  credential,
1281
1195
  failure: target
@@ -1285,10 +1199,18 @@ async function probeMachine(credential, identity, { connectMs = CONNECT_TIMEOUT_
1285
1199
  credential,
1286
1200
  failure: connection.failure
1287
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;
1288
1210
  const reply = await connection.request("build.offer", {
1289
1211
  repo: identity.repo,
1290
1212
  ...identity.lockfile ? { lockfile: identity.lockfile } : {},
1291
- ...identity.rubyVersion ? { rubyVersion: identity.rubyVersion } : {}
1213
+ ...native ? { native } : identity.rubyVersion ? { rubyVersion: identity.rubyVersion } : {}
1292
1214
  }, offerMs);
1293
1215
  const failure = replyError(reply);
1294
1216
  if (failure || !("result" in reply)) {
@@ -1317,12 +1239,15 @@ async function chooseBuildMachine({ projectRoot, target, mode, here, note, machi
1317
1239
  return `this app is not in a git checkout (${error.message.split("\n")[0]})`;
1318
1240
  }
1319
1241
  if (namedBuildMachine(selected)) machines = machines.filter((each) => each.machine === selected).slice(0, 1);
1242
+ else machines = machines.filter((each) => automaticMachineEnabled("build", each.machine));
1320
1243
  const rubyVersion = readRubyVersion(projectRoot) ?? void 0;
1321
1244
  const asked = await Promise.all(machines.map((credential) => probeMachine(credential, {
1322
1245
  ...identity,
1323
- rubyVersion
1246
+ rubyVersion,
1247
+ ...target.platform !== "macos" ? { native: target.native } : {}
1324
1248
  })));
1325
1249
  const { order, reasons, candidates } = pickOffer({
1250
+ localEnabled: selected !== "auto" || automaticMachineEnabled("build", "local"),
1326
1251
  selected,
1327
1252
  mode,
1328
1253
  here,
@@ -1353,9 +1278,14 @@ async function chooseBuildMachine({ projectRoot, target, mode, here, note, machi
1353
1278
  target,
1354
1279
  offerMs: Date.now() - started,
1355
1280
  identity,
1356
- runnersUp: rest
1281
+ runnersUp: rest,
1282
+ automatic: selected === "auto"
1357
1283
  };
1358
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
+ }
1359
1289
  const ARTIFACT_NAME = {
1360
1290
  ios: /^[^/]+\.app$/,
1361
1291
  android: /^[^/]+\.apk$/,
@@ -1366,12 +1296,12 @@ const ARTIFACT_NAME = {
1366
1296
  * `RESUME_WINDOW_MS`, shorter than the machine's grace for a dropped connection, has passed. Returns why it
1367
1297
  * could not.
1368
1298
  */
1369
- async function resumeJob(credential, job, abandoned) {
1299
+ async function resumeJob(credential, job, abandoned, signal, native) {
1370
1300
  const deadline = Date.now() + RESUME_WINDOW_MS;
1371
1301
  let delay = RESUME_DELAY_MS;
1372
1302
  let last = "no attempt";
1373
1303
  while (!abandoned()) {
1374
- const target = pinnedEndpoint$1(credential);
1304
+ const target = await pinnedEndpoint$1(credential);
1375
1305
  const connection = typeof target === "string" ? {
1376
1306
  failure: target,
1377
1307
  refused: false
@@ -1380,9 +1310,24 @@ async function resumeJob(credential, job, abandoned) {
1380
1310
  if (connection.refused) return `the machine turned this Mac away (${connection.failure})`;
1381
1311
  last = connection.failure;
1382
1312
  } else {
1313
+ if (abandoned()) {
1314
+ connection.close();
1315
+ break;
1316
+ }
1317
+ if (native && !connection.supports(NATIVE_BUILD_FEATURE[native])) {
1318
+ connection.close();
1319
+ return unsupportedNative(native);
1320
+ }
1383
1321
  const early = [];
1384
1322
  connection.onProgress((event) => early.push(event));
1323
+ const cancel = () => connection.close();
1324
+ signal?.addEventListener("abort", cancel, { once: true });
1385
1325
  const reply = await connection.request("build.attach", { job }, OFFER_TIMEOUT_MS);
1326
+ signal?.removeEventListener("abort", cancel);
1327
+ if (abandoned()) {
1328
+ connection.close();
1329
+ break;
1330
+ }
1386
1331
  if ("result" in reply) {
1387
1332
  const outcome = isJsonObject(reply.result) ? reply.result.outcome : null;
1388
1333
  return {
@@ -1398,17 +1343,18 @@ async function resumeJob(credential, job, abandoned) {
1398
1343
  }
1399
1344
  connection.drop();
1400
1345
  }
1401
- if (Date.now() + delay > deadline) break;
1402
- await new Promise((resolve) => setTimeout(resolve, delay));
1346
+ if (abandoned() || Date.now() + delay > deadline) break;
1347
+ await setTimeout$1(delay, void 0, { signal }).catch(() => {});
1403
1348
  delay = Math.min(delay * 2, RESUME_MAX_DELAY_MS);
1404
1349
  }
1405
1350
  return `no connection to it within ${RESUME_WINDOW_MS / 6e4} min (${last})`;
1406
1351
  }
1407
1352
  /** Mirrors the checkout on the machine: the manifest in pages, then every blob it lacks. */
1408
- async function syncSource(connection, identity, onEnter) {
1353
+ async function syncSource(connection, identity, onEnter, native) {
1409
1354
  onEnter("sync");
1410
1355
  const syncStarted = Date.now();
1411
- 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." };
1412
1358
  const bySha = new Map(manifest.map((file) => [file.sha256, file]));
1413
1359
  const missing = [];
1414
1360
  for (let index = 0; index < manifest.length || index === 0;) {
@@ -1452,12 +1398,20 @@ async function syncSource(connection, identity, onEnter) {
1452
1398
  digest: manifestDigest(manifest)
1453
1399
  };
1454
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
+ }
1455
1407
  /**
1456
1408
  * Builds on the chosen machine and brings the `.app` or `.apk` back into `stagingDir`, verified against the
1457
1409
  * sha256 the machine reports. The caller re-fingerprints and stores it.
1458
1410
  */
1459
1411
  async function offloadBuild({ choice, expectedFingerprint, request, stagingDir, onPhase, onEnter, onRecord, note }) {
1460
1412
  const { identity } = choice;
1413
+ const native = request.platform !== "macos" ? request.native : void 0;
1414
+ let sourceDigest = null;
1461
1415
  const started = Date.now();
1462
1416
  const fail = (reason) => {
1463
1417
  closeOffload(choice);
@@ -1467,23 +1421,38 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
1467
1421
  reason: reason.split("\n")[0].slice(0, 300)
1468
1422
  };
1469
1423
  };
1424
+ const signal = runCancellationSignal();
1425
+ const throwIfCancelled = () => {
1426
+ if (signal?.aborted) throw Object.assign(/* @__PURE__ */ new Error("The offloaded build was cancelled."), { code: "STIM_CANCELLED" });
1427
+ };
1428
+ let settle;
1429
+ let settled = false;
1430
+ const outcome = new Promise((resolve) => {
1431
+ settle = (value) => {
1432
+ settled = true;
1433
+ resolve(value);
1434
+ };
1435
+ });
1436
+ const cancel = () => {
1437
+ settle({
1438
+ ok: false,
1439
+ code: "cancelled"
1440
+ });
1441
+ closeOffload(choice);
1442
+ };
1443
+ signal?.addEventListener("abort", cancel, { once: true });
1470
1444
  try {
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.");
1471
1448
  let job = null;
1472
1449
  let early = [];
1473
- let settle;
1474
- let settled = false;
1475
- const outcome = new Promise((resolve) => {
1476
- settle = (value) => {
1477
- settled = true;
1478
- resolve(value);
1479
- };
1480
- });
1481
1450
  let resuming = false;
1482
1451
  const reattach = async (why) => {
1483
1452
  if (resuming || settled) return;
1484
1453
  resuming = true;
1485
1454
  note(`offload: the connection to ${choice.machine} dropped (${why}); reattaching to the build there`);
1486
- const resumed = await resumeJob(choice.credential, job, () => settled);
1455
+ const resumed = await resumeJob(choice.credential, job, () => settled, signal, native?.provider);
1487
1456
  resuming = false;
1488
1457
  if (settled) {
1489
1458
  if (typeof resumed !== "string") resumed.connection.close();
@@ -1526,21 +1495,44 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
1526
1495
  return true;
1527
1496
  };
1528
1497
  for (;;) {
1529
- const synced = await syncSource(choice.connection, identity, onEnter);
1498
+ if (choice.automatic && !automaticMachineEnabled("build", choice.machine)) {
1499
+ const reason = `${choice.machine} is disabled in remote.buildPoolDisabled`;
1500
+ if (moveOn(reason)) continue;
1501
+ return fail(reason);
1502
+ }
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));
1509
+ throwIfCancelled();
1530
1510
  if ("failure" in synced) {
1531
1511
  if (moveOn(synced.failure)) continue;
1532
1512
  return fail(synced.failure);
1533
1513
  }
1534
1514
  ({syncMs, uploadedBytes} = synced);
1515
+ sourceDigest = synced.digest;
1535
1516
  onPhase("sync", `${synced.files} files, uploaded ${synced.uploaded} (${mb(uploadedBytes)}) in ${seconds(syncMs)}`);
1536
1517
  workerStarted = Date.now();
1537
1518
  early = [];
1538
1519
  choice.connection.onProgress((event) => job === null ? early.push(event) : handle(event));
1520
+ if (choice.automatic) requireAutomaticMachine("build", choice.machine);
1521
+ throwIfCancelled();
1539
1522
  const reply = await choice.connection.request("build.start", {
1540
1523
  repo: identity.repo,
1541
1524
  project: identity.project,
1542
1525
  platform: request.platform,
1543
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
+ } } : {},
1544
1536
  ...request.platform === "macos" ? { macos: {
1545
1537
  product: request.product,
1546
1538
  infoPlist: request.infoPlist,
@@ -1550,6 +1542,7 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
1550
1542
  } } : {
1551
1543
  configuration: request.platform === "ios" ? request.configuration : null,
1552
1544
  scheme: request.platform === "ios" ? request.scheme : null,
1545
+ iosProjectPath: request.platform === "ios" ? request.iosProjectPath ?? null : null,
1553
1546
  runtime: request.platform === "ios" ? request.runtime : null,
1554
1547
  packageName: packageName(join(identity.repoRoot, identity.project)),
1555
1548
  isExpo: request.isExpo,
@@ -1558,6 +1551,7 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
1558
1551
  },
1559
1552
  stimBuild: choice.target.local.stimBuild
1560
1553
  });
1554
+ throwIfCancelled();
1561
1555
  if ("result" in reply) {
1562
1556
  job = reply.result.job;
1563
1557
  break;
@@ -1575,14 +1569,18 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
1575
1569
  }), JOB_TIMEOUT_MS);
1576
1570
  const result = await outcome;
1577
1571
  clearTimeout(timer);
1572
+ throwIfCancelled();
1578
1573
  const { connection, machine } = choice;
1579
1574
  connection.onProgress(null);
1580
1575
  const workerMs = Date.now() - workerStarted;
1581
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.");
1582
1579
  const artifact = result.artifact;
1583
1580
  const name = typeof artifact?.name === "string" ? artifact.name : "";
1584
1581
  if (!ARTIFACT_NAME[request.platform].test(name) || typeof artifact.sha256 !== "string") return fail(`the machine reported no .${request.platform === "android" ? "apk" : "app"} artifact`);
1585
1582
  onEnter("fetch");
1583
+ throwIfCancelled();
1586
1584
  const fetchStarted = Date.now();
1587
1585
  rmSync(stagingDir, {
1588
1586
  recursive: true,
@@ -1612,6 +1610,7 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
1612
1610
  const fetched = await connection.request("build.artifact", { job }, 9e5);
1613
1611
  connection.onBinary(null);
1614
1612
  closeSync(fd);
1613
+ throwIfCancelled();
1615
1614
  const fetchFailure = replyError(fetched);
1616
1615
  if (fetchFailure || !("result" in fetched)) return fail(`fetch: ${fetchFailure ?? "no reply"}`);
1617
1616
  const digest = hash.digest("hex");
@@ -1624,8 +1623,10 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
1624
1623
  "-C",
1625
1624
  stagingDir
1626
1625
  ], { timeoutMs: 6e5 });
1626
+ throwIfCancelled();
1627
1627
  rmSync(archive, { force: true });
1628
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`);
1629
1630
  if (request.platform === "ios" && !existsSync(join(artifactPath, "Info.plist"))) return fail(`fetch: ${name} has no Info.plist`);
1630
1631
  if (request.platform === "android" && !(lstatSync(artifactPath, { throwIfNoEntry: false })?.isFile() ?? false)) return fail(`fetch: ${name} is not a file`);
1631
1632
  if (request.platform === "macos") {
@@ -1647,6 +1648,7 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
1647
1648
  sha256: digest
1648
1649
  } } : {},
1649
1650
  compilationCache: request.platform === "ios" ? compilationActivity(result.compilationCache) : COMPILATION_CACHE_UNAVAILABLE,
1651
+ ...native?.provider === "gradle" ? { androidPackage: result.androidPackage } : {},
1650
1652
  ccache: request.platform === "android" ? ccacheActivity(result.compilationCache) : CCACHE_UNAVAILABLE,
1651
1653
  timings: {
1652
1654
  offerMs: choice.offerMs,
@@ -1660,7 +1662,11 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
1660
1662
  }
1661
1663
  };
1662
1664
  } catch (error) {
1665
+ closeOffload(choice);
1666
+ throwIfCancelled();
1663
1667
  return fail(error.message.split("\n")[0] ?? String(error));
1668
+ } finally {
1669
+ signal?.removeEventListener("abort", cancel);
1664
1670
  }
1665
1671
  }
1666
1672
  function packageName(projectRoot) {
@@ -1754,9 +1760,10 @@ function offloadCheck(projectRoot, targets) {
1754
1760
  };
1755
1761
  probe.connection.close();
1756
1762
  resolved ??= targets();
1763
+ const resolvedTargets = await resolved;
1757
1764
  const problems = [];
1758
- for (const target of resolved) for (const problem of offerProblems(probe.offer, target)) {
1759
- 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) ? {
1760
1767
  ...problem,
1761
1768
  reason: `${PLATFORM_LABEL[target.platform]}: ${problem.reason}`
1762
1769
  } : problem;
@@ -1769,4 +1776,4 @@ function offloadCheck(projectRoot, targets) {
1769
1776
  };
1770
1777
  }
1771
1778
  //#endregion
1772
- export { pinnedEndpoint$1 as C, parseMachine$1 as S, resolveBuildPlacement as _, offloadBuild as a, hostingMachines as b, placementLoad as c, setRemoteLogSink as d, OffloadRefusal as f, resolveBuildMachine as g, parseBuildMachineOption as h, closeOffload as i, remotePhaseText as l, namedBuildMachine as m, buildPlacementCandidates as n, offloadCheck as o, buildPlacementSettings as p, chooseBuildMachine as r, offloadPlacement as s, BuildConnection as t, simulatorRuntime as u, inspectBuildMachines as v, realIo as w, inspectDeviceHostMachines as x, configuredMachines 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 };
@@ -10,8 +10,18 @@ interface ExecOptions {
10
10
  omitEnv?: readonly string[];
11
11
  /** Text written to the child's stdin; `runFile` only. */
12
12
  input?: string;
13
+ /** `runFileAsync` only: called with the child right after it starts; a throw kills the child and rejects the call. */
14
+ onSpawn?: (child: ChildProcess) => void;
13
15
  /** `runFile` only: return stdout as written, without trimming surrounding whitespace. */
14
16
  untrimmed?: boolean;
17
+ /**
18
+ * `runFile` and `runFileAsync`: reject nonempty stderr even when the command exits successfully.
19
+ */
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[];
15
25
  }
16
26
  interface Executor {
17
27
  run(cmd: string, opts?: ExecOptions): string;
@@ -1,12 +1,12 @@
1
1
  import { t as process_identity_exports } from "./process-identity-Biv3arCO.mjs";
2
+ import { R as deviceSlotKey, W as validateDeviceSlot } from "./config-D0LhTY3S.mjs";
3
+ import { w as workspaceLogsDir } from "./workspace-state-CSIFuuDa.mjs";
2
4
  import { t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
3
- import { R as deviceSlotKey, W as validateDeviceSlot } from "./config-CgYMahz7.mjs";
4
- import { x as workspaceLogsDir } from "./workspace-state-C59dqG-Z.mjs";
5
- import { r as createNdjsonWriter } from "./ndjson-LGZAMsqa.mjs";
6
- import { a as startIosDeviceConsole, i as parseDeviceConsoleLine } from "./ios-device-Bn7jWDB1.mjs";
7
- import { a as unregisterCollector, i as registerCollector, r as readCollectors, t as collectorProcessTitle } from "./ownership-D6Se7gg_.mjs";
8
- import { a as waitForAppPid, i as startAndroidLogcat, n as parseLogcatLine, o as watchAppPid, t as androidClockOffset } from "./android-CBwCNicO.mjs";
9
- import { n as parseLogStreamLine, r as startIosLogStream, t as appNameFromBundleId } from "./ios-Bvbf0SGY.mjs";
5
+ import { r as createNdjsonWriter } from "./ndjson-GOnbyvso.mjs";
6
+ import { a as startIosDeviceConsole, i as parseDeviceConsoleLine } from "./ios-device-B1B14xND.mjs";
7
+ import { a as unregisterCollector, i as registerCollector, r as readCollectors, t as collectorProcessTitle } from "./ownership-DXzMMSx6.mjs";
8
+ import { a as waitForAppPid, i as startAndroidLogcat, n as parseLogcatLine, o as watchAppPid, t as androidClockOffset } from "./android-CK3fpEyZ.mjs";
9
+ import { n as parseLogStreamLine, r as startIosLogStream, t as appNameFromBundleId } from "./ios-VGvNdsv2.mjs";
10
10
  import { existsSync } from "node:fs";
11
11
  import { isAbsolute, join, resolve } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
@@ -1,7 +1,7 @@
1
1
  import { n as stripAnsi } from "./process-output-Fpl8PYi8.mjs";
2
- import { t as NO_PROJECT_REFUSAL } from "./project-C3ChRLZy.mjs";
3
- import chalk from "chalk";
2
+ import { t as NO_PROJECT_REFUSAL } from "./project-B8_duAzW.mjs";
4
3
  import { quotedPath as quotedPath$1 } from "@stim-cli/core";
4
+ import chalk from "chalk";
5
5
  //#region src/diagnostics/launch-error-preview.ts
6
6
  function framePriority(frame, root) {
7
7
  const normalized = frame.replaceAll("\\", "/");