stim 1.17.1 → 1.18.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 (150) hide show
  1. package/README.md +50 -1
  2. package/dist/{activity-IRvJY_ru.mjs → activity-BxnkDZDi.mjs} +4 -4
  3. package/dist/{agent-device-usage-output-BK41vnzN.mjs → agent-device-usage-output-CZ66YA4T.mjs} +8 -8
  4. package/dist/android-BKA16DTf.mjs +4 -0
  5. package/dist/{android-B6rDWrTm.mjs → android-BvwuMUJ9.mjs} +192 -98
  6. package/dist/{android-DE4v59Mb.mjs → android-CBwCNicO.mjs} +2 -2
  7. package/dist/{android-B_YNZy61.mjs → android-DC3ZLuw0.mjs} +9 -9
  8. package/dist/{android-cas-ZFpjJBL_.mjs → android-cas-BBFyr9CA.mjs} +5 -5
  9. package/dist/android-cas-compiler.mjs +2 -2
  10. package/dist/api-D0VtFhqk.mjs +152 -0
  11. package/dist/api-run.d.mts +1 -0
  12. package/dist/api-run.mjs +145 -0
  13. package/dist/api.d.mts +212 -0
  14. package/dist/api.mjs +2 -0
  15. package/dist/{app-install-VPkI5WEg.mjs → app-install-D-PUENug.mjs} +3 -3
  16. package/dist/{attempt-Bk2ouQVL.mjs → attempt-DW-mQqM7.mjs} +14 -4
  17. package/dist/{budget-CUDz870h.mjs → budget-DIhL2BKx.mjs} +1581 -61
  18. package/dist/build-facts-DcLv2HSd.d.mts +111 -0
  19. package/dist/{cache-manifest-DnUHBJaU.mjs → cache-manifest-BI7ozfmH.mjs} +3 -3
  20. package/dist/cache-manifest.mjs +1 -1
  21. package/dist/{chrome-BrcVWQyD.mjs → chrome-DJnmnS5q.mjs} +1 -1
  22. package/dist/cli-DNJMwADx.mjs +68 -0
  23. package/dist/cli.mjs +1 -1
  24. package/dist/{client-CxNotugx.mjs → client-DzhapoMB.mjs} +184 -49
  25. package/dist/collector-run.mjs +9 -9
  26. package/dist/{command-output-CDAIFD-8.mjs → command-output-CBtzyIgF.mjs} +2 -2
  27. package/dist/{config-D0DbpZtM.mjs → config-CgYMahz7.mjs} +5 -5
  28. package/dist/{created-devices-CpvzBL25.mjs → created-devices-DKbeWsiK.mjs} +2 -2
  29. package/dist/{dependency-state-CLcBHH2M.mjs → dependency-state-xKoOypBm.mjs} +2 -2
  30. package/dist/{detached-entry-CiTEgMR1.mjs → detached-entry-BLfcgc4O.mjs} +1 -1
  31. package/dist/{dev-client-B-XC4uRH.mjs → dev-client-CLnIH02x.mjs} +2 -2
  32. package/dist/{device-C8IOoJPv.mjs → device-D7oCHGWi.mjs} +8 -8
  33. package/dist/{device-capacity-ZC4XSZ4R.mjs → device-capacity-EUOOYr8T.mjs} +13 -13
  34. package/dist/device-host-worker.mjs +19 -19
  35. package/dist/{device-ios-C2Um-XUR.mjs → device-ios-6MewKKnc.mjs} +13 -13
  36. package/dist/{device-lease-DpoS36_u.mjs → device-lease-Co-EwifR.mjs} +9 -9
  37. package/dist/{device-lease-run-B8YltIH_.mjs → device-lease-run-w1xhCqIW.mjs} +4 -4
  38. package/dist/{device-pool-3o7s5lo4.mjs → device-pool-F5WvRPtD.mjs} +3 -3
  39. package/dist/{device-remote-hFSkpfQu.mjs → device-remote-tHbd1fKy.mjs} +15 -15
  40. package/dist/{doctor-BF_435OC.mjs → doctor-B9ckYUoU.mjs} +33 -30
  41. package/dist/{error-diagnostics-C-NMnhrk.mjs → error-diagnostics-BRXOqDzG.mjs} +11 -11
  42. package/dist/{exec-CaErVv7t.mjs → exec-DWhCZDGm.mjs} +65 -2
  43. package/dist/{gc-BsWKa_-d.mjs → gc-Dh6mXOKC.mjs} +23 -469
  44. package/dist/{gradle-bsBRy6i9.mjs → gradle-C6GKnunQ.mjs} +6 -6
  45. package/dist/{guide-status-Du-ZQ3FD.mjs → guide-status-Bhf9SJXc.mjs} +3 -3
  46. package/dist/{guide-DC3n6-NH.mjs → guide-tm6igAaa.mjs} +314 -72
  47. package/dist/{hosted-android-DgU31wY3.mjs → hosted-android-Bv285t_t.mjs} +3 -3
  48. package/dist/{hosted-client-CdSoH-G6.mjs → hosted-client-C-rMtppF.mjs} +2 -2
  49. package/dist/{hosted-ios-BTaVj4Jb.mjs → hosted-ios-D5mNmtj4.mjs} +1 -1
  50. package/dist/{hosted-logs-SxfBvSvc.mjs → hosted-logs-DN8Iz4mJ.mjs} +9 -9
  51. package/dist/{hosted-macos-CT9E3meL.mjs → hosted-macos-B-wEjlwG.mjs} +5 -5
  52. package/dist/{hosted-native-CZLOrMrN.mjs → hosted-native-DeeOG8xZ.mjs} +6 -6
  53. package/dist/{idle-shutdown-IGyNV8Cr.mjs → idle-shutdown-jjtxAn5C.mjs} +7 -8
  54. package/dist/{ios-DSfnPfse.mjs → ios-B3SbOWdw.mjs} +19 -7
  55. package/dist/{ios-Cr1h5gnO.mjs → ios-Bvbf0SGY.mjs} +1 -1
  56. package/dist/{ios-C7JuT1bI.mjs → ios-CPdkoKaa.mjs} +181 -95
  57. package/dist/ios-DNpdygiv.mjs +7 -0
  58. package/dist/{ios-device-Ee7sDXfp.mjs → ios-device-Bn7jWDB1.mjs} +1 -1
  59. package/dist/{ios-device-pAN4t54-.mjs → ios-device-vOaByMhw.mjs} +3 -3
  60. package/dist/{ios-state-8L3lgum6.mjs → ios-state-B2pES6Vs.mjs} +1 -1
  61. package/dist/{launch-verify-lTnWZikR.mjs → launch-verify-hskNcWkZ.mjs} +5 -5
  62. package/dist/{logs-DNxKcok9.mjs → logs-1Ylc1Ri6.mjs} +13 -13
  63. package/dist/macos-Ck7OBMNY.mjs +2 -0
  64. package/dist/{macos-BEs8cwKx.mjs → macos-DF6C7wUU.mjs} +118 -34
  65. package/dist/macos-run.mjs +1 -1
  66. package/dist/maintenance-run.mjs +349 -47
  67. package/dist/{metro-g8NqiJp-.mjs → metro-Dh8T4Ytd.mjs} +5 -5
  68. package/dist/{metro-gateway-CHqK0p-a.mjs → metro-gateway-CEUw9RMH.mjs} +4 -4
  69. package/dist/{metro-store-CDDZHtPl.mjs → metro-store-BAHFcJm6.mjs} +16 -2
  70. package/dist/{named-ports-C8m8EQs2.mjs → named-ports-BFIz1Shz.mjs} +61 -6
  71. package/dist/{native-run-D1Q56rAD.mjs → native-run-B06FkmZx.mjs} +4 -4
  72. package/dist/{native-runtime-YhaSxP4v.mjs → native-runtime-D_603JiC.mjs} +6 -6
  73. package/dist/{ndjson-PclOIwVY.mjs → ndjson-LGZAMsqa.mjs} +8 -4
  74. package/dist/offload-worker.d.mts +3 -17
  75. package/dist/offload-worker.mjs +10 -11
  76. package/dist/{ownership-DyRQru0q.mjs → ownership-D6Se7gg_.mjs} +3 -3
  77. package/dist/{page-QF_Ke10s.mjs → page-Bv9s2KwY.mjs} +1 -1
  78. package/dist/placement-log-BcPhRwR4.mjs +84 -0
  79. package/dist/{build-plan-BJSXzsCL.mjs → plan-placement-CtxkCO54.mjs} +122 -48
  80. package/dist/{ports-4nYqxPWC.mjs → ports-BLTtfBq-.mjs} +3 -3
  81. package/dist/{prebuild-D7RUMkwN.mjs → prebuild-juCyreaR.mjs} +5 -5
  82. package/dist/preview-D5V07Nrl.mjs +987 -0
  83. package/dist/{rolldown-runtime-BsOwBuO_.mjs → process-identity-Biv3arCO.mjs} +11 -1
  84. package/dist/{project-D47Rh0ly.mjs → project-C3ChRLZy.mjs} +4 -68
  85. package/dist/projects-DD7dBTCj.mjs +92 -0
  86. package/dist/{pull-request-pxnDCQzn.mjs → pull-request-u4vp7zmK.mjs} +1 -1
  87. package/dist/pull-requests.mjs +1 -1
  88. package/dist/{recordings-BNgsD1Tr.mjs → recordings-BAWltsVt.mjs} +1 -1
  89. package/dist/{reload-byOjE3P4.mjs → reload-DvyWzVjJ.mjs} +15 -15
  90. package/dist/{remote-cache-Ci4DQYYj.mjs → remote-cache-BGg7aGIZ.mjs} +5 -5
  91. package/dist/{run-CkE9y0nc.mjs → run-vhtDgpHO.mjs} +5 -5
  92. package/dist/{server-bare-BkrSxSsZ.mjs → server-bare-B9E7S1aR.mjs} +4 -5
  93. package/dist/server-expo-Db9HBUoa.mjs +2 -0
  94. package/dist/{server-expo-CHTyXD0G.mjs → server-expo-DoBHhuzK.mjs} +5 -6
  95. package/dist/{settings-SR9OmW9h.mjs → settings-Byp2AQ_E.mjs} +6 -5
  96. package/dist/{settings-Cfd-0r2d.mjs → settings-CQyMElEP.mjs} +24 -763
  97. package/dist/{settings-BBpMcDbD.mjs → settings-Djl-mpjX.mjs} +1 -1
  98. package/dist/settings.schema.json +128 -5
  99. package/dist/{simslim-BWNrc3M3.mjs → simslim-EHBQ2R4G.mjs} +7 -7
  100. package/dist/{slot-launch-DmjBmpQV.mjs → slot-launch-C1nqh4mu.mjs} +6 -6
  101. package/dist/{spawn-claims-D0XfSe8u.mjs → spawn-claims-Kqiifsk2.mjs} +1 -1
  102. package/dist/{spawn-entry-jw19m8Nl.mjs → spawn-entry-Dcb7sDUz.mjs} +1 -0
  103. package/dist/{stage-CzaiwFsI.mjs → stage-g_WqWeaE.mjs} +14 -3
  104. package/dist/{start-BHIersSm.mjs → start-D-IeOyoK.mjs} +19 -19
  105. package/dist/{start-BEP-XmEA.mjs → start-NTc1Ifqs.mjs} +1 -1
  106. package/dist/{state-PeQiJuIu.mjs → state-BG-aAk4k.mjs} +4 -4
  107. package/dist/{state-B4PQFXMW.d.mts → state-lKh8shBp.d.mts} +2 -3
  108. package/dist/{state-DyaNTgfR.mjs → state-xnqpx3AD.mjs} +2 -2
  109. package/dist/{stats-Bq4fJOdB.mjs → stats-DY6ekz8F.mjs} +5 -5
  110. package/dist/{status-DlKvVJBE.mjs → status-BEeL6IKz.mjs} +6 -4
  111. package/dist/{status-CqeWbIWW.mjs → status-DCjZeE-K.mjs} +34 -35
  112. package/dist/{status-CzS4bPO9.mjs → status-Dcr0Qexd.mjs} +5 -5
  113. package/dist/{stim-desktop-Cnz8CPnl.mjs → stim-desktop-Behws_fi.mjs} +1 -1
  114. package/dist/{stim-installations-QMEI32v1.mjs → stim-installations-aR_WsNOA.mjs} +1 -1
  115. package/dist/{stop-D03prQnW.mjs → stop-BGhKMOuH.mjs} +5 -5
  116. package/dist/{stop-Cj01je8D.mjs → stop-BTjs3xbc.mjs} +17 -18
  117. package/dist/{stop-DOxwkH4j.mjs → stop-DIJtD2GL.mjs} +2 -2
  118. package/dist/supervisor-run.d.mts +24 -22
  119. package/dist/supervisor-run.mjs +21 -22
  120. package/dist/{support-BE-tbOP9.mjs → support-BaayeYCf.mjs} +7 -7
  121. package/dist/{support-WvD__tSV.mjs → support-Bwvbrq5-.mjs} +5 -5
  122. package/dist/{toolchain-CdrxgUeA.mjs → toolchain-Bnyd_K3A.mjs} +480 -27
  123. package/dist/{trigger-Bemu8ytw.mjs → trigger-CJMhwXe8.mjs} +5 -5
  124. package/dist/trigger-DWAq9hkw.mjs +2 -0
  125. package/dist/{warm-progress-DF4JRvYI.mjs → warm-progress-CjB-MP6H.mjs} +7 -7
  126. package/dist/web-B6_bFDn7.mjs +2 -0
  127. package/dist/{web-DddUdJzr.mjs → web-DTU0uiWj.mjs} +22 -22
  128. package/dist/web-run.mjs +10 -10
  129. package/dist/{workspace-state-DqcCbpE7.mjs → workspace-state-C59dqG-Z.mjs} +3 -3
  130. package/dist/{ownership-BMLqyBgJ.mjs → workspaces-xXItD8Dj.mjs} +577 -23
  131. package/dist/{worktree-CsAjSmEo.mjs → worktree-BJfnU6r5.mjs} +1 -1
  132. package/dist/{worktree-AK_m-d_m.mjs → worktree-BSYlofkq.mjs} +234 -35
  133. package/dist/worktree-CsKYexJM.mjs +753 -0
  134. package/package.json +10 -5
  135. package/dist/build-progress-D0WkjCPd.mjs +0 -1036
  136. package/dist/build-slots-41v_d2x9.mjs +0 -142
  137. package/dist/cli-BLQqDGlX.mjs +0 -51
  138. package/dist/deps-CVxlsRFn.mjs +0 -438
  139. package/dist/errors-B6W7FkxQ.mjs +0 -15
  140. package/dist/idle-BXG__1BA.mjs +0 -176
  141. package/dist/in-use-CvGOUn-x.mjs +0 -130
  142. package/dist/preview-C_JjkEy4.mjs +0 -318
  143. package/dist/process-identity-BmRm1qV0.mjs +0 -12
  144. package/dist/server-expo-CeakhI8P.mjs +0 -2
  145. package/dist/state-BKa8wNN5.mjs +0 -148
  146. package/dist/stop-cause-rhmGfwvt.mjs +0 -97
  147. package/dist/trigger-CXts6qt1.mjs +0 -2
  148. package/dist/watchman-APYjXSQa.mjs +0 -48
  149. package/dist/workspace-process-lock-2mNzOlaB.mjs +0 -58
  150. package/dist/workspaces-Bcib97-6.mjs +0 -306
