stim 1.17.2 → 1.19.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.
- package/README.md +50 -1
- package/dist/{activity-IRvJY_ru.mjs → activity-BSw0x84R.mjs} +4 -4
- package/dist/{agent-device-usage-output-BK41vnzN.mjs → agent-device-usage-output-Bjxu_VmX.mjs} +8 -8
- package/dist/{android-B9yaNKwa.mjs → android-BXc6BmEi.mjs} +658 -404
- package/dist/{android-B_YNZy61.mjs → android-BdQC6zs1.mjs} +9 -9
- package/dist/android-BvV1gGdN.mjs +4 -0
- package/dist/{android-DE4v59Mb.mjs → android-DE5g_5gv.mjs} +2 -2
- package/dist/{android-cas-ZFpjJBL_.mjs → android-cas-C87geAg-.mjs} +5 -5
- package/dist/android-cas-compiler.mjs +2 -2
- package/dist/api-DIf_s86s.mjs +150 -0
- package/dist/api-run.d.mts +1 -0
- package/dist/api-run.mjs +145 -0
- package/dist/api.d.mts +218 -0
- package/dist/api.mjs +2 -0
- package/dist/{app-install-VPkI5WEg.mjs → app-install-CFI4ptUL.mjs} +33 -13
- package/dist/{attempt-Bk2ouQVL.mjs → attempt-DW-mQqM7.mjs} +14 -4
- package/dist/{web-Dl0KSRHf.mjs → browser-web-CDhqQxhv.mjs} +138 -176
- package/dist/{budget-DfcgFdQB.mjs → budget-Dz-kyXLv.mjs} +1391 -58
- package/dist/build-facts-B87vo-Im.d.mts +142 -0
- package/dist/{cache-manifest-DnUHBJaU.mjs → cache-manifest-DiwKSyv9.mjs} +3 -3
- package/dist/cache-manifest.mjs +1 -1
- package/dist/{chrome-BrcVWQyD.mjs → chrome-BTUOo8p2.mjs} +1 -1
- package/dist/cli-C9Zvgbdc.mjs +68 -0
- package/dist/cli.mjs +1 -1
- package/dist/{client-SvBdgObD.mjs → client-CuUo8Nvg.mjs} +381 -49
- package/dist/collector-run.d.mts +6 -0
- package/dist/collector-run.mjs +9 -9
- package/dist/{command-output-CDAIFD-8.mjs → command-output-Bt8ygHE7.mjs} +2 -2
- package/dist/{config-D0DbpZtM.mjs → config-DC5jEtd1.mjs} +9 -5
- package/dist/{created-devices-CpvzBL25.mjs → created-devices-D0s26Hx5.mjs} +2 -2
- package/dist/{dependency-state-CLcBHH2M.mjs → dependency-state-DKaS1OM9.mjs} +2 -2
- package/dist/{detached-entry-CiTEgMR1.mjs → detached-entry-pB_IPfV4.mjs} +1 -1
- package/dist/{dev-client-B-XC4uRH.mjs → dev-client-CDY_zAmD.mjs} +2 -2
- package/dist/{device-C8IOoJPv.mjs → device-BHw0WlHl.mjs} +8 -8
- package/dist/{device-capacity-ZC4XSZ4R.mjs → device-capacity-BoN1jQkO.mjs} +25 -14
- package/dist/device-host-worker.mjs +19 -19
- package/dist/{gradle-dJpre3Yn.mjs → device-ios-B2ruohE-.mjs} +661 -107
- package/dist/{device-lease-DpoS36_u.mjs → device-lease-Cu7PtQpZ.mjs} +9 -9
- package/dist/{device-lease-run-B8YltIH_.mjs → device-lease-run-D38SnZtt.mjs} +4 -4
- package/dist/{device-pool-3o7s5lo4.mjs → device-pool-CP4rAsHx.mjs} +3 -3
- package/dist/{device-remote-hFSkpfQu.mjs → device-remote-B5GiUifk.mjs} +15 -16
- package/dist/{doctor-CFv1fCrF.mjs → doctor-PRJH94v6.mjs} +61 -34
- package/dist/doctor-watchman-wZhyTdp4.mjs +259 -0
- package/dist/{error-diagnostics-C-NMnhrk.mjs → error-diagnostics--v5HGOU0.mjs} +11 -11
- package/dist/{exec-CaErVv7t.mjs → exec-i8GDPwq9.mjs} +93 -6
- package/dist/{gc-DEmkg3Xh.mjs → gc-CyHMcKcn.mjs} +25 -471
- package/dist/{guide-CSmhMTf8.mjs → guide-dRaQJJk9.mjs} +681 -507
- package/dist/{hosted-android-E1Heoqnv.mjs → hosted-android-Bs7fytj7.mjs} +3 -3
- package/dist/{hosted-client-Bp4knSwu.mjs → hosted-client-C5G4h_OJ.mjs} +2 -2
- package/dist/{hosted-ios-BnbeYOUD.mjs → hosted-ios-Bik035eL.mjs} +1 -1
- package/dist/{hosted-logs-OcgIBvCT.mjs → hosted-logs-CI9Az9a4.mjs} +9 -9
- package/dist/{hosted-macos-DcxKjtfH.mjs → hosted-macos-DABm3ScO.mjs} +5 -5
- package/dist/{hosted-native-1Ebbig4w.mjs → hosted-native-DbV2aUVw.mjs} +13 -9
- package/dist/{idle-shutdown-IGyNV8Cr.mjs → idle-shutdown-DYiMBn34.mjs} +7 -8
- package/dist/{ios-DSfnPfse.mjs → ios-BWbHFZVz.mjs} +19 -7
- package/dist/{ios-Cr1h5gnO.mjs → ios-DCrkgXEM.mjs} +1 -1
- package/dist/ios-DYUr9LGc.mjs +8 -0
- package/dist/ios-ITYno9rO.mjs +2241 -0
- package/dist/{ios-device-pAN4t54-.mjs → ios-device-CJ_ZOGky.mjs} +3 -3
- package/dist/{ios-device-Ee7sDXfp.mjs → ios-device-CrW8aMb5.mjs} +1 -1
- package/dist/{ios-state-8L3lgum6.mjs → ios-state-By0XuLTQ.mjs} +1 -1
- package/dist/{launch-verify-lTnWZikR.mjs → launch-verify-DCveSE6d.mjs} +5 -5
- package/dist/{logs-BAaS7gS2.mjs → logs-Bao1HOMy.mjs} +13 -13
- package/dist/{macos-BIRQS90f.mjs → macos-ciyBdTY5.mjs} +194 -132
- package/dist/macos-nIOATJmR.mjs +2 -0
- package/dist/macos-run.mjs +1 -1
- package/dist/maintenance-run.mjs +349 -47
- package/dist/manifest-CX0d1IkZ.mjs +148 -0
- package/dist/{metro-g8NqiJp-.mjs → metro-DI2AjPF_.mjs} +196 -43
- package/dist/{metro-gateway-C-ijGI2F.mjs → metro-gateway-BxpJLIwh.mjs} +4 -4
- package/dist/{metro-store-CDDZHtPl.mjs → metro-store-BAHFcJm6.mjs} +16 -2
- package/dist/{build-plan-BDsSi09V.mjs → miss-reason-CjQCOKqq.mjs} +207 -249
- package/dist/{named-ports-C8m8EQs2.mjs → named-ports-B4iWH4E-.mjs} +70 -8
- package/dist/{native-runtime-BEK171xQ.mjs → native-runtime-BGxsbfny.mjs} +6 -6
- package/dist/{ndjson-PclOIwVY.mjs → ndjson-LGZAMsqa.mjs} +8 -4
- package/dist/offload-worker.d.mts +3 -45
- package/dist/offload-worker.mjs +11 -11
- package/dist/{ownership-DyRQru0q.mjs → ownership-EB0ySdGK.mjs} +3 -3
- package/dist/{page-QF_Ke10s.mjs → page-Bv9s2KwY.mjs} +1 -1
- package/dist/placement-log-Dxe34rJm.mjs +88 -0
- package/dist/plan-placement-BVY59qa8.mjs +290 -0
- package/dist/{ports-4nYqxPWC.mjs → ports-ByPc0_97.mjs} +3 -3
- package/dist/{prebuild-D7RUMkwN.mjs → prebuild-BKJ3wJI1.mjs} +5 -5
- package/dist/preview-BbSottmO.mjs +986 -0
- package/dist/{rolldown-runtime-BsOwBuO_.mjs → process-identity-Biv3arCO.mjs} +11 -1
- package/dist/project-B5eQ1USs.mjs +612 -0
- package/dist/{pull-request-pxnDCQzn.mjs → pull-request-IHNdMnG8.mjs} +1 -1
- package/dist/pull-requests.mjs +1 -1
- package/dist/react-native-ios-BMh2i25I.mjs +2 -0
- package/dist/react-native-ios-CSpIxexZ.mjs +2083 -0
- package/dist/{recordings-BNgsD1Tr.mjs → recordings-Bv8McZN4.mjs} +1 -1
- package/dist/{reload-DcRxj3qG.mjs → reload-rAPdqeRR.mjs} +14 -15
- package/dist/{remote-cache-Ci4DQYYj.mjs → remote-cache-N3hAEaAL.mjs} +5 -5
- package/dist/{run-CkE9y0nc.mjs → run-lAkle1-c.mjs} +5 -5
- package/dist/{server-bare-BkrSxSsZ.mjs → server-bare-CkIjnegq.mjs} +5 -6
- package/dist/server-expo-DGsdUGX2.mjs +2 -0
- package/dist/{server-expo-CHTyXD0G.mjs → server-expo-n3tUi3Em.mjs} +5 -6
- package/dist/{settings-SR9OmW9h.mjs → settings-2am7TSxp.mjs} +5 -5
- package/dist/{settings-Cfd-0r2d.mjs → settings-CxgngUZk.mjs} +116 -103
- package/dist/{settings-BBpMcDbD.mjs → settings-Djl-mpjX.mjs} +1 -1
- package/dist/settings.schema.json +198 -9
- package/dist/{simslim-BWNrc3M3.mjs → simslim-D9LkJxIO.mjs} +7 -7
- package/dist/{slot-launch-DmjBmpQV.mjs → slot-launch-HV_KtPIw.mjs} +6 -6
- package/dist/{spawn-claims-D0XfSe8u.mjs → spawn-claims-Kqiifsk2.mjs} +1 -1
- package/dist/{spawn-entry-jw19m8Nl.mjs → spawn-entry-Dcb7sDUz.mjs} +1 -0
- package/dist/{stage-CzaiwFsI.mjs → stage-BgaZJVZY.mjs} +14 -3
- package/dist/{start-AlfCPRVU.mjs → start-D8NbxNBb.mjs} +1 -1
- package/dist/{start-DUz3mYdb.mjs → start-DtxthMfI.mjs} +34 -27
- package/dist/{state-PeQiJuIu.mjs → state-D2KtnALG.mjs} +4 -4
- package/dist/{state-DyaNTgfR.mjs → state-Tp4uImDS.mjs} +5 -3
- package/dist/{state-B4PQFXMW.d.mts → state-lKh8shBp.d.mts} +2 -3
- package/dist/{stats-Bq4fJOdB.mjs → stats-8EJ5ivFT.mjs} +6 -6
- package/dist/{status-DlKvVJBE.mjs → status-BEeL6IKz.mjs} +6 -4
- package/dist/{status-BA2pehwf.mjs → status-BOyp0CUl.mjs} +31 -34
- package/dist/{stim-desktop-Cnz8CPnl.mjs → stim-desktop-C65jk_q2.mjs} +1 -1
- package/dist/{stim-installations-QMEI32v1.mjs → stim-installations-DbmUjsLv.mjs} +1 -1
- package/dist/stop-D0UIpZuh.mjs +72 -0
- package/dist/{stop-zF1y04f2.mjs → stop-DRipAg-e.mjs} +2 -2
- package/dist/{stop-DQIasgKp.mjs → stop-zDlknFoz.mjs} +79 -33
- package/dist/supervisor-run.d.mts +24 -22
- package/dist/supervisor-run.mjs +21 -22
- package/dist/{support-CsJNVfOc.mjs → support-CNqYkV0y.mjs} +7 -7
- package/dist/{support-WvD__tSV.mjs → support-DsNaFg6D.mjs} +5 -5
- package/dist/swiftpm-macos-aJzPAj1t.mjs +119 -0
- package/dist/{toolchain-DNyR3NPq.mjs → toolchain-C3xSoyUl.mjs} +475 -269
- package/dist/{trigger-Bemu8ytw.mjs → trigger-CAhmG-4s.mjs} +6 -6
- package/dist/trigger-CoalsMTV.mjs +2 -0
- package/dist/{warm-progress-DF4JRvYI.mjs → warm-progress-pFLB2e6P.mjs} +135 -38
- package/dist/{status-CzS4bPO9.mjs → watchman-BL1IdOtq.mjs} +75 -7
- package/dist/web-DFpFeZu5.mjs +2 -0
- package/dist/web-DbbrPUn0.mjs +127 -0
- package/dist/web-run.mjs +10 -10
- package/dist/{workspace-state-DqcCbpE7.mjs → workspace-state-CR9X86xJ.mjs} +3 -3
- package/dist/{ownership-BMLqyBgJ.mjs → workspaces-Djb2gfi9.mjs} +661 -23
- package/dist/{worktree-Be_7NLKy.mjs → worktree-BL4M1rhB.mjs} +252 -50
- package/dist/{worktree-j3IcFAI5.mjs → worktree-DV-A0LNL.mjs} +1 -1
- package/package.json +10 -5
- package/shim/bundle-response.cjs +2 -1
- package/shim/bundle-response.d.cts +1 -1
- package/shim/expo-metro-config.cjs +1 -1
- package/dist/build-progress-D0WkjCPd.mjs +0 -1036
- package/dist/build-slots-41v_d2x9.mjs +0 -142
- package/dist/cli-CYXb29oE.mjs +0 -51
- package/dist/deps-iKw0eD4P.mjs +0 -438
- package/dist/device-ios-C2Um-XUR.mjs +0 -412
- package/dist/errors-B6W7FkxQ.mjs +0 -15
- package/dist/guide-status-Du-ZQ3FD.mjs +0 -125
- package/dist/idle-BXG__1BA.mjs +0 -176
- package/dist/in-use-CvGOUn-x.mjs +0 -130
- package/dist/ios-BJ4XUSe1.mjs +0 -3867
- package/dist/native-run-D1Q56rAD.mjs +0 -158
- package/dist/preview-B-g9d7kV.mjs +0 -318
- package/dist/process-identity-BmRm1qV0.mjs +0 -12
- package/dist/project-D47Rh0ly.mjs +0 -276
- package/dist/server-expo-CeakhI8P.mjs +0 -2
- package/dist/state-BKa8wNN5.mjs +0 -148
- package/dist/stop-BIiM49EY.mjs +0 -48
- package/dist/stop-cause-rhmGfwvt.mjs +0 -97
- package/dist/trigger-CXts6qt1.mjs +0 -2
- package/dist/watchman-APYjXSQa.mjs +0 -48
- package/dist/workspace-process-lock-2mNzOlaB.mjs +0 -58
- package/dist/workspaces-Bcib97-6.mjs +0 -306
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
import { t as getExecutor } from "./exec-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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";
|
|
6
9
|
import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readlinkSync, realpathSync, renameSync, rmSync, writeFileSync, writeSync } from "node:fs";
|
|
7
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";
|
|
8
13
|
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
14
|
import { releaseClaim, tryAcquireClaim } from "@stim-cli/core/ownership-claim";
|
|
11
|
-
import {
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
12
17
|
import { WebSocket } from "ws";
|
|
13
18
|
//#region src/offload/access-ticket.ts
|
|
14
19
|
function readAccessTicket() {
|
|
@@ -689,6 +694,9 @@ function parseBuildMachineOption(value) {
|
|
|
689
694
|
throw new InvalidArgumentError(error.message);
|
|
690
695
|
}
|
|
691
696
|
}
|
|
697
|
+
function requireLocalBuild(selected) {
|
|
698
|
+
if (selected === "auto" && !automaticMachineEnabled("build", "local")) throw new OffloadRefusal("auto", "local is disabled in remote.buildPoolDisabled; enable it or select a machine explicitly");
|
|
699
|
+
}
|
|
692
700
|
function namedBuildMachine(selected) {
|
|
693
701
|
return selected !== "auto" && selected !== "local";
|
|
694
702
|
}
|
|
@@ -703,8 +711,26 @@ function requireConfiguredMachine(selected, machines) {
|
|
|
703
711
|
if (!match) throw new OffloadRefusal(selected, `not listed in remote.machines (configured: ${entries.join(", ") || "none"})`);
|
|
704
712
|
return match;
|
|
705
713
|
}
|
|
714
|
+
const settingSource = (flag, env, setting) => flag !== void 0 ? "flag" : env?.trim() ? "env" : setting !== void 0 ? "setting" : "default";
|
|
715
|
+
let buildMachineFlag;
|
|
716
|
+
/** The settings that decide where a build runs, with the layer each value came from, for this run's `--remote-build`. */
|
|
717
|
+
function buildPlacementSettings(selected) {
|
|
718
|
+
const flag = buildMachineFlag;
|
|
719
|
+
const remote = loadConfig$1()?.remote;
|
|
720
|
+
const rawMode = process.env.STIM_REMOTE_BUILD_MODE || remote?.buildMode;
|
|
721
|
+
return [{
|
|
722
|
+
key: "remote.build",
|
|
723
|
+
value: selected,
|
|
724
|
+
from: settingSource(flag, process.env.STIM_REMOTE_BUILD, remote?.build)
|
|
725
|
+
}, {
|
|
726
|
+
key: "remote.buildMode",
|
|
727
|
+
value: selected === "local" ? "off" : OFFLOAD_MODES.includes(rawMode) ? rawMode : "auto",
|
|
728
|
+
from: settingSource(void 0, process.env.STIM_REMOTE_BUILD_MODE, remote?.buildMode)
|
|
729
|
+
}];
|
|
730
|
+
}
|
|
706
731
|
function resolveBuildPlacement(flag) {
|
|
707
732
|
const remote = loadConfig$1()?.remote;
|
|
733
|
+
buildMachineFlag = flag;
|
|
708
734
|
let selected = "auto";
|
|
709
735
|
try {
|
|
710
736
|
selected = resolveBuildMachine(flag, process.env.STIM_REMOTE_BUILD, remote?.build);
|
|
@@ -723,6 +749,176 @@ function resolveBuildPlacement(flag) {
|
|
|
723
749
|
}
|
|
724
750
|
}
|
|
725
751
|
//#endregion
|
|
752
|
+
//#region src/remote-log.ts
|
|
753
|
+
let sink = null;
|
|
754
|
+
/** Where this run's failed requests to another Mac are written: its workspace log, so `stim logs` shows them. */
|
|
755
|
+
function setRemoteLogSink(next) {
|
|
756
|
+
sink = next;
|
|
757
|
+
}
|
|
758
|
+
function reportRemoteFailure(event, fields) {
|
|
759
|
+
try {
|
|
760
|
+
sink?.({
|
|
761
|
+
src: "build",
|
|
762
|
+
level: "warn",
|
|
763
|
+
event,
|
|
764
|
+
...fields
|
|
765
|
+
});
|
|
766
|
+
} catch {}
|
|
767
|
+
}
|
|
768
|
+
//#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
|
|
726
922
|
//#region src/offload/client.ts
|
|
727
923
|
const CONNECT_TIMEOUT_MS = 1e4;
|
|
728
924
|
const OFFER_TIMEOUT_MS = 2e4;
|
|
@@ -750,9 +946,11 @@ function offloadMode(env = process.env) {
|
|
|
750
946
|
return OFFLOAD_MODES.includes(raw) ? raw : "auto";
|
|
751
947
|
}
|
|
752
948
|
function buildPlacementCandidates(selected) {
|
|
949
|
+
const config = loadConfig$1();
|
|
753
950
|
return {
|
|
951
|
+
localEnabled: selected !== "auto" || automaticMachineEnabled("build", "local", config),
|
|
754
952
|
mode: selected === "local" ? "off" : offloadMode(),
|
|
755
|
-
machines: selected === "local" ? [] : namedBuildMachine(selected) ? pairedMachines([selected]).slice(0, 1) : pairedMachines()
|
|
953
|
+
machines: selected === "local" ? [] : namedBuildMachine(selected) ? pairedMachines([selected]).slice(0, 1) : pairedMachines().filter((entry) => automaticMachineEnabled("build", entry.machine, config))
|
|
756
954
|
};
|
|
757
955
|
}
|
|
758
956
|
function remotePhaseText(name, msg, machine) {
|
|
@@ -778,41 +976,58 @@ function placementLoad(choice) {
|
|
|
778
976
|
* this Mac has a free `concurrency.maxBuilds` slot and its load per core is under `server.maxLoadPerCore`.
|
|
779
977
|
* A named selection ignores local capacity and mode, and refuses an unsupported build or missing pairing.
|
|
780
978
|
*/
|
|
781
|
-
function offloadPlacement({ mode, machines, here, unsupported, selected = "auto" }) {
|
|
979
|
+
function offloadPlacement({ mode, machines, here, unsupported, localEnabled = true, selected = "auto" }) {
|
|
782
980
|
if (namedBuildMachine(selected)) {
|
|
783
981
|
if (unsupported) throw new OffloadRefusal(selected, `${unsupported}, so a named remote Mac cannot take it`);
|
|
784
982
|
if (machines === 0) throw new OffloadRefusal(selected, "no remote Mac is paired");
|
|
785
983
|
return {
|
|
786
984
|
offload: true,
|
|
985
|
+
code: "named",
|
|
787
986
|
reason: `selected with --remote-build ${selected}`
|
|
788
987
|
};
|
|
789
988
|
}
|
|
790
989
|
if (selected === "local") return {
|
|
791
990
|
offload: false,
|
|
991
|
+
code: "local-selected",
|
|
792
992
|
reason: "--remote-build local"
|
|
793
993
|
};
|
|
994
|
+
if (!localEnabled) {
|
|
995
|
+
const reason = mode === "off" ? "remote.buildMode is off" : unsupported ?? (machines === 0 ? "no enabled remote Mac is paired" : null);
|
|
996
|
+
if (reason) throw new OffloadRefusal("auto", `${reason}; local is disabled in remote.buildPoolDisabled`);
|
|
997
|
+
return {
|
|
998
|
+
offload: true,
|
|
999
|
+
code: "local-disabled",
|
|
1000
|
+
reason: "local is disabled in remote.buildPoolDisabled"
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
794
1003
|
if (mode === "off") return {
|
|
795
1004
|
offload: false,
|
|
1005
|
+
code: "mode-off",
|
|
796
1006
|
reason: "remote.buildMode is off"
|
|
797
1007
|
};
|
|
798
1008
|
if (machines === 0) return {
|
|
799
1009
|
offload: false,
|
|
1010
|
+
code: "no-remote-mac",
|
|
800
1011
|
reason: "no remote Mac is paired"
|
|
801
1012
|
};
|
|
802
1013
|
if (unsupported) return {
|
|
803
1014
|
offload: false,
|
|
1015
|
+
code: "unsupported",
|
|
804
1016
|
reason: unsupported
|
|
805
1017
|
};
|
|
806
1018
|
if (mode === "force") return {
|
|
807
1019
|
offload: true,
|
|
1020
|
+
code: "forced",
|
|
808
1021
|
reason: "remote.buildMode is force"
|
|
809
1022
|
};
|
|
810
1023
|
const busy = saturation(here);
|
|
811
1024
|
return busy ? {
|
|
812
1025
|
offload: true,
|
|
1026
|
+
code: "this-mac-busy",
|
|
813
1027
|
reason: `this Mac is busy: ${busy} (${capacityText(here)})`
|
|
814
1028
|
} : {
|
|
815
1029
|
offload: false,
|
|
1030
|
+
code: "this-mac-free",
|
|
816
1031
|
reason: `${capacityText(here)} here`
|
|
817
1032
|
};
|
|
818
1033
|
}
|
|
@@ -848,28 +1063,29 @@ function offerProblems(offer, target) {
|
|
|
848
1063
|
* A machine that reports no load (a stim-server older than capacity) takes an `auto` build only while
|
|
849
1064
|
* every local slot is busy. The rest rank warmest first, then least loaded.
|
|
850
1065
|
*/
|
|
851
|
-
function pickOffer({ mode, here, offers, target, selected = "auto" }) {
|
|
1066
|
+
function pickOffer({ mode, here, offers, target, localEnabled = true, selected = "auto" }) {
|
|
852
1067
|
const slotsFull = here.maxBuilds > 0 && here.builds >= here.maxBuilds;
|
|
853
1068
|
const reasons = [];
|
|
1069
|
+
const candidates = [];
|
|
1070
|
+
const skip = (machine, code, msg, detail) => {
|
|
1071
|
+
reasons.push(`${machine}: ${msg}`);
|
|
1072
|
+
candidates.push({
|
|
1073
|
+
machine,
|
|
1074
|
+
code,
|
|
1075
|
+
msg,
|
|
1076
|
+
...detail ? { detail } : {}
|
|
1077
|
+
});
|
|
1078
|
+
};
|
|
854
1079
|
const ranked = [];
|
|
855
1080
|
offers.forEach(({ machine, offer, failure }, index) => {
|
|
856
1081
|
if (namedBuildMachine(selected) && machine !== selected) return;
|
|
857
|
-
if (!offer) return
|
|
1082
|
+
if (!offer) return skip(machine, "unreachable", failure ?? "no offer");
|
|
858
1083
|
const problems = offerProblems(offer, target);
|
|
859
|
-
if (problems.length)
|
|
860
|
-
reasons.push(`${machine}: ${problems.map((problem) => problem.reason).join("; ")}`);
|
|
861
|
-
return;
|
|
862
|
-
}
|
|
1084
|
+
if (problems.length) return skip(machine, problemCode(problems[0]), problems.map((problem) => problem.reason).join("; "), problems.map((problem) => problem.code));
|
|
863
1085
|
const load = offer.capacity.loadPerCore;
|
|
864
|
-
if (selected === "auto" && mode === "auto" && !slotsFull) {
|
|
865
|
-
if (typeof load !== "number")
|
|
866
|
-
|
|
867
|
-
return;
|
|
868
|
-
}
|
|
869
|
-
if (load >= here.loadPerCore) {
|
|
870
|
-
reasons.push(`${machine}: no less loaded (load ${load}/core there, ${here.loadPerCore}/core here)`);
|
|
871
|
-
return;
|
|
872
|
-
}
|
|
1086
|
+
if (selected === "auto" && localEnabled && mode === "auto" && !slotsFull) {
|
|
1087
|
+
if (typeof load !== "number") return skip(machine, "load", "capacity unknown (older stim-server) while this Mac has a free slot");
|
|
1088
|
+
if (load >= here.loadPerCore) return skip(machine, "load", `no less loaded (load ${load}/core there, ${here.loadPerCore}/core here)`);
|
|
873
1089
|
}
|
|
874
1090
|
const score = Number(offer.warm.checkout) + Number(offer.warm.dependencies) + Number(offer.warm.build);
|
|
875
1091
|
ranked.push({
|
|
@@ -877,13 +1093,22 @@ function pickOffer({ mode, here, offers, target, selected = "auto" }) {
|
|
|
877
1093
|
score,
|
|
878
1094
|
load: typeof load === "number" ? load : Number.POSITIVE_INFINITY
|
|
879
1095
|
});
|
|
1096
|
+
candidates.push({
|
|
1097
|
+
machine,
|
|
1098
|
+
code: "accepted",
|
|
1099
|
+
msg: typeof load === "number" ? `can take the build (load ${load}/core there)` : "can take the build"
|
|
1100
|
+
});
|
|
880
1101
|
});
|
|
881
1102
|
ranked.sort((a, b) => b.score - a.score || a.load - b.load);
|
|
882
1103
|
return {
|
|
883
1104
|
order: ranked.map((each) => each.index),
|
|
884
|
-
reasons
|
|
1105
|
+
reasons,
|
|
1106
|
+
candidates
|
|
885
1107
|
};
|
|
886
1108
|
}
|
|
1109
|
+
function problemCode({ code }) {
|
|
1110
|
+
return code === "disk" || code === "busy" || code === "unreachable" ? code : code === "runtime" ? "no-matching-device" : "version-mismatch";
|
|
1111
|
+
}
|
|
887
1112
|
const openSockets = /* @__PURE__ */ new Set();
|
|
888
1113
|
let closingOnExit = false;
|
|
889
1114
|
/**
|
|
@@ -966,7 +1191,32 @@ var BuildConnection = class BuildConnection {
|
|
|
966
1191
|
socket.on("error", () => {});
|
|
967
1192
|
}
|
|
968
1193
|
/** Resolves why it could not connect; `refused` when the machine answered and turned this Mac away. */
|
|
969
|
-
static open(target, token, timeoutMs, capability = "build") {
|
|
1194
|
+
static async open(target, token, timeoutMs, capability = "build") {
|
|
1195
|
+
const started = performance.now();
|
|
1196
|
+
const result = await BuildConnection.connect(target, token, timeoutMs, capability);
|
|
1197
|
+
const ms = Math.round(performance.now() - started);
|
|
1198
|
+
debugLog.log("remote_connect", {
|
|
1199
|
+
host: target.host,
|
|
1200
|
+
capability,
|
|
1201
|
+
ms,
|
|
1202
|
+
timeoutMs,
|
|
1203
|
+
...result instanceof BuildConnection ? { ok: true } : {
|
|
1204
|
+
ok: false,
|
|
1205
|
+
failure: result.failure,
|
|
1206
|
+
code: result.code
|
|
1207
|
+
}
|
|
1208
|
+
});
|
|
1209
|
+
if (!(result instanceof BuildConnection)) reportRemoteFailure("remote_connect_failed", {
|
|
1210
|
+
host: target.host,
|
|
1211
|
+
capability,
|
|
1212
|
+
ms,
|
|
1213
|
+
timeoutMs,
|
|
1214
|
+
msg: result.failure,
|
|
1215
|
+
code: result.code
|
|
1216
|
+
});
|
|
1217
|
+
return result;
|
|
1218
|
+
}
|
|
1219
|
+
static connect(target, token, timeoutMs, capability) {
|
|
970
1220
|
return new Promise((resolve) => {
|
|
971
1221
|
const options = {
|
|
972
1222
|
handshakeTimeout: timeoutMs,
|
|
@@ -996,7 +1246,8 @@ var BuildConnection = class BuildConnection {
|
|
|
996
1246
|
protocol: 1,
|
|
997
1247
|
client: {
|
|
998
1248
|
name: "stim",
|
|
999
|
-
version: "1"
|
|
1249
|
+
version: "1",
|
|
1250
|
+
runId: runId()
|
|
1000
1251
|
},
|
|
1001
1252
|
auth: { deviceToken: token }
|
|
1002
1253
|
}
|
|
@@ -1028,17 +1279,36 @@ var BuildConnection = class BuildConnection {
|
|
|
1028
1279
|
message: this.closed
|
|
1029
1280
|
} });
|
|
1030
1281
|
const id = this.nextId++;
|
|
1282
|
+
const started = performance.now();
|
|
1283
|
+
const done = (reply) => {
|
|
1284
|
+
const ms = Math.round(performance.now() - started);
|
|
1285
|
+
debugLog.log("remote_request", {
|
|
1286
|
+
method,
|
|
1287
|
+
ms,
|
|
1288
|
+
..."error" in reply ? {
|
|
1289
|
+
ok: false,
|
|
1290
|
+
code: reply.error.code
|
|
1291
|
+
} : { ok: true }
|
|
1292
|
+
});
|
|
1293
|
+
if ("error" in reply) reportRemoteFailure("remote_request_failed", {
|
|
1294
|
+
method,
|
|
1295
|
+
ms,
|
|
1296
|
+
code: reply.error.code,
|
|
1297
|
+
msg: reply.error.message
|
|
1298
|
+
});
|
|
1299
|
+
return reply;
|
|
1300
|
+
};
|
|
1031
1301
|
return new Promise((resolve) => {
|
|
1032
1302
|
const timer = setTimeout(() => {
|
|
1033
1303
|
this.pending.delete(id);
|
|
1034
|
-
resolve({ error: {
|
|
1304
|
+
resolve(done({ error: {
|
|
1035
1305
|
code: "timeout",
|
|
1036
1306
|
message: `${method} got no reply in ${timeoutMs / 1e3} s`
|
|
1037
|
-
} });
|
|
1307
|
+
} }));
|
|
1038
1308
|
}, timeoutMs);
|
|
1039
1309
|
this.pending.set(id, (reply) => {
|
|
1040
1310
|
clearTimeout(timer);
|
|
1041
|
-
resolve(reply);
|
|
1311
|
+
resolve(done(reply));
|
|
1042
1312
|
});
|
|
1043
1313
|
this.socket.send(JSON.stringify({
|
|
1044
1314
|
id,
|
|
@@ -1048,6 +1318,7 @@ var BuildConnection = class BuildConnection {
|
|
|
1048
1318
|
});
|
|
1049
1319
|
}
|
|
1050
1320
|
async sendBinary(frame) {
|
|
1321
|
+
if (this.closed) return;
|
|
1051
1322
|
this.socket.send(frame, { binary: true });
|
|
1052
1323
|
while (this.socket.bufferedAmount > MAX_BUFFERED && !this.closed) await new Promise((resolve) => setTimeout(resolve, 5));
|
|
1053
1324
|
}
|
|
@@ -1085,6 +1356,14 @@ var BuildConnection = class BuildConnection {
|
|
|
1085
1356
|
}
|
|
1086
1357
|
forget() {
|
|
1087
1358
|
this.ended = true;
|
|
1359
|
+
this.closed ??= "the connection was closed by this client";
|
|
1360
|
+
for (const resolve of this.pending.values()) resolve({ error: {
|
|
1361
|
+
code: "closed",
|
|
1362
|
+
message: this.closed
|
|
1363
|
+
} });
|
|
1364
|
+
this.pending.clear();
|
|
1365
|
+
this.progress = null;
|
|
1366
|
+
this.binary = null;
|
|
1088
1367
|
clearInterval(this.keepalive);
|
|
1089
1368
|
this.socket.removeAllListeners("close");
|
|
1090
1369
|
openSockets.delete(this.socket);
|
|
@@ -1189,7 +1468,8 @@ async function probeMachine(credential, identity, { connectMs = CONNECT_TIMEOUT_
|
|
|
1189
1468
|
};
|
|
1190
1469
|
const reply = await connection.request("build.offer", {
|
|
1191
1470
|
repo: identity.repo,
|
|
1192
|
-
...identity.lockfile ? { lockfile: identity.lockfile } : {}
|
|
1471
|
+
...identity.lockfile ? { lockfile: identity.lockfile } : {},
|
|
1472
|
+
...identity.rubyVersion ? { rubyVersion: identity.rubyVersion } : {}
|
|
1193
1473
|
}, offerMs);
|
|
1194
1474
|
const failure = replyError(reply);
|
|
1195
1475
|
if (failure || !("result" in reply)) {
|
|
@@ -1209,7 +1489,7 @@ async function probeMachine(credential, identity, { connectMs = CONNECT_TIMEOUT_
|
|
|
1209
1489
|
* Asks every paired machine for an offer in parallel and keeps the connection to the one `pickOffer` chooses, or
|
|
1210
1490
|
* returns why none takes the build. A named selection probes only its credential and skips auto load gating.
|
|
1211
1491
|
*/
|
|
1212
|
-
async function chooseBuildMachine({ projectRoot, target, mode, here, note, machines = pairedMachines(), selected = "auto" }) {
|
|
1492
|
+
async function chooseBuildMachine({ projectRoot, target, mode, here, note, machines = pairedMachines(), selected = "auto", onCandidates }) {
|
|
1213
1493
|
const started = Date.now();
|
|
1214
1494
|
let identity;
|
|
1215
1495
|
try {
|
|
@@ -1218,8 +1498,14 @@ async function chooseBuildMachine({ projectRoot, target, mode, here, note, machi
|
|
|
1218
1498
|
return `this app is not in a git checkout (${error.message.split("\n")[0]})`;
|
|
1219
1499
|
}
|
|
1220
1500
|
if (namedBuildMachine(selected)) machines = machines.filter((each) => each.machine === selected).slice(0, 1);
|
|
1221
|
-
|
|
1222
|
-
const
|
|
1501
|
+
else machines = machines.filter((each) => automaticMachineEnabled("build", each.machine));
|
|
1502
|
+
const rubyVersion = readRubyVersion(projectRoot) ?? void 0;
|
|
1503
|
+
const asked = await Promise.all(machines.map((credential) => probeMachine(credential, {
|
|
1504
|
+
...identity,
|
|
1505
|
+
rubyVersion
|
|
1506
|
+
})));
|
|
1507
|
+
const { order, reasons, candidates } = pickOffer({
|
|
1508
|
+
localEnabled: selected !== "auto" || automaticMachineEnabled("build", "local"),
|
|
1223
1509
|
selected,
|
|
1224
1510
|
mode,
|
|
1225
1511
|
here,
|
|
@@ -1233,6 +1519,7 @@ async function chooseBuildMachine({ projectRoot, target, mode, here, note, machi
|
|
|
1233
1519
|
asked.forEach((each, at) => {
|
|
1234
1520
|
if (!order.includes(at) && "connection" in each) each.connection.close();
|
|
1235
1521
|
});
|
|
1522
|
+
onCandidates?.(candidates);
|
|
1236
1523
|
if (order.length === 0) return reasons.length ? reasons.join("; ") : "no remote Mac is paired";
|
|
1237
1524
|
for (const reason of reasons) note(reason);
|
|
1238
1525
|
const [first, ...rest] = order.map((at) => {
|
|
@@ -1249,7 +1536,8 @@ async function chooseBuildMachine({ projectRoot, target, mode, here, note, machi
|
|
|
1249
1536
|
target,
|
|
1250
1537
|
offerMs: Date.now() - started,
|
|
1251
1538
|
identity,
|
|
1252
|
-
runnersUp: rest
|
|
1539
|
+
runnersUp: rest,
|
|
1540
|
+
automatic: selected === "auto"
|
|
1253
1541
|
};
|
|
1254
1542
|
}
|
|
1255
1543
|
const ARTIFACT_NAME = {
|
|
@@ -1262,7 +1550,7 @@ const ARTIFACT_NAME = {
|
|
|
1262
1550
|
* `RESUME_WINDOW_MS`, shorter than the machine's grace for a dropped connection, has passed. Returns why it
|
|
1263
1551
|
* could not.
|
|
1264
1552
|
*/
|
|
1265
|
-
async function resumeJob(credential, job, abandoned) {
|
|
1553
|
+
async function resumeJob(credential, job, abandoned, signal) {
|
|
1266
1554
|
const deadline = Date.now() + RESUME_WINDOW_MS;
|
|
1267
1555
|
let delay = RESUME_DELAY_MS;
|
|
1268
1556
|
let last = "no attempt";
|
|
@@ -1276,9 +1564,20 @@ async function resumeJob(credential, job, abandoned) {
|
|
|
1276
1564
|
if (connection.refused) return `the machine turned this Mac away (${connection.failure})`;
|
|
1277
1565
|
last = connection.failure;
|
|
1278
1566
|
} else {
|
|
1567
|
+
if (abandoned()) {
|
|
1568
|
+
connection.close();
|
|
1569
|
+
break;
|
|
1570
|
+
}
|
|
1279
1571
|
const early = [];
|
|
1280
1572
|
connection.onProgress((event) => early.push(event));
|
|
1573
|
+
const cancel = () => connection.close();
|
|
1574
|
+
signal?.addEventListener("abort", cancel, { once: true });
|
|
1281
1575
|
const reply = await connection.request("build.attach", { job }, OFFER_TIMEOUT_MS);
|
|
1576
|
+
signal?.removeEventListener("abort", cancel);
|
|
1577
|
+
if (abandoned()) {
|
|
1578
|
+
connection.close();
|
|
1579
|
+
break;
|
|
1580
|
+
}
|
|
1282
1581
|
if ("result" in reply) {
|
|
1283
1582
|
const outcome = isJsonObject(reply.result) ? reply.result.outcome : null;
|
|
1284
1583
|
return {
|
|
@@ -1294,8 +1593,8 @@ async function resumeJob(credential, job, abandoned) {
|
|
|
1294
1593
|
}
|
|
1295
1594
|
connection.drop();
|
|
1296
1595
|
}
|
|
1297
|
-
if (Date.now() + delay > deadline) break;
|
|
1298
|
-
await
|
|
1596
|
+
if (abandoned() || Date.now() + delay > deadline) break;
|
|
1597
|
+
await setTimeout$1(delay, void 0, { signal }).catch(() => {});
|
|
1299
1598
|
delay = Math.min(delay * 2, RESUME_MAX_DELAY_MS);
|
|
1300
1599
|
}
|
|
1301
1600
|
return `no connection to it within ${RESUME_WINDOW_MS / 6e4} min (${last})`;
|
|
@@ -1363,23 +1662,36 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
|
|
|
1363
1662
|
reason: reason.split("\n")[0].slice(0, 300)
|
|
1364
1663
|
};
|
|
1365
1664
|
};
|
|
1665
|
+
const signal = runCancellationSignal();
|
|
1666
|
+
const throwIfCancelled = () => {
|
|
1667
|
+
if (signal?.aborted) throw Object.assign(/* @__PURE__ */ new Error("The offloaded build was cancelled."), { code: "STIM_CANCELLED" });
|
|
1668
|
+
};
|
|
1669
|
+
let settle;
|
|
1670
|
+
let settled = false;
|
|
1671
|
+
const outcome = new Promise((resolve) => {
|
|
1672
|
+
settle = (value) => {
|
|
1673
|
+
settled = true;
|
|
1674
|
+
resolve(value);
|
|
1675
|
+
};
|
|
1676
|
+
});
|
|
1677
|
+
const cancel = () => {
|
|
1678
|
+
settle({
|
|
1679
|
+
ok: false,
|
|
1680
|
+
code: "cancelled"
|
|
1681
|
+
});
|
|
1682
|
+
closeOffload(choice);
|
|
1683
|
+
};
|
|
1684
|
+
signal?.addEventListener("abort", cancel, { once: true });
|
|
1366
1685
|
try {
|
|
1686
|
+
throwIfCancelled();
|
|
1367
1687
|
let job = null;
|
|
1368
1688
|
let early = [];
|
|
1369
|
-
let settle;
|
|
1370
|
-
let settled = false;
|
|
1371
|
-
const outcome = new Promise((resolve) => {
|
|
1372
|
-
settle = (value) => {
|
|
1373
|
-
settled = true;
|
|
1374
|
-
resolve(value);
|
|
1375
|
-
};
|
|
1376
|
-
});
|
|
1377
1689
|
let resuming = false;
|
|
1378
1690
|
const reattach = async (why) => {
|
|
1379
1691
|
if (resuming || settled) return;
|
|
1380
1692
|
resuming = true;
|
|
1381
1693
|
note(`offload: the connection to ${choice.machine} dropped (${why}); reattaching to the build there`);
|
|
1382
|
-
const resumed = await resumeJob(choice.credential, job, () => settled);
|
|
1694
|
+
const resumed = await resumeJob(choice.credential, job, () => settled, signal);
|
|
1383
1695
|
resuming = false;
|
|
1384
1696
|
if (settled) {
|
|
1385
1697
|
if (typeof resumed !== "string") resumed.connection.close();
|
|
@@ -1422,7 +1734,13 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
|
|
|
1422
1734
|
return true;
|
|
1423
1735
|
};
|
|
1424
1736
|
for (;;) {
|
|
1737
|
+
if (choice.automatic && !automaticMachineEnabled("build", choice.machine)) {
|
|
1738
|
+
const reason = `${choice.machine} is disabled in remote.buildPoolDisabled`;
|
|
1739
|
+
if (moveOn(reason)) continue;
|
|
1740
|
+
return fail(reason);
|
|
1741
|
+
}
|
|
1425
1742
|
const synced = await syncSource(choice.connection, identity, onEnter);
|
|
1743
|
+
throwIfCancelled();
|
|
1426
1744
|
if ("failure" in synced) {
|
|
1427
1745
|
if (moveOn(synced.failure)) continue;
|
|
1428
1746
|
return fail(synced.failure);
|
|
@@ -1432,6 +1750,8 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
|
|
|
1432
1750
|
workerStarted = Date.now();
|
|
1433
1751
|
early = [];
|
|
1434
1752
|
choice.connection.onProgress((event) => job === null ? early.push(event) : handle(event));
|
|
1753
|
+
if (choice.automatic) requireAutomaticMachine("build", choice.machine);
|
|
1754
|
+
throwIfCancelled();
|
|
1435
1755
|
const reply = await choice.connection.request("build.start", {
|
|
1436
1756
|
repo: identity.repo,
|
|
1437
1757
|
project: identity.project,
|
|
@@ -1454,6 +1774,7 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
|
|
|
1454
1774
|
},
|
|
1455
1775
|
stimBuild: choice.target.local.stimBuild
|
|
1456
1776
|
});
|
|
1777
|
+
throwIfCancelled();
|
|
1457
1778
|
if ("result" in reply) {
|
|
1458
1779
|
job = reply.result.job;
|
|
1459
1780
|
break;
|
|
@@ -1471,6 +1792,7 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
|
|
|
1471
1792
|
}), JOB_TIMEOUT_MS);
|
|
1472
1793
|
const result = await outcome;
|
|
1473
1794
|
clearTimeout(timer);
|
|
1795
|
+
throwIfCancelled();
|
|
1474
1796
|
const { connection, machine } = choice;
|
|
1475
1797
|
connection.onProgress(null);
|
|
1476
1798
|
const workerMs = Date.now() - workerStarted;
|
|
@@ -1479,6 +1801,7 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
|
|
|
1479
1801
|
const name = typeof artifact?.name === "string" ? artifact.name : "";
|
|
1480
1802
|
if (!ARTIFACT_NAME[request.platform].test(name) || typeof artifact.sha256 !== "string") return fail(`the machine reported no .${request.platform === "android" ? "apk" : "app"} artifact`);
|
|
1481
1803
|
onEnter("fetch");
|
|
1804
|
+
throwIfCancelled();
|
|
1482
1805
|
const fetchStarted = Date.now();
|
|
1483
1806
|
rmSync(stagingDir, {
|
|
1484
1807
|
recursive: true,
|
|
@@ -1508,6 +1831,7 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
|
|
|
1508
1831
|
const fetched = await connection.request("build.artifact", { job }, 9e5);
|
|
1509
1832
|
connection.onBinary(null);
|
|
1510
1833
|
closeSync(fd);
|
|
1834
|
+
throwIfCancelled();
|
|
1511
1835
|
const fetchFailure = replyError(fetched);
|
|
1512
1836
|
if (fetchFailure || !("result" in fetched)) return fail(`fetch: ${fetchFailure ?? "no reply"}`);
|
|
1513
1837
|
const digest = hash.digest("hex");
|
|
@@ -1520,6 +1844,7 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
|
|
|
1520
1844
|
"-C",
|
|
1521
1845
|
stagingDir
|
|
1522
1846
|
], { timeoutMs: 6e5 });
|
|
1847
|
+
throwIfCancelled();
|
|
1523
1848
|
rmSync(archive, { force: true });
|
|
1524
1849
|
const artifactPath = join(stagingDir, name);
|
|
1525
1850
|
if (request.platform === "ios" && !existsSync(join(artifactPath, "Info.plist"))) return fail(`fetch: ${name} has no Info.plist`);
|
|
@@ -1556,7 +1881,11 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
|
|
|
1556
1881
|
}
|
|
1557
1882
|
};
|
|
1558
1883
|
} catch (error) {
|
|
1884
|
+
closeOffload(choice);
|
|
1885
|
+
throwIfCancelled();
|
|
1559
1886
|
return fail(error.message.split("\n")[0] ?? String(error));
|
|
1887
|
+
} finally {
|
|
1888
|
+
signal?.removeEventListener("abort", cancel);
|
|
1560
1889
|
}
|
|
1561
1890
|
}
|
|
1562
1891
|
function packageName(projectRoot) {
|
|
@@ -1634,7 +1963,10 @@ function offloadCheck(projectRoot, targets) {
|
|
|
1634
1963
|
reason: `this app is not in a git checkout (${identity})`
|
|
1635
1964
|
}]
|
|
1636
1965
|
};
|
|
1637
|
-
const probe = await probeMachine(credential,
|
|
1966
|
+
const probe = await probeMachine(credential, {
|
|
1967
|
+
...identity,
|
|
1968
|
+
rubyVersion: readRubyVersion(projectRoot) ?? void 0
|
|
1969
|
+
}, {
|
|
1638
1970
|
connectMs: DOCTOR_CONNECT_MS,
|
|
1639
1971
|
offerMs: DOCTOR_OFFER_MS
|
|
1640
1972
|
});
|
|
@@ -1662,4 +1994,4 @@ function offloadCheck(projectRoot, targets) {
|
|
|
1662
1994
|
};
|
|
1663
1995
|
}
|
|
1664
1996
|
//#endregion
|
|
1665
|
-
export { realIo as
|
|
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 };
|