@@ -0,0 +1,111 @@
1
+ //#region src/engine/build-facts.d.ts
2
+ type CacheHitLevel = "local" | "remote" | false;
3
+ type LaunchStatus = boolean | "unverified" | "bundling";
4
+ interface LogsInfo {
5
+ dir?: string;
6
+ }
7
+ interface CompilationCacheActivity {
8
+ status: "reported" | "unavailable" | "not-run";
9
+ hits: number | null;
10
+ cacheableTasks: number | null;
11
+ hitRatePercent: number | null;
12
+ swiftTargetsWithoutExplicitModules?: number;
13
+ }
14
+ interface CcacheActivity {
15
+ status: "reported" | "unavailable" | "not-run";
16
+ hits: number | null;
17
+ misses: number | null;
18
+ hitRatePercent: number | null;
19
+ }
20
+ interface DevServerStart {
21
+ started: true;
22
+ reason: "not running" | "stopped (idle)";
23
+ }
24
+ interface RunLeaseFacts {
25
+ kind: string;
26
+ expiresAt: string;
27
+ }
28
+ interface IosFacts {
29
+ devicePlacement?: import("@stim-cli/core/state").DevicePlacement;
30
+ host?: import("@stim-cli/core/state").HostedIosStatus;
31
+ slot?: string;
32
+ platform: string;
33
+ udid: string;
34
+ deviceName: string | null;
35
+ deviceType: string | null;
36
+ runtime: string | null;
37
+ fingerprint?: string | null;
38
+ configuration: string | null;
39
+ scheme?: string;
40
+ cacheKey?: string | null;
41
+ cacheHit: CacheHitLevel;
42
+ cacheSkipped: boolean;
43
+ buildMachine?: string;
44
+ builtOn?: string;
45
+ /** The remote Mac that compiled the app when the build was offloaded; absent otherwise. */
46
+ offloadedTo?: string;
47
+ /** Why the app was built here after the run considered offloading it; absent otherwise. */
48
+ offloadFallback?: string;
49
+ compilationCache: CompilationCacheActivity;
50
+ waitedForBuild: {
51
+ pid: number | null;
52
+ ms: number;
53
+ } | null;
54
+ appPath?: string | null;
55
+ bundleId?: string | null;
56
+ installSkipped: boolean;
57
+ launched: LaunchStatus;
58
+ metroPort?: number | null;
59
+ logs: LogsInfo;
60
+ agentDevice?: {
61
+ stateDir: string;
62
+ };
63
+ durationMs?: number;
64
+ webPreviewUrl?: string | null;
65
+ lease?: RunLeaseFacts | null;
66
+ devServer?: DevServerStart;
67
+ }
68
+ interface AndroidFacts {
69
+ devicePlacement?: import("@stim-cli/core/state").DevicePlacement;
70
+ host?: import("@stim-cli/core/state").HostedAndroidStatus;
71
+ slot?: string;
72
+ platform: string;
73
+ serial: string | null;
74
+ avdName: string | null;
75
+ deviceName: string | null;
76
+ systemImage: string | null;
77
+ deviceProfile: string | null;
78
+ fingerprint: string | null;
79
+ cacheKey: string | null;
80
+ variant: string | null;
81
+ metroPort: number | null;
82
+ cacheHit: CacheHitLevel;
83
+ cacheSkipped: boolean;
84
+ buildMachine?: string;
85
+ builtOn?: string;
86
+ /** The remote Mac that compiled the APK when the build was offloaded; absent otherwise. */
87
+ offloadedTo?: string;
88
+ /** Why the APK was built here after the run considered offloading it; absent otherwise. */
89
+ offloadFallback?: string;
90
+ waitedForBuild: {
91
+ pid: number | null;
92
+ ms: number;
93
+ } | null;
94
+ appPath: string | null;
95
+ bundleId: string | null;
96
+ installSkipped: boolean;
97
+ launched: LaunchStatus;
98
+ ccache: CcacheActivity;
99
+ debugHttpHost: string | null;
100
+ debugHttpHostNote: string | null;
101
+ devClientUrl: string | null;
102
+ logs: string | null;
103
+ agentDevice?: {
104
+ stateDir: string;
105
+ };
106
+ durationMs: number | null;
107
+ lease?: RunLeaseFacts | null;
108
+ devServer?: DevServerStart;
109
+ }
110
+ //#endregion
111
+ export { IosFacts as i, CcacheActivity as n, CompilationCacheActivity as r, AndroidFacts as t };
@@ -1,8 +1,8 @@
1
- import { d as getConfigDir } from "./config-D0DbpZtM.mjs";
2
- import { existsSync } from "fs";
3
- import { homedir } from "os";
1
+ import { d as getConfigDir } from "./config-CgYMahz7.mjs";
4
2
  import { isAbsolute, join, resolve } from "path";
3
+ import { existsSync } from "fs";
5
4
  import { readCacheManifest, updateCacheManifest } from "@stim-cli/core";
5
+ import { homedir } from "os";
6
6
  //#region src/cache/cache-manifest.ts
7
7
  function manifestPath() {
8
8
  return join(getConfigDir(), "caches.json");
@@ -1,2 +1,2 @@
1
- import { a as unregister, i as registeredCaches, n as readManifest, r as register, t as manifestPath } from "./cache-manifest-DnUHBJaU.mjs";
1
+ import { a as unregister, i as registeredCaches, n as readManifest, r as register, t as manifestPath } from "./cache-manifest-BI7ozfmH.mjs";
2
2
  export { manifestPath, readManifest, register, registeredCaches, unregister };
@@ -1,4 +1,4 @@
1
- import { t as getExecutor } from "./exec-CaErVv7t.mjs";
1
+ import { t as getExecutor } from "./exec-DWhCZDGm.mjs";
2
2
  import { existsSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { homedir } from "node:os";
@@ -0,0 +1,68 @@
1
+ import { n as debugLog } from "./exec-DWhCZDGm.mjs";
2
+ import { y as refuseRelativeStimPaths } from "./config-CgYMahz7.mjs";
3
+ import { readFileSync } from "node:fs";
4
+ import { runId } from "@stim-cli/core/state";
5
+ import { Command } from "commander";
6
+ //#region bin/cli.ts
7
+ const commands = /* @__PURE__ */ new Map([
8
+ ["doctor", () => import("./doctor-B9ckYUoU.mjs")],
9
+ ["worktree", () => import("./worktree-BJfnU6r5.mjs")],
10
+ ["start", () => import("./start-NTc1Ifqs.mjs")],
11
+ ["ports", () => import("./ports-BLTtfBq-.mjs")],
12
+ ["stop", () => import("./stop-DIJtD2GL.mjs")],
13
+ ["ios", () => import("./ios-DNpdygiv.mjs")],
14
+ ["android", () => import("./android-BKA16DTf.mjs")],
15
+ ["web", () => import("./web-B6_bFDn7.mjs")],
16
+ ["macos", () => import("./macos-Ck7OBMNY.mjs")],
17
+ ["reload", () => import("./reload-DvyWzVjJ.mjs")],
18
+ ["device", () => import("./device-D7oCHGWi.mjs")],
19
+ ["logs", () => import("./logs-1Ylc1Ri6.mjs")],
20
+ ["settings", () => import("./settings-Byp2AQ_E.mjs")],
21
+ ["status", () => import("./status-DCjZeE-K.mjs")],
22
+ ["stats", () => import("./stats-DY6ekz8F.mjs")],
23
+ ["gc", () => import("./gc-Dh6mXOKC.mjs")],
24
+ ["guide", () => import("./guide-tm6igAaa.mjs")]
25
+ ]);
26
+ const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
27
+ const program = new Command();
28
+ program.name("stim").description("Isolated React Native dev environments per project/worktree").version(pkg.version);
29
+ try {
30
+ refuseRelativeStimPaths();
31
+ process.env.STIM_RUN_ID = runId();
32
+ const first = process.argv[2];
33
+ const loadCommand = commands.get((first === "help" ? process.argv[3] : first) ?? "");
34
+ if (loadCommand) (await loadCommand()).default(program, pkg.version);
35
+ else if (first !== "--version" && first !== "-V") {
36
+ const modules = await Promise.all([...commands.values()].map((load) => load()));
37
+ for (const module of modules) module.default(program, pkg.version);
38
+ }
39
+ program.hook("preAction", async (_command, action) => {
40
+ if (debugLog.enabled()) {
41
+ const path = [];
42
+ for (let each = action; each?.parent; each = each.parent) path.unshift(each.name());
43
+ const started = Date.now();
44
+ debugLog.log("run_start", {
45
+ version: pkg.version,
46
+ command: path,
47
+ flags: process.argv.slice(2).filter((arg) => arg.startsWith("-")).map((arg) => arg.split("=")[0])
48
+ });
49
+ process.on("exit", (code) => debugLog.log("run_end", {
50
+ exit: code,
51
+ ms: Date.now() - started
52
+ }));
53
+ }
54
+ try {
55
+ const { triggerMaintenance } = await import("./trigger-DWAq9hkw.mjs");
56
+ triggerMaintenance(action.name());
57
+ } catch {}
58
+ });
59
+ await program.parseAsync();
60
+ } catch (err) {
61
+ const code = err?.code;
62
+ const message = err instanceof Error ? err.message : String(err);
63
+ if (typeof code === "string" && code.startsWith("STIM_")) console.error(`${code}: ${message}`);
64
+ else console.error(`Unexpected error: ${message.replace(/\.$/, "")}. Report it at ${pkg.bugs.url}`);
65
+ process.exit(1);
66
+ }
67
+ //#endregion
68
+ export {};
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-BLQqDGlX.mjs");
7
+ } else import("./cli-DNJMwADx.mjs");
8
8
  //#endregion
9
9
  export {};
@@ -1,14 +1,14 @@
1
- import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
- import { _ as loadConfig$1, d as getConfigDir } from "./config-D0DbpZtM.mjs";
3
- import { w as withDirLock } from "./workspace-state-DqcCbpE7.mjs";
4
- import { T as CCACHE_UNAVAILABLE, a as toolchainMismatches, l as COMPILATION_CACHE_UNAVAILABLE, s as manifestDigest } from "./toolchain-CdrxgUeA.mjs";
5
- import { createHash } from "node:crypto";
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";
6
5
  import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readlinkSync, realpathSync, renameSync, rmSync, writeFileSync, writeSync } from "node:fs";
7
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";
8
9
  import { InvalidArgumentError } from "commander";
9
- import { OFFLOAD_MODES, acceptsShape, buildMachinesFile, buildMachinesLock, deviceHostMachinesClaims, deviceHostMachinesFile, deviceHostMachinesLock, endpoint, findPeer as findPeer$1, isJsonObject, parseMachine, parseMachine as parseMachine$1, pinnedEndpoint, readBuildMachines, readDeviceHostMachines, saturation, settingDefinition } from "@stim-cli/core/state";
10
10
  import { releaseClaim, tryAcquireClaim } from "@stim-cli/core/ownership-claim";
11
- import { hostname } from "node:os";
11
+ import { createHash } from "node:crypto";
12
12
  import { WebSocket } from "ws";
13
13
  //#region src/offload/access-ticket.ts
14
14
  function readAccessTicket() {
@@ -30,6 +30,28 @@ function readHostPermissions(value) {
30
30
  //#region src/offload/tailnet.ts
31
31
  const MAC_APP_TAILSCALE = "/Applications/Tailscale.app/Contents/MacOS/Tailscale";
32
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
+ }
33
55
  function tailscaleStatus() {
34
56
  for (const binary of ["tailscale", MAC_APP_TAILSCALE]) {
35
57
  const output = getExecutor().runFileQuiet(binary, ["status", "--json"], { timeoutMs: 5e3 });
@@ -161,6 +183,7 @@ async function request(machine, peer, port, deviceName, io) {
161
183
  deviceToken: reply.result.deviceToken,
162
184
  state: "pending",
163
185
  requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
186
+ expiresAt: reply.result.approval.expiresAt,
164
187
  ...ticket ? { ticketHash: ticket.ticketHash } : {}
165
188
  };
166
189
  return {
@@ -169,7 +192,8 @@ async function request(machine, peer, port, deviceName, io) {
169
192
  dnsName: peer.dnsName,
170
193
  state: "pending",
171
194
  deviceId: credential.deviceId,
172
- requestedAt: credential.requestedAt
195
+ requestedAt: credential.requestedAt,
196
+ expiresAt: credential.expiresAt
173
197
  },
174
198
  finding: note$1(`Asked ${machine} for hosting access`, `The request lapses at ${reply.result.approval.expiresAt}.`, approval$1(machine, credential.deviceId)),
175
199
  credential
@@ -300,10 +324,7 @@ async function inspectDeviceHostMachines({ fix }, io = realIo, entries = configu
300
324
  const reply = await io.hello(endpoint(peer, parsed.port), { deviceToken: credential.deviceToken });
301
325
  if ("result" in reply && reply.result?.device?.id === credential.deviceId && Array.isArray(reply.result.capabilities) && reply.result.capabilities.includes("device-host")) {
302
326
  if (credential.state !== "approved") {
303
- credentials = credentials.map((each) => each.machine === machine ? {
304
- ...each,
305
- state: "approved"
306
- } : each);
327
+ credentials = credentials.map((each) => each.machine === machine ? markApproved(each) : each);
307
328
  store(credentials);
308
329
  }
309
330
  const host = readHostPermissions(reply.result.host);
@@ -326,17 +347,18 @@ async function inspectDeviceHostMachines({ fix }, io = realIo, entries = configu
326
347
  if (pending && !ask) {
327
348
  inspected.machines.push({
328
349
  ...known,
329
- state: "pending"
350
+ ...pendingReport(credential)
330
351
  });
331
- inspected.findings.push(note$1(`Remote Mac ${machine} has not approved this Mac for device hosting yet`, `Requested at ${credential.requestedAt}.`, approval$1(machine, credential.deviceId)));
352
+ inspected.findings.push(note$1(`Remote Mac ${machine} has not approved this Mac for device hosting yet`, requestedLine(credential), approval$1(machine, credential.deviceId)));
332
353
  continue;
333
354
  }
334
355
  if (!ask) {
356
+ const lapsed = revoked && isLapsed(credential);
335
357
  inspected.machines.push({
336
358
  ...known,
337
- state: revoked ? "revoked" : "unreachable"
359
+ state: lapsed ? "lapsed" : revoked ? "revoked" : "unreachable"
338
360
  });
339
- inspected.findings.push(note$1(`Remote Mac ${machine} ${revoked ? "no longer accepts this Mac for device hosting" : "did not confirm hosting access"}`, "The saved token and pinned node are preserved.", revoked ? "Run `stim doctor --fix` to ask again." : `Check stim-server and its tailnet serve route on ${machine}, then run doctor again.`));
361
+ inspected.findings.push(note$1(lapsed ? `Hosting request to ${machine} lapsed` : `Remote Mac ${machine} ${revoked ? "no longer accepts this Mac for device hosting" : "did not confirm hosting access"}`, lapsed ? `${lapsedLine(machine, credential)} The saved token and pinned node are preserved.` : "The saved token and pinned node are preserved.", revoked ? "Run `stim doctor --fix` to ask again." : `Check stim-server and its tailnet serve route on ${machine}, then run doctor again.`));
340
362
  continue;
341
363
  }
342
364
  }
@@ -471,6 +493,7 @@ async function requestAccess(entry, peer, port, deviceName, io) {
471
493
  deviceToken: reply.result.deviceToken,
472
494
  state: "pending",
473
495
  requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
496
+ expiresAt: reply.result.approval.expiresAt,
474
497
  ...ticket ? { ticketHash: ticket.ticketHash } : {}
475
498
  };
476
499
  updateCredentials((credentials) => [...credentials.filter((each) => each.machine !== entry), credential]);
@@ -480,7 +503,8 @@ async function requestAccess(entry, peer, port, deviceName, io) {
480
503
  state: "pending",
481
504
  dnsName: peer.dnsName,
482
505
  deviceId: credential.deviceId,
483
- requestedAt: credential.requestedAt
506
+ requestedAt: credential.requestedAt,
507
+ expiresAt: credential.expiresAt
484
508
  },
485
509
  finding: note(`Asked ${entry} for build access`, `The request lapses at ${reply.result.approval.expiresAt}.`, approval(entry, credential.deviceId))
486
510
  };
@@ -538,10 +562,7 @@ async function inspectMachine(entry, status, deviceName, fix, io, check) {
538
562
  if ("result" in reply && reply.result.capabilities.includes("build")) {
539
563
  const permissions = readHostPermissions(reply.result.host);
540
564
  const host = permissions ? { host: permissions } : {};
541
- if (credential.state !== "approved") updateCredentials((credentials) => credentials.map((each) => each.machine === entry ? {
542
- ...each,
543
- state: "approved"
544
- } : each));
565
+ if (credential.state !== "approved") updateCredentials((credentials) => credentials.map((each) => each.machine === entry ? markApproved(each) : each));
545
566
  if (!check) return {
546
567
  report: {
547
568
  ...paired,
@@ -574,19 +595,26 @@ async function inspectMachine(entry, status, deviceName, fix, io, check) {
574
595
  return {
575
596
  report: {
576
597
  ...paired,
577
- state: "pending"
598
+ ...pendingReport(credential)
578
599
  },
579
- finding: note(`Remote Mac ${entry} has not approved this Mac for builds yet`, `Requested at ${credential.requestedAt}.`, approval(entry, credential.deviceId))
600
+ finding: note(`Remote Mac ${entry} has not approved this Mac for builds yet`, requestedLine(credential), approval(entry, credential.deviceId))
580
601
  };
581
602
  }
582
603
  if ("error" in reply && reply.error.code === "unauthorized") {
583
604
  if (fix) return requestAccess(entry, peer, parsed.port, deviceName, io);
605
+ if (isLapsed(credential)) return {
606
+ report: {
607
+ ...paired,
608
+ state: "lapsed"
609
+ },
610
+ finding: note(`Build request to ${entry} lapsed`, lapsedLine(entry, credential), "Run `stim doctor --fix` to ask again.")
611
+ };
584
612
  return {
585
613
  report: {
586
614
  ...paired,
587
615
  state: "revoked"
588
616
  },
589
- finding: note(`Remote Mac ${entry} no longer accepts this Mac for builds`, `${reply.error.message} It was revoked, or the request lapsed before approval.`, "Run `stim doctor --fix` to ask again.")
617
+ finding: note(`Remote Mac ${entry} no longer accepts this Mac for builds`, `${reply.error.message} It was revoked or denied.`, "Run `stim doctor --fix` to ask again.")
590
618
  };
591
619
  }
592
620
  const reason = "failed" in reply ? reply.failed : "error" in reply ? reply.error.message : "unexpected reply";
@@ -675,8 +703,26 @@ function requireConfiguredMachine(selected, machines) {
675
703
  if (!match) throw new OffloadRefusal(selected, `not listed in remote.machines (configured: ${entries.join(", ") || "none"})`);
676
704
  return match;
677
705
  }
706
+ const settingSource = (flag, env, setting) => flag !== void 0 ? "flag" : env?.trim() ? "env" : setting !== void 0 ? "setting" : "default";
707
+ let buildMachineFlag;
708
+ /** The settings that decide where a build runs, with the layer each value came from, for this run's `--remote-build`. */
709
+ function buildPlacementSettings(selected) {
710
+ const flag = buildMachineFlag;
711
+ const remote = loadConfig$1()?.remote;
712
+ const rawMode = process.env.STIM_REMOTE_BUILD_MODE || remote?.buildMode;
713
+ return [{
714
+ key: "remote.build",
715
+ value: selected,
716
+ from: settingSource(flag, process.env.STIM_REMOTE_BUILD, remote?.build)
717
+ }, {
718
+ key: "remote.buildMode",
719
+ value: selected === "local" ? "off" : OFFLOAD_MODES.includes(rawMode) ? rawMode : "auto",
720
+ from: settingSource(void 0, process.env.STIM_REMOTE_BUILD_MODE, remote?.buildMode)
721
+ }];
722
+ }
678
723
  function resolveBuildPlacement(flag) {
679
724
  const remote = loadConfig$1()?.remote;
725
+ buildMachineFlag = flag;
680
726
  let selected = "auto";
681
727
  try {
682
728
  selected = resolveBuildMachine(flag, process.env.STIM_REMOTE_BUILD, remote?.build);
@@ -695,6 +741,23 @@ function resolveBuildPlacement(flag) {
695
741
  }
696
742
  }
697
743
  //#endregion
744
+ //#region src/remote-log.ts
745
+ let sink = null;
746
+ /** Where this run's failed requests to another Mac are written: its workspace log, so `stim logs` shows them. */
747
+ function setRemoteLogSink(next) {
748
+ sink = next;
749
+ }
750
+ function reportRemoteFailure(event, fields) {
751
+ try {
752
+ sink?.({
753
+ src: "build",
754
+ level: "warn",
755
+ event,
756
+ ...fields
757
+ });
758
+ } catch {}
759
+ }
760
+ //#endregion
698
761
  //#region src/offload/client.ts
699
762
  const CONNECT_TIMEOUT_MS = 1e4;
700
763
  const OFFER_TIMEOUT_MS = 2e4;
@@ -756,35 +819,43 @@ function offloadPlacement({ mode, machines, here, unsupported, selected = "auto"
756
819
  if (machines === 0) throw new OffloadRefusal(selected, "no remote Mac is paired");
757
820
  return {
758
821
  offload: true,
822
+ code: "named",
759
823
  reason: `selected with --remote-build ${selected}`
760
824
  };
761
825
  }
762
826
  if (selected === "local") return {
763
827
  offload: false,
828
+ code: "local-selected",
764
829
  reason: "--remote-build local"
765
830
  };
766
831
  if (mode === "off") return {
767
832
  offload: false,
833
+ code: "mode-off",
768
834
  reason: "remote.buildMode is off"
769
835
  };
770
836
  if (machines === 0) return {
771
837
  offload: false,
838
+ code: "no-remote-mac",
772
839
  reason: "no remote Mac is paired"
773
840
  };
774
841
  if (unsupported) return {
775
842
  offload: false,
843
+ code: "unsupported",
776
844
  reason: unsupported
777
845
  };
778
846
  if (mode === "force") return {
779
847
  offload: true,
848
+ code: "forced",
780
849
  reason: "remote.buildMode is force"
781
850
  };
782
851
  const busy = saturation(here);
783
852
  return busy ? {
784
853
  offload: true,
854
+ code: "this-mac-busy",
785
855
  reason: `this Mac is busy: ${busy} (${capacityText(here)})`
786
856
  } : {
787
857
  offload: false,
858
+ code: "this-mac-free",
788
859
  reason: `${capacityText(here)} here`
789
860
  };
790
861
  }
@@ -823,25 +894,26 @@ function offerProblems(offer, target) {
823
894
  function pickOffer({ mode, here, offers, target, selected = "auto" }) {
824
895
  const slotsFull = here.maxBuilds > 0 && here.builds >= here.maxBuilds;
825
896
  const reasons = [];
897
+ const candidates = [];
898
+ const skip = (machine, code, msg, detail) => {
899
+ reasons.push(`${machine}: ${msg}`);
900
+ candidates.push({
901
+ machine,
902
+ code,
903
+ msg,
904
+ ...detail ? { detail } : {}
905
+ });
906
+ };
826
907
  const ranked = [];
827
908
  offers.forEach(({ machine, offer, failure }, index) => {
828
909
  if (namedBuildMachine(selected) && machine !== selected) return;
829
- if (!offer) return void reasons.push(`${machine}: ${failure ?? "no offer"}`);
910
+ if (!offer) return skip(machine, "unreachable", failure ?? "no offer");
830
911
  const problems = offerProblems(offer, target);
831
- if (problems.length) {
832
- reasons.push(`${machine}: ${problems.map((problem) => problem.reason).join("; ")}`);
833
- return;
834
- }
912
+ if (problems.length) return skip(machine, problemCode(problems[0]), problems.map((problem) => problem.reason).join("; "), problems.map((problem) => problem.code));
835
913
  const load = offer.capacity.loadPerCore;
836
914
  if (selected === "auto" && mode === "auto" && !slotsFull) {
837
- if (typeof load !== "number") {
838
- reasons.push(`${machine}: capacity unknown (older stim-server) while this Mac has a free slot`);
839
- return;
840
- }
841
- if (load >= here.loadPerCore) {
842
- reasons.push(`${machine}: no less loaded (load ${load}/core there, ${here.loadPerCore}/core here)`);
843
- return;
844
- }
915
+ if (typeof load !== "number") return skip(machine, "load", "capacity unknown (older stim-server) while this Mac has a free slot");
916
+ if (load >= here.loadPerCore) return skip(machine, "load", `no less loaded (load ${load}/core there, ${here.loadPerCore}/core here)`);
845
917
  }
846
918
  const score = Number(offer.warm.checkout) + Number(offer.warm.dependencies) + Number(offer.warm.build);
847
919
  ranked.push({
@@ -849,13 +921,22 @@ function pickOffer({ mode, here, offers, target, selected = "auto" }) {
849
921
  score,
850
922
  load: typeof load === "number" ? load : Number.POSITIVE_INFINITY
851
923
  });
924
+ candidates.push({
925
+ machine,
926
+ code: "accepted",
927
+ msg: typeof load === "number" ? `can take the build (load ${load}/core there)` : "can take the build"
928
+ });
852
929
  });
853
930
  ranked.sort((a, b) => b.score - a.score || a.load - b.load);
854
931
  return {
855
932
  order: ranked.map((each) => each.index),
856
- reasons
933
+ reasons,
934
+ candidates
857
935
  };
858
936
  }
937
+ function problemCode({ code }) {
938
+ return code === "disk" || code === "busy" || code === "unreachable" ? code : code === "runtime" ? "no-matching-device" : "version-mismatch";
939
+ }
859
940
  const openSockets = /* @__PURE__ */ new Set();
860
941
  let closingOnExit = false;
861
942
  /**
@@ -938,7 +1019,32 @@ var BuildConnection = class BuildConnection {
938
1019
  socket.on("error", () => {});
939
1020
  }
940
1021
  /** Resolves why it could not connect; `refused` when the machine answered and turned this Mac away. */
941
- static open(target, token, timeoutMs, capability = "build") {
1022
+ static async open(target, token, timeoutMs, capability = "build") {
1023
+ const started = performance.now();
1024
+ const result = await BuildConnection.connect(target, token, timeoutMs, capability);
1025
+ const ms = Math.round(performance.now() - started);
1026
+ debugLog.log("remote_connect", {
1027
+ host: target.host,
1028
+ capability,
1029
+ ms,
1030
+ timeoutMs,
1031
+ ...result instanceof BuildConnection ? { ok: true } : {
1032
+ ok: false,
1033
+ failure: result.failure,
1034
+ code: result.code
1035
+ }
1036
+ });
1037
+ if (!(result instanceof BuildConnection)) reportRemoteFailure("remote_connect_failed", {
1038
+ host: target.host,
1039
+ capability,
1040
+ ms,
1041
+ timeoutMs,
1042
+ msg: result.failure,
1043
+ code: result.code
1044
+ });
1045
+ return result;
1046
+ }
1047
+ static connect(target, token, timeoutMs, capability) {
942
1048
  return new Promise((resolve) => {
943
1049
  const options = {
944
1050
  handshakeTimeout: timeoutMs,
@@ -968,7 +1074,8 @@ var BuildConnection = class BuildConnection {
968
1074
  protocol: 1,
969
1075
  client: {
970
1076
  name: "stim",
971
- version: "1"
1077
+ version: "1",
1078
+ runId: runId()
972
1079
  },
973
1080
  auth: { deviceToken: token }
974
1081
  }
@@ -1000,17 +1107,36 @@ var BuildConnection = class BuildConnection {
1000
1107
  message: this.closed
1001
1108
  } });
1002
1109
  const id = this.nextId++;
1110
+ const started = performance.now();
1111
+ const done = (reply) => {
1112
+ const ms = Math.round(performance.now() - started);
1113
+ debugLog.log("remote_request", {
1114
+ method,
1115
+ ms,
1116
+ ..."error" in reply ? {
1117
+ ok: false,
1118
+ code: reply.error.code
1119
+ } : { ok: true }
1120
+ });
1121
+ if ("error" in reply) reportRemoteFailure("remote_request_failed", {
1122
+ method,
1123
+ ms,
1124
+ code: reply.error.code,
1125
+ msg: reply.error.message
1126
+ });
1127
+ return reply;
1128
+ };
1003
1129
  return new Promise((resolve) => {
1004
1130
  const timer = setTimeout(() => {
1005
1131
  this.pending.delete(id);
1006
- resolve({ error: {
1132
+ resolve(done({ error: {
1007
1133
  code: "timeout",
1008
1134
  message: `${method} got no reply in ${timeoutMs / 1e3} s`
1009
- } });
1135
+ } }));
1010
1136
  }, timeoutMs);
1011
1137
  this.pending.set(id, (reply) => {
1012
1138
  clearTimeout(timer);
1013
- resolve(reply);
1139
+ resolve(done(reply));
1014
1140
  });
1015
1141
  this.socket.send(JSON.stringify({
1016
1142
  id,
@@ -1161,7 +1287,8 @@ async function probeMachine(credential, identity, { connectMs = CONNECT_TIMEOUT_
1161
1287
  };
1162
1288
  const reply = await connection.request("build.offer", {
1163
1289
  repo: identity.repo,
1164
- ...identity.lockfile ? { lockfile: identity.lockfile } : {}
1290
+ ...identity.lockfile ? { lockfile: identity.lockfile } : {},
1291
+ ...identity.rubyVersion ? { rubyVersion: identity.rubyVersion } : {}
1165
1292
  }, offerMs);
1166
1293
  const failure = replyError(reply);
1167
1294
  if (failure || !("result" in reply)) {
@@ -1181,7 +1308,7 @@ async function probeMachine(credential, identity, { connectMs = CONNECT_TIMEOUT_
1181
1308
  * Asks every paired machine for an offer in parallel and keeps the connection to the one `pickOffer` chooses, or
1182
1309
  * returns why none takes the build. A named selection probes only its credential and skips auto load gating.
1183
1310
  */
1184
- async function chooseBuildMachine({ projectRoot, target, mode, here, note, machines = pairedMachines(), selected = "auto" }) {
1311
+ async function chooseBuildMachine({ projectRoot, target, mode, here, note, machines = pairedMachines(), selected = "auto", onCandidates }) {
1185
1312
  const started = Date.now();
1186
1313
  let identity;
1187
1314
  try {
@@ -1190,8 +1317,12 @@ async function chooseBuildMachine({ projectRoot, target, mode, here, note, machi
1190
1317
  return `this app is not in a git checkout (${error.message.split("\n")[0]})`;
1191
1318
  }
1192
1319
  if (namedBuildMachine(selected)) machines = machines.filter((each) => each.machine === selected).slice(0, 1);
1193
- const asked = await Promise.all(machines.map((credential) => probeMachine(credential, identity)));
1194
- const { order, reasons } = pickOffer({
1320
+ const rubyVersion = readRubyVersion(projectRoot) ?? void 0;
1321
+ const asked = await Promise.all(machines.map((credential) => probeMachine(credential, {
1322
+ ...identity,
1323
+ rubyVersion
1324
+ })));
1325
+ const { order, reasons, candidates } = pickOffer({
1195
1326
  selected,
1196
1327
  mode,
1197
1328
  here,
@@ -1205,6 +1336,7 @@ async function chooseBuildMachine({ projectRoot, target, mode, here, note, machi
1205
1336
  asked.forEach((each, at) => {
1206
1337
  if (!order.includes(at) && "connection" in each) each.connection.close();
1207
1338
  });
1339
+ onCandidates?.(candidates);
1208
1340
  if (order.length === 0) return reasons.length ? reasons.join("; ") : "no remote Mac is paired";
1209
1341
  for (const reason of reasons) note(reason);
1210
1342
  const [first, ...rest] = order.map((at) => {
@@ -1606,7 +1738,10 @@ function offloadCheck(projectRoot, targets) {
1606
1738
  reason: `this app is not in a git checkout (${identity})`
1607
1739
  }]
1608
1740
  };
1609
- const probe = await probeMachine(credential, identity, {
1741
+ const probe = await probeMachine(credential, {
1742
+ ...identity,
1743
+ rubyVersion: readRubyVersion(projectRoot) ?? void 0
1744
+ }, {
1610
1745
  connectMs: DOCTOR_CONNECT_MS,
1611
1746
  offerMs: DOCTOR_OFFER_MS
1612
1747
  });
@@ -1634,4 +1769,4 @@ function offloadCheck(projectRoot, targets) {
1634
1769
  };
1635
1770
  }
1636
1771
  //#endregion
1637
- export { realIo as S, configuredMachines as _, offloadBuild as a, parseMachine$1 as b, placementLoad as c, OffloadRefusal as d, namedBuildMachine as f, inspectBuildMachines as g, resolveBuildPlacement as h, closeOffload as i, remotePhaseText as l, resolveBuildMachine as m, buildPlacementCandidates as n, offloadCheck as o, parseBuildMachineOption as p, chooseBuildMachine as r, offloadPlacement as s, BuildConnection as t, simulatorRuntime as u, hostingMachines as v, pinnedEndpoint$1 as x, inspectDeviceHostMachines as y };
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 };