stim 1.34.0 → 1.36.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/dist/{acquire-artifact-B0SWoVNt.mjs → acquire-artifact-DGTHCd0Q.mjs} +19 -3
- package/dist/{act-7f7wIvCQ.mjs → act-CvN9xyV6.mjs} +18 -19
- package/dist/{activity-DkS-Ir6V.mjs → activity-BpdDvA8Q.mjs} +4 -4
- package/dist/{agent-device-usage-output-BVdgKbfz.mjs → agent-device-usage-output-pbhaW7_k.mjs} +4 -4
- package/dist/{android-B2DSKg-A.mjs → android-5wkcMFDG.mjs} +1 -1
- package/dist/{android-BU4uBX8I.mjs → android-B0qTdwnt.mjs} +5 -5
- package/dist/{android-Iq0M2t2d.mjs → android-SJx1V3R3.mjs} +5 -5
- package/dist/{android-cas-Dhcjkys1.mjs → android-cas-BS9_55U5.mjs} +4 -4
- package/dist/{android-D7qjT2fq.mjs → android-e9WP10cE.mjs} +47 -41
- package/dist/{android-image-DFk26aY6.mjs → android-image-BZ57YL6R.mjs} +2 -2
- package/dist/{android-project-DhO6el8N.d.mts → android-project-v_1nej_x.d.mts} +3 -1
- package/dist/api.d.mts +20 -6
- package/dist/api.mjs +31 -26
- package/dist/{apk-signed-tail-CPpA-3vu.mjs → apk-signed-tail-eZyXIrnc.mjs} +2 -2
- package/dist/{artifact-DmRtKFXW.mjs → artifact-C3srrGVU.mjs} +36 -14
- package/dist/{artifact-CNnTZs3R.mjs → artifact-ubNdxo_K.mjs} +52 -19
- package/dist/{auto-placement-DA65VQH7.mjs → auto-placement-CKFpFCuq.mjs} +7 -8
- package/dist/{browser-web-Dn_hgKZC.mjs → browser-web-B63vLrj6.mjs} +11 -11
- package/dist/{build-D7SuVoRX.mjs → build-B_Dx9SKJ.mjs} +72 -25
- package/dist/{build-cache-Cqj4FGnX.mjs → build-cache-BW8T4oD2.mjs} +10 -5
- package/dist/{build-plan-Ct1FiA3E.mjs → build-plan-CeIooJqn.mjs} +6 -6
- package/dist/{cache-manifest-8JX9S1QJ.mjs → cache-manifest-CxWkZmUr.mjs} +1 -1
- package/dist/cache-manifest.mjs +1 -1
- package/dist/{cli-DjdvQG5U.mjs → cli-COdXswmc.mjs} +19 -18
- package/dist/cli.mjs +1 -1
- package/dist/{client-kU37gURU.mjs → client-KSWIeQLA.mjs} +9 -8
- package/dist/collector-run.mjs +4 -4
- package/dist/{command-output-DjExb_tZ.mjs → command-output-T3cDctRE.mjs} +1 -1
- package/dist/{config-BW0fAtoK.mjs → config-BA3TgoIP.mjs} +59 -10
- package/dist/{dependencies-CZ7c8XX_.mjs → dependencies-LH-7k1Vr.mjs} +35 -38
- package/dist/{dependency-state-mL5g4H6K.mjs → dependency-state-Blfp3AkG.mjs} +1 -1
- package/dist/{dev-client-D3wOllNd.mjs → dev-client-MO_DjTZr.mjs} +2 -2
- package/dist/{device-DH5yf6TU.mjs → device-BZWt_aPJ.mjs} +11 -11
- package/dist/{device-CWPFAqs0.mjs → device-DNTWFFB1.mjs} +12 -12
- package/dist/{device-android-ChfF5I9A.mjs → device-android-BB9Lugr8.mjs} +8 -8
- package/dist/{ccache-ChBMXlBq.mjs → device-capacity-CpAVcKgq.mjs} +1006 -23
- package/dist/device-host-worker.mjs +13 -14
- package/dist/{device-pool-EZziQ0uS.mjs → device-pool-BJToGG-q.mjs} +3 -3
- package/dist/{device-provision-CfpEy-Vm.mjs → device-provision-CxQDCtrc.mjs} +9 -9
- package/dist/{doctor-CGBsS_68.mjs → doctor-C-46SkBe.mjs} +1 -1
- package/dist/{doctor-dwRboaXx.mjs → doctor-CYZ45LnX.mjs} +20 -21
- package/dist/{doctor-B5uEZiKp.mjs → doctor-DV639Iw_.mjs} +15 -16
- package/dist/doctor-fingerprint.mjs +1 -1
- package/dist/{doctor-watchman-r7M9UPi0.mjs → doctor-watchman-TZxYt_98.mjs} +4 -4
- package/dist/{eas-build-BpHIt_M3.mjs → eas-build-Xf5Jd6LR.mjs} +2 -2
- package/dist/{eas-fallback-CNPcjq3K.mjs → eas-fallback-DNYPNinw.mjs} +4 -4
- package/dist/{eas-session-ledger-CXwagcQ7.mjs → eas-session-ledger-CXZ4aix0.mjs} +5 -5
- package/dist/{error-diagnostics-Ym2mjmJ0.mjs → error-diagnostics-CvB5bpPU.mjs} +8 -8
- package/dist/{gc-B464w_a5.mjs → gc-8Qm_K1iy.mjs} +2 -2
- package/dist/{gc-C7YxKNci.mjs → gc-DiUhrVFo.mjs} +17 -17
- package/dist/{guide-BFW-cB7R.mjs → guide-CuvHRP53.mjs} +130 -65
- package/dist/{hosted-android-B7yfqXEy.mjs → hosted-android-D__HGVIO.mjs} +3 -3
- package/dist/{hosted-client-CQuzZwAu.mjs → hosted-client-DMOOu8vD.mjs} +6 -6
- package/dist/{hosted-logs-DfYCqrrR.mjs → hosted-logs-CClIktQM.mjs} +6 -6
- package/dist/{hosted-logs-sync-Dlpa2mQF.mjs → hosted-logs-sync-B7XCeqVm.mjs} +5 -5
- package/dist/{hosted-macos-JKefapEm.mjs → hosted-macos-D4sDJIbB.mjs} +5 -5
- package/dist/{hosted-native-B7OFt5gB.mjs → hosted-native-BiiUG_Q6.mjs} +7 -7
- package/dist/{ios-sUPbWqEh.mjs → ios-BZuhvLGr.mjs} +8 -8
- package/dist/{ios-BdZdNsAj.mjs → ios-BhfpRfNf.mjs} +4 -4
- package/dist/{ios-DSBs9c6d.mjs → ios-BhpUWyN7.mjs} +39 -32
- package/dist/{ios-device-Drplrgzo.mjs → ios-device-vhYL1N--.mjs} +1 -1
- package/dist/{ios-project-DoTQYghg.mjs → ios-project-Cz0S1Sz-.mjs} +7 -7
- package/dist/{ios-signing-R34vbrNU.mjs → ios-signing-BcH88ecn.mjs} +1 -1
- package/dist/{ios-state-CWsBysyN.mjs → ios-state-Avwz2cbZ.mjs} +1 -1
- package/dist/{js-swap-DrokhOfo.mjs → js-swap-Desu2iII.mjs} +4 -4
- package/dist/{launch-Cy-MWEZc.mjs → launch-6ckG9UrC.mjs} +19 -19
- package/dist/{lightened-ALwQcQtV.mjs → lightened-B6TnpHMX.mjs} +4 -4
- package/dist/{logs-DuED-01Z.mjs → logs-B-DMy1K0.mjs} +7 -7
- package/dist/{logs-DWT7jGpF.mjs → logs-CO0CJRiC.mjs} +5 -5
- package/dist/{macos-CtBtA_hW.mjs → macos-B1aCXhem.mjs} +19 -19
- package/dist/{macos-kWGcfLzx.mjs → macos-CduEmWAK.mjs} +1 -1
- package/dist/macos-run.mjs +1 -1
- package/dist/maintenance-run.mjs +5 -5
- package/dist/{metro-B5PVz0Wv.mjs → metro-DM3yJL1M.mjs} +2 -2
- package/dist/{metro-request-2LmFpC1z.mjs → metro-request-O8Q-JvBg.mjs} +1 -1
- package/dist/{named-ports-CA0nB9Yw.mjs → named-ports-BvB_CO9B.mjs} +2 -2
- package/dist/{native-android-tISP2EVp.mjs → native-android-28NOG2-y.mjs} +17 -17
- package/dist/{native-gradle-artifact-inputs-DVoeZkrw.mjs → native-gradle-artifact-inputs-DxVEtS6_.mjs} +3 -3
- package/dist/{native-runtime-CNgxeOCG.mjs → native-runtime-C3_NkUlW.mjs} +6 -6
- package/dist/{native-xcode-inputs-DGzkhpu4.mjs → native-xcode-inputs-BWFTw5cJ.mjs} +2 -2
- package/dist/{native-xcode-ios-B9nsfId2.mjs → native-xcode-ios-B4Qoc7-U.mjs} +16 -16
- package/dist/offload-worker.d.mts +1 -1
- package/dist/offload-worker.mjs +19 -19
- package/dist/{ownership-hC7xvvxF.mjs → ownership-C3y8wLSl.mjs} +2 -2
- package/dist/{phone-reverses-Bo0EzJ-P.mjs → phone-reverses-BhSxbqom.mjs} +4 -4
- package/dist/{placement-log-BsGvKLtU.mjs → placement-log-B8RCSzOm.mjs} +1 -1
- package/dist/{plan-placement-Am0IjuxB.mjs → plan-placement-BI4r55aP.mjs} +6 -6
- package/dist/{ports-CNzg1LYD.mjs → ports-DhOSyWm9.mjs} +3 -3
- package/dist/{prebuild-BfJII57G.mjs → prebuild-Di2IhUue.mjs} +9 -9
- package/dist/{project-DU3jjnBE.mjs → project-Clk1JAWL.mjs} +15 -15
- package/dist/{react-native-android-3dZiJv-4.mjs → react-native-android-bdHBMrnf.mjs} +23 -23
- package/dist/{react-native-build-CuvNPt-q.mjs → react-native-build-CJega_NM.mjs} +7 -7
- package/dist/{react-native-doctor-a35BwMUO.mjs → react-native-doctor-CY7BGITE.mjs} +5 -5
- package/dist/{react-native-ios-CqnReM1t.mjs → react-native-ios-Bo5EDVel.mjs} +25 -20
- package/dist/recorded-build-BuGL_fnm.mjs +58 -0
- package/dist/{recordings-AKZfsW2W.mjs → recordings-6i2-x8LU.mjs} +1 -1
- package/dist/{reload-CC3sNo10.mjs → reload-A_axEbeS.mjs} +14 -14
- package/dist/{reload-YPnvXGlB.mjs → reload-BvDuvygc.mjs} +1 -1
- package/dist/remote-PD6CM0xp.mjs +158 -0
- package/dist/{remote-BfaIHnhM.mjs → remote-WK_swZEw.mjs} +8 -8
- package/dist/{result-H-AxY7Ud.mjs → result-BkDpN5Mz.mjs} +23 -18
- package/dist/{run-N-BbRxnC.mjs → run-CTFi4Nq2.mjs} +1 -1
- package/dist/{server-bare-BTlICuaX.mjs → server-bare-W0aPsRZE.mjs} +1 -1
- package/dist/{server-command-Bn_gOObU.mjs → server-command-S00-ddjK.mjs} +4 -4
- package/dist/server-expo-BWJDa7Qb.mjs +2 -0
- package/dist/{server-expo-CUJ2fuv1.mjs → server-expo-cc-cmfsl.mjs} +2 -2
- package/dist/{settings-B5IBDfTx.mjs → settings-Bap-qjnp.mjs} +1 -1
- package/dist/{settings-DoWHJopy.mjs → settings-C_Xu4RNZ.mjs} +2 -2
- package/dist/{settings-payload-Bxw3hid5.mjs → settings-payload-D5jjhmrw.mjs} +4 -4
- package/dist/{settings-write-C6d7EC54.mjs → settings-write-C5huI-iS.mjs} +5 -5
- package/dist/settings.schema.json +1 -1
- package/dist/{simslim-DKdJx0LL.mjs → simslim-DlBvwgeF.mjs} +1 -1
- package/dist/{slot-launch-kq0-7w9C.mjs → slot-launch-qEQVTth7.mjs} +6 -6
- package/dist/{stage-CBfMTUK3.mjs → stage-CxVI_MlP.mjs} +1 -1
- package/dist/{start-Diu8q6_U.mjs → start-CVzivp35.mjs} +1 -1
- package/dist/{start-WTMumMHi.mjs → start-YpT_JpQQ.mjs} +10 -10
- package/dist/{state-DSOUqO-o.mjs → state-B4B5P4LU.mjs} +1 -1
- package/dist/{state-CtmYTRWm.mjs → state-B9VFI6r0.mjs} +2 -2
- package/dist/{stats-D4cW247z.mjs → stats-COoePEri.mjs} +4 -4
- package/dist/{status-CEwxo-PS.mjs → status-CIg0k7Qb.mjs} +21 -22
- package/dist/{status-B-oeWS0Z.mjs → status-CIvU5-TF.mjs} +1 -1
- package/dist/{status-DTFhDnd1.mjs → status-CQWmNl1f.mjs} +1 -1
- package/dist/{status-watch-D63R5nSI.mjs → status-watch--rfZynD_.mjs} +4 -4
- package/dist/status-worker.mjs +1 -1
- package/dist/{stim-desktop-_WTria-r.mjs → stim-desktop-D0P_bzmd.mjs} +1 -1
- package/dist/{stop-HL2RvhrE.mjs → stop-BEBBruvD.mjs} +4 -4
- package/dist/{stop-Dm9ON6Os.mjs → stop-BiI4_VFG.mjs} +2 -2
- package/dist/{stop-vX4w3_8V.mjs → stop-C3d5qG7C.mjs} +15 -15
- package/dist/supervisor-run.mjs +12 -13
- package/dist/support-BJbXovoO.mjs +2089 -0
- package/dist/{support-BNe1_Hg7.mjs → support-Vckdzrmk.mjs} +5 -5
- package/dist/{swiftpm-artifact-inputs-uq5oV7xR.mjs → swiftpm-artifact-inputs-DbuF-Yrr.mjs} +1 -1
- package/dist/{swiftpm-macos-ClF203_2.mjs → swiftpm-macos-Bu8xeBfM.mjs} +8 -8
- package/dist/{toolchain-CECahBDa.mjs → toolchain-CIRdNl-c.mjs} +1 -1
- package/dist/{toolchain-CYonNjd_.mjs → toolchain-DCnWAxQL.mjs} +71 -7
- package/dist/{warm-progress-q11Vdvhh.mjs → warm-progress-DwsGBpph.mjs} +3 -3
- package/dist/{web-BdaMBr2O.mjs → web-D2En0Wa5.mjs} +8 -8
- package/dist/web-Dpj70t4p.mjs +2 -0
- package/dist/web-run.mjs +4 -4
- package/dist/{workspace-state-BdZ-QgmB.mjs → workspace-state-BhwAJBgA.mjs} +2 -2
- package/dist/{workspaces-B9v5V7Es.mjs → workspaces-Dh5N70qW.mjs} +11 -11
- package/dist/{worktree-C4MBOMz7.mjs → worktree-DLm4s2Ib.mjs} +1 -1
- package/dist/{worktree-C3N86KpI.mjs → worktree-Yw3EW77B.mjs} +24 -24
- package/package.json +7 -7
- package/dist/device-capacity-CHC3dKWg.mjs +0 -867
- package/dist/device-ios-B3-Ah--R.mjs +0 -716
- package/dist/device-lease-run-i3FNX4OE.mjs +0 -282
- package/dist/idle-shutdown-DPD8oV36.mjs +0 -142
- package/dist/selection-CH-MUVGD.mjs +0 -791
- package/dist/server-expo-B59N16uT.mjs +0 -2
- package/dist/support-ivsfDj0v.mjs +0 -287
- package/dist/web-CIpHpGp8.mjs +0 -2
|
@@ -1,867 +0,0 @@
|
|
|
1
|
-
import { D as ownership_claim_exports, m as workspaceProcessLockError, p as withWorkspaceProcessLock } from "./exec-CGuDzt40.mjs";
|
|
2
|
-
import { a as formatElapsed, l as phaseLine } from "./command-output-DjExb_tZ.mjs";
|
|
3
|
-
import { d as getConfigDir, ft as deviceSlotPlatforms, ht as projectDeviceSlots, k as withConfigLockAsync$1, p as getPlatformDeviceLimits, u as getConcurrencyLimits, v as loadConfig$1 } from "./config-BW0fAtoK.mjs";
|
|
4
|
-
import { g as deviceReclaimIdleMinutesSetting } from "./settings-B5IBDfTx.mjs";
|
|
5
|
-
import { N as listAdbDevices, P as listAdbDevicesAsync } from "./android-Iq0M2t2d.mjs";
|
|
6
|
-
import { ot as recordCapacityRefusal, st as recordCapacityWait, v as withIdleWorkspace } from "./workspaces-B9v5V7Es.mjs";
|
|
7
|
-
import { t as canonicalPath } from "./paths-BsJMoPP2.mjs";
|
|
8
|
-
import { d as listAllIosSims, f as listAllIosSimsAsync, l as iosRuntimeMatches, y as parseRuntimeVersion } from "./ios-BdZdNsAj.mjs";
|
|
9
|
-
import { i as writeIdleShutdownEvent, n as dueIdleDevices, r as shutDownIdleDevices } from "./idle-shutdown-DPD8oV36.mjs";
|
|
10
|
-
import { realpathSync } from "node:fs";
|
|
11
|
-
import { basename, join } from "node:path";
|
|
12
|
-
import { workspaceId } from "@stim-cli/core";
|
|
13
|
-
import { DEVICE_RECLAIM_REASON, automaticMachineEnabled } from "@stim-cli/core/state";
|
|
14
|
-
import { setTimeout } from "node:timers/promises";
|
|
15
|
-
//#region src/workspace/display-label.ts
|
|
16
|
-
function baseLabel({ root, label }) {
|
|
17
|
-
return label || basename(root);
|
|
18
|
-
}
|
|
19
|
-
function pathSegments(root) {
|
|
20
|
-
return root.split(/[\\/]+/).filter(Boolean).toReversed();
|
|
21
|
-
}
|
|
22
|
-
function workspaceLabels(workspaces) {
|
|
23
|
-
const labels = /* @__PURE__ */ new Map();
|
|
24
|
-
const groups = /* @__PURE__ */ new Map();
|
|
25
|
-
for (const workspace of workspaces) {
|
|
26
|
-
const base = baseLabel(workspace);
|
|
27
|
-
const group = groups.get(base) ?? [];
|
|
28
|
-
if (!group.some(({ root }) => root === workspace.root)) group.push(workspace);
|
|
29
|
-
groups.set(base, group);
|
|
30
|
-
}
|
|
31
|
-
for (const [base, group] of groups) {
|
|
32
|
-
const [only] = group;
|
|
33
|
-
if (group.length === 1 && only) {
|
|
34
|
-
labels.set(only.root, base);
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
const segments = group.map(({ root }) => pathSegments(root));
|
|
38
|
-
const longest = Math.max(...segments.map((parts) => parts.length));
|
|
39
|
-
let depth = 0;
|
|
40
|
-
while (depth < longest && segments.every((parts) => parts[depth] === segments[0]?.[depth])) depth++;
|
|
41
|
-
const suffixes = segments.map((parts) => parts[depth]);
|
|
42
|
-
const distinct = new Set(suffixes).size === group.length;
|
|
43
|
-
group.forEach(({ root }, index) => {
|
|
44
|
-
const suffix = distinct ? suffixes[index] : root;
|
|
45
|
-
labels.set(root, suffix ? `${base} (${suffix})` : base);
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
return labels;
|
|
49
|
-
}
|
|
50
|
-
function workspaceDisplayLabel(root, config) {
|
|
51
|
-
const registered = Object.entries(config?.projects ?? {}).map(([path, project]) => ({
|
|
52
|
-
root: path,
|
|
53
|
-
label: project.label
|
|
54
|
-
}));
|
|
55
|
-
const own = registered.find((entry) => entry.root === root);
|
|
56
|
-
return workspaceLabels([...registered, own ?? { root }]).get(root) ?? basename(root);
|
|
57
|
-
}
|
|
58
|
-
//#endregion
|
|
59
|
-
//#region src/devices/queue-reclaim.ts
|
|
60
|
-
async function reclaimIdleDevice(root, idleMs, out, now, skipped, platform) {
|
|
61
|
-
try {
|
|
62
|
-
const self = canonicalPath(root);
|
|
63
|
-
const candidate = dueIdleDevices(void 0, idleMs, now()).filter(({ device }) => {
|
|
64
|
-
try {
|
|
65
|
-
return realpathSync(device.project) !== self;
|
|
66
|
-
} catch {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
}).filter(({ device }) => !platform || device.kind === platform).toSorted((a, b) => b.idleForMs - a.idleForMs).find(({ device }) => !skipped.has(`${device.kind}:${device.id}`));
|
|
70
|
-
if (!candidate) return 0;
|
|
71
|
-
const { device, idleForMs } = candidate;
|
|
72
|
-
const label = workspaceDisplayLabel(device.project, loadConfig$1());
|
|
73
|
-
const log = (event) => {
|
|
74
|
-
writeIdleShutdownEvent(device.project, event);
|
|
75
|
-
if (event.level === "warn") out(`${"device".padEnd(11)} ${event.msg}`);
|
|
76
|
-
};
|
|
77
|
-
skipped.add(`${device.kind}:${device.id}`);
|
|
78
|
-
const run = await withIdleWorkspace(device.project, () => shutDownIdleDevices(device.project, idleMs, log, now(), {
|
|
79
|
-
only: device,
|
|
80
|
-
reason: DEVICE_RECLAIM_REASON
|
|
81
|
-
}), {
|
|
82
|
-
purpose: "device queue reclaim",
|
|
83
|
-
supervisor: false,
|
|
84
|
-
managedLocks: false
|
|
85
|
-
});
|
|
86
|
-
if (!run.ran) {
|
|
87
|
-
log({
|
|
88
|
-
level: "warn",
|
|
89
|
-
event: "device_idle_shutdown_failed",
|
|
90
|
-
msg: `could not reclaim ${device.name}: ${run.reasons.join("; ")}`
|
|
91
|
-
});
|
|
92
|
-
return 0;
|
|
93
|
-
}
|
|
94
|
-
if (!run.value) return 0;
|
|
95
|
-
skipped.delete(`${device.kind}:${device.id}`);
|
|
96
|
-
out(`${"device".padEnd(11)} reclaimed ${device.kind === "ios" ? "iOS simulator" : "Android emulator"} ${device.name} (workspace ${label}, idle ${Math.floor(idleForMs / 6e4)}m) for this waiting run`);
|
|
97
|
-
return run.value;
|
|
98
|
-
} catch (error) {
|
|
99
|
-
out(`${"device".padEnd(11)} could not reclaim an idle device: ${error?.message || error}`);
|
|
100
|
-
return 0;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
//#endregion
|
|
104
|
-
//#region src/engine/device-capacity.ts
|
|
105
|
-
const LIVE_SIM_STATES = /* @__PURE__ */ new Set(["Booted", "Booting"]);
|
|
106
|
-
const ADMISSION_LOCK = "device-admission";
|
|
107
|
-
const ADMISSION_LOCK_WAIT_MS = 3e5;
|
|
108
|
-
const LOCK_QUIET_MS = 5e3;
|
|
109
|
-
const LOCK_PROGRESS_MS = 3e4;
|
|
110
|
-
const INVENTORY_REFRESH_MS = 1e4;
|
|
111
|
-
const LISTING_TIMEOUT_MS = 3e4;
|
|
112
|
-
const NO_ADB = {
|
|
113
|
-
emulators: [],
|
|
114
|
-
physical: [],
|
|
115
|
-
unhealthy: []
|
|
116
|
-
};
|
|
117
|
-
var DeviceAdmissionRefusal = class extends Error {
|
|
118
|
-
code;
|
|
119
|
-
remedy;
|
|
120
|
-
constructor(refusal) {
|
|
121
|
-
super(refusal.message);
|
|
122
|
-
this.code = refusal.code;
|
|
123
|
-
this.remedy = refusal.remedy;
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
var DeviceCountUnavailable = class extends Error {};
|
|
127
|
-
function deviceKey(platform, key) {
|
|
128
|
-
return `${platform}:${key}`;
|
|
129
|
-
}
|
|
130
|
-
function bootingDevicesRoot() {
|
|
131
|
-
return join(getConfigDir(), "device-boots");
|
|
132
|
-
}
|
|
133
|
-
function liveEmulatorPorts(adb) {
|
|
134
|
-
return [...adb.emulators, ...adb.unhealthy.filter((entry) => entry.kind === "emulator")];
|
|
135
|
-
}
|
|
136
|
-
function readBootingDevices() {
|
|
137
|
-
const survey = (0, ownership_claim_exports.readClaimSet)(bootingDevicesRoot());
|
|
138
|
-
const unresolved = survey.unresolved[0];
|
|
139
|
-
if (unresolved) throw new ownership_claim_exports.ClaimRefusedError({
|
|
140
|
-
claimPath: unresolved.path,
|
|
141
|
-
root: bootingDevicesRoot(),
|
|
142
|
-
reason: unresolved.reason,
|
|
143
|
-
label: "device boot"
|
|
144
|
-
});
|
|
145
|
-
const booting = [];
|
|
146
|
-
for (const holder of survey.live) {
|
|
147
|
-
const { platform, key } = holder.details;
|
|
148
|
-
if (typeof platform === "string" && typeof key === "string") booting.push({
|
|
149
|
-
platform,
|
|
150
|
-
key,
|
|
151
|
-
displayName: typeof holder.details.displayName === "string" ? holder.details.displayName : void 0
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
return booting;
|
|
155
|
-
}
|
|
156
|
-
function listSimsForCount() {
|
|
157
|
-
return process.platform === "darwin" ? listAllIosSims({ timeoutMs: LISTING_TIMEOUT_MS }) : [];
|
|
158
|
-
}
|
|
159
|
-
function listAdbForCount() {
|
|
160
|
-
return listAdbDevices({ timeoutMs: LISTING_TIMEOUT_MS });
|
|
161
|
-
}
|
|
162
|
-
function toolAbsent(error) {
|
|
163
|
-
const { code, status, stderr } = error ?? {};
|
|
164
|
-
return code === "ENOENT" || status === 127 || /unable to find utility|invalid active developer path|Xcode license/.test(String(stderr ?? ""));
|
|
165
|
-
}
|
|
166
|
-
function recordsOwnedEmulator(config) {
|
|
167
|
-
return Object.values(config?.projects || {}).some((project) => projectDeviceSlots(project).some(({ platforms }) => platforms.android?.owned && typeof platforms.android.consolePort === "number"));
|
|
168
|
-
}
|
|
169
|
-
function liveOwnedDeviceKeys({ sims = [], adbEmulators = [], config = null, booting = [], exclude = [] }) {
|
|
170
|
-
const keys = /* @__PURE__ */ new Set();
|
|
171
|
-
for (const sim of sims) {
|
|
172
|
-
if (!sim?.name?.startsWith("stim-")) continue;
|
|
173
|
-
if (exclude.some((device) => device.platform === "ios" && device.key === sim.udid)) continue;
|
|
174
|
-
if (LIVE_SIM_STATES.has(sim.state)) keys.add(deviceKey("ios", sim.udid));
|
|
175
|
-
}
|
|
176
|
-
const livePorts = new Set(adbEmulators.map((e) => e.consolePort));
|
|
177
|
-
for (const proj of Object.values(config?.projects || {})) for (const { platforms } of projectDeviceSlots(proj)) {
|
|
178
|
-
const android = platforms.android;
|
|
179
|
-
if (android?.owned && android.avdName && typeof android.consolePort === "number" && livePorts.has(android.consolePort)) keys.add(deviceKey("android", android.avdName));
|
|
180
|
-
}
|
|
181
|
-
for (const device of booting) if (!exclude.some((excluded) => excluded.platform === device.platform && excluded.key === device.key)) keys.add(deviceKey(device.platform, device.key));
|
|
182
|
-
return keys;
|
|
183
|
-
}
|
|
184
|
-
function inventoryKeys(inventory, exclude = []) {
|
|
185
|
-
return liveOwnedDeviceKeys({
|
|
186
|
-
...inventory,
|
|
187
|
-
adbEmulators: liveEmulatorPorts(inventory.adb),
|
|
188
|
-
exclude
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
const PLATFORM_CAPS = {
|
|
192
|
-
ios: {
|
|
193
|
-
setting: "concurrency.maxIosDevices",
|
|
194
|
-
noun: "iOS simulator"
|
|
195
|
-
},
|
|
196
|
-
android: {
|
|
197
|
-
setting: "concurrency.maxAndroidDevices",
|
|
198
|
-
noun: "Android emulator"
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
function isCappedPlatform(platform) {
|
|
202
|
-
return platform === "ios" || platform === "android";
|
|
203
|
-
}
|
|
204
|
-
function platformCount(keys, platform) {
|
|
205
|
-
return [...keys].filter((key) => key.startsWith(`${platform}:`)).length;
|
|
206
|
-
}
|
|
207
|
-
/** The caps that apply to a boot on `platform` given the booted `keys`, the platform's own cap first. */
|
|
208
|
-
function deviceLimits(keys, platform, max, platformMax) {
|
|
209
|
-
const limits = [];
|
|
210
|
-
if (platformMax > 0 && isCappedPlatform(platform)) limits.push({
|
|
211
|
-
...PLATFORM_CAPS[platform],
|
|
212
|
-
count: platformCount(keys, platform),
|
|
213
|
-
max: platformMax,
|
|
214
|
-
platform
|
|
215
|
-
});
|
|
216
|
-
if (max > 0) limits.push({
|
|
217
|
-
setting: "concurrency.maxDevices",
|
|
218
|
-
noun: "device",
|
|
219
|
-
count: keys.size,
|
|
220
|
-
max
|
|
221
|
-
});
|
|
222
|
-
return limits;
|
|
223
|
-
}
|
|
224
|
-
/** Whether a queued waiter is held back by its own platform cap, so it does not hold up the runs behind it. */
|
|
225
|
-
function platformWalled(keys, waiter, platformMax) {
|
|
226
|
-
const platform = String(waiter.details.platform);
|
|
227
|
-
if (!isCappedPlatform(platform)) return false;
|
|
228
|
-
const cap = platformMax[platform] ?? 0;
|
|
229
|
-
return cap > 0 && platformCount(keys, platform) >= cap;
|
|
230
|
-
}
|
|
231
|
-
/** The first cap with no room left, or the first cap when all have room (for a run waiting behind another). */
|
|
232
|
-
function bindingLimit(limits) {
|
|
233
|
-
return limits.find((limit) => limit.count >= limit.max) ?? limits[0];
|
|
234
|
-
}
|
|
235
|
-
function atCapacityRefusal({ count, max, setting, noun }) {
|
|
236
|
-
return {
|
|
237
|
-
code: "STIM_AT_CAPACITY",
|
|
238
|
-
message: count < max ? `${count}/${max} Stim ${noun}s are in use, but another run is ahead in the device slot queue.` : `${count} Stim ${noun}(s) are already booted and ${setting} is ${max}, so booting another would exceed the cap.`,
|
|
239
|
-
remedy: `stop an environment (stim stop) or raise ${setting}`
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
function uncountedRefusal(error) {
|
|
243
|
-
return {
|
|
244
|
-
code: "STIM_NO_DEVICE",
|
|
245
|
-
message: `A device cap (concurrency.maxDevices, concurrency.maxIosDevices or concurrency.maxAndroidDevices) is set, and Stim could not count the booted devices: ${error?.message || error}`,
|
|
246
|
-
remedy: `Listing devices times out when the machine is overloaded: retry once the load falls. Otherwise run \`stim doctor\` to check the simulator and adb toolchains, and check that ${join(getConfigDir(), "config.json")} and ${bootingDevicesRoot()} are readable.`
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
function listed(source, empty, absent = () => false) {
|
|
250
|
-
try {
|
|
251
|
-
return (typeof source === "function" ? source() : source) ?? empty;
|
|
252
|
-
} catch (error) {
|
|
253
|
-
if (absent(error)) return empty;
|
|
254
|
-
throw new DeviceCountUnavailable("", { cause: error });
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
async function listedAwait(read, empty, absent = () => false) {
|
|
258
|
-
try {
|
|
259
|
-
return await read() ?? empty;
|
|
260
|
-
} catch (error) {
|
|
261
|
-
if (absent(error)) return empty;
|
|
262
|
-
throw new DeviceCountUnavailable("", { cause: error });
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
async function readInventoryAsync({ sims = () => process.platform === "darwin" ? listAllIosSimsAsync({ timeoutMs: LISTING_TIMEOUT_MS }) : [], adb = () => listAdbDevicesAsync({ timeoutMs: LISTING_TIMEOUT_MS }), config = loadConfig$1, booting = readBootingDevices }) {
|
|
266
|
-
try {
|
|
267
|
-
const recorded = await listedAwait(() => typeof config === "function" ? config() : config, null);
|
|
268
|
-
return {
|
|
269
|
-
booting: await listedAwait(() => typeof booting === "function" ? booting() : booting, []),
|
|
270
|
-
sims: await listedAwait(() => typeof sims === "function" ? sims() : sims, [], toolAbsent),
|
|
271
|
-
adb: recordsOwnedEmulator(recorded) ? await listedAwait(() => typeof adb === "function" ? adb() : adb, NO_ADB, toolAbsent) : NO_ADB,
|
|
272
|
-
config: recorded
|
|
273
|
-
};
|
|
274
|
-
} catch (error) {
|
|
275
|
-
if (error instanceof DeviceCountUnavailable) {
|
|
276
|
-
if ((0, ownership_claim_exports.isClaimRefusal)(error.cause)) throw error.cause;
|
|
277
|
-
return uncountedRefusal(error.cause);
|
|
278
|
-
}
|
|
279
|
-
throw error;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
function readInventory({ sims = listSimsForCount, adb = listAdbForCount, config = loadConfig$1, booting = readBootingDevices }) {
|
|
283
|
-
try {
|
|
284
|
-
const recorded = listed(config, null);
|
|
285
|
-
return {
|
|
286
|
-
booting: listed(booting, []),
|
|
287
|
-
sims: listed(sims, [], toolAbsent),
|
|
288
|
-
adb: recordsOwnedEmulator(recorded) ? listed(adb, NO_ADB, toolAbsent) : NO_ADB,
|
|
289
|
-
config: recorded
|
|
290
|
-
};
|
|
291
|
-
} catch (error) {
|
|
292
|
-
if (error instanceof DeviceCountUnavailable) {
|
|
293
|
-
if ((0, ownership_claim_exports.isClaimRefusal)(error.cause)) throw error.cause;
|
|
294
|
-
return uncountedRefusal(error.cause);
|
|
295
|
-
}
|
|
296
|
-
throw error;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Owned simulators that are booted or booting, owned emulators adb lists in any state, and devices another
|
|
301
|
-
* run is booting under `concurrency.maxDevices`, each counted once unless excluded; when a listing fails, why the count is unknown.
|
|
302
|
-
*/
|
|
303
|
-
function countLiveOwnedDevices(sources = {}, { exclude = [] } = {}) {
|
|
304
|
-
const counts = countLiveOwnedDevicesByPlatform(sources, { exclude });
|
|
305
|
-
return "unknown" in counts ? counts : counts.ios + counts.android;
|
|
306
|
-
}
|
|
307
|
-
/** The counts of {@link countLiveOwnedDevices} split by platform. */
|
|
308
|
-
function countLiveOwnedDevicesByPlatform(sources = {}, { exclude = [] } = {}) {
|
|
309
|
-
try {
|
|
310
|
-
const inventory = readInventory(sources);
|
|
311
|
-
if ("code" in inventory) return { unknown: inventory.message };
|
|
312
|
-
const keys = inventoryKeys(inventory, exclude);
|
|
313
|
-
return {
|
|
314
|
-
ios: platformCount(keys, "ios"),
|
|
315
|
-
android: platformCount(keys, "android")
|
|
316
|
-
};
|
|
317
|
-
} catch (error) {
|
|
318
|
-
if ((0, ownership_claim_exports.isClaimRefusal)(error)) return { unknown: error.message };
|
|
319
|
-
throw error;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
async function listedAsync(source, empty) {
|
|
323
|
-
try {
|
|
324
|
-
return await source();
|
|
325
|
-
} catch (error) {
|
|
326
|
-
if (toolAbsent(error)) return empty;
|
|
327
|
-
throw new DeviceCountUnavailable("", { cause: error });
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
async function countLiveOwnedDevicesFrom(sources, { exclude = [] } = {}) {
|
|
331
|
-
try {
|
|
332
|
-
const inventory = await readInventoryAsync(sources);
|
|
333
|
-
return "code" in inventory ? { unknown: inventory.message } : inventoryKeys(inventory, exclude).size;
|
|
334
|
-
} catch (error) {
|
|
335
|
-
if ((0, ownership_claim_exports.isClaimRefusal)(error)) return { unknown: error.message };
|
|
336
|
-
throw error;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
async function countLiveOwnedDevicesAsync(operation) {
|
|
340
|
-
try {
|
|
341
|
-
operation.signal.throwIfAborted();
|
|
342
|
-
const config = loadConfig$1();
|
|
343
|
-
const booting = readBootingDevices();
|
|
344
|
-
const [sims, adb] = await Promise.all([process.platform === "darwin" ? listedAsync(() => listAllIosSimsAsync({ timeoutMs: LISTING_TIMEOUT_MS }), []) : [], recordsOwnedEmulator(config) ? listedAsync(() => listAdbDevicesAsync({ timeoutMs: LISTING_TIMEOUT_MS }), NO_ADB) : NO_ADB]);
|
|
345
|
-
operation.signal.throwIfAborted();
|
|
346
|
-
return countLiveOwnedDevices({
|
|
347
|
-
config,
|
|
348
|
-
booting,
|
|
349
|
-
sims,
|
|
350
|
-
adb
|
|
351
|
-
});
|
|
352
|
-
} catch (error) {
|
|
353
|
-
operation.signal.throwIfAborted();
|
|
354
|
-
if ((0, ownership_claim_exports.isClaimRefusal)(error)) return { unknown: error.message };
|
|
355
|
-
if (error instanceof DeviceCountUnavailable) return { unknown: uncountedRefusal(error.cause).message };
|
|
356
|
-
throw error;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
function workspaceHasLiveDevice({ platform, project, slot = "default", sims = [], adbEmulators = [] } = {}) {
|
|
360
|
-
if (!platform) return false;
|
|
361
|
-
const record = deviceSlotPlatforms(project, slot)?.[platform];
|
|
362
|
-
if (!record) return false;
|
|
363
|
-
if (platform === "ios") return sims.some((s) => s.udid === record.deviceUdid && LIVE_SIM_STATES.has(s.state));
|
|
364
|
-
return typeof record.consolePort === "number" && adbEmulators.some((e) => e.consolePort === record.consolePort);
|
|
365
|
-
}
|
|
366
|
-
function deviceCapacityRefusal({ platform, project, slot = "default", max, platformMax, sims = [], adb = null, config = null, booting = [] } = {}) {
|
|
367
|
-
if (!(max && max > 0) && !(platformMax && platformMax > 0)) return null;
|
|
368
|
-
const adbEmulators = liveEmulatorPorts(adb ?? NO_ADB);
|
|
369
|
-
if (workspaceHasLiveDevice({
|
|
370
|
-
platform,
|
|
371
|
-
project,
|
|
372
|
-
slot,
|
|
373
|
-
sims,
|
|
374
|
-
adbEmulators
|
|
375
|
-
})) return null;
|
|
376
|
-
const full = deviceLimits(liveOwnedDeviceKeys({
|
|
377
|
-
sims,
|
|
378
|
-
adbEmulators,
|
|
379
|
-
config,
|
|
380
|
-
booting
|
|
381
|
-
}), platform ?? "", max ?? 0, platformMax ?? 0).find((limit) => limit.count >= limit.max);
|
|
382
|
-
return full ? {
|
|
383
|
-
...atCapacityRefusal(full),
|
|
384
|
-
max: full.max
|
|
385
|
-
} : null;
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* The early `concurrency.maxDevices` check, before Metro starts or a device is created. It is advisory, so an
|
|
389
|
-
* unknown count passes: `withDeviceBootAdmission` makes the binding decision when the boot starts.
|
|
390
|
-
*/
|
|
391
|
-
async function checkDeviceCapacity({ platform, project, slot = "default", max, platformMax = platform && isCappedPlatform(platform) ? getPlatformDeviceLimits()[platform] : 0, ...sources } = {}) {
|
|
392
|
-
if (!(max && max > 0) && !(platformMax && platformMax > 0)) return null;
|
|
393
|
-
let inventory;
|
|
394
|
-
try {
|
|
395
|
-
inventory = await readInventoryAsync(sources);
|
|
396
|
-
} catch (error) {
|
|
397
|
-
if ((0, ownership_claim_exports.isClaimRefusal)(error)) return null;
|
|
398
|
-
throw error;
|
|
399
|
-
}
|
|
400
|
-
if ("code" in inventory) return null;
|
|
401
|
-
return deviceCapacityRefusal({
|
|
402
|
-
platform,
|
|
403
|
-
project,
|
|
404
|
-
slot,
|
|
405
|
-
max,
|
|
406
|
-
platformMax,
|
|
407
|
-
...inventory
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
async function peekDeviceSlots({ platform, project, slot = "default", max = getConcurrencyLimits().maxDevices, platformMax = getPlatformDeviceLimits()[platform], sources = {} }) {
|
|
411
|
-
try {
|
|
412
|
-
const inventory = await readInventoryAsync(sources);
|
|
413
|
-
if ("code" in inventory) return {
|
|
414
|
-
count: null,
|
|
415
|
-
max,
|
|
416
|
-
queued: 0,
|
|
417
|
-
localLive: false
|
|
418
|
-
};
|
|
419
|
-
const owned = deviceSlotPlatforms(project, slot)?.[platform]?.owned === true;
|
|
420
|
-
const keys = inventoryKeys(inventory);
|
|
421
|
-
const limits = deviceLimits(keys, platform, max, platformMax);
|
|
422
|
-
const tightest = limits.length ? limits.reduce((a, b) => b.max - b.count < a.max - a.count ? b : a) : void 0;
|
|
423
|
-
return {
|
|
424
|
-
count: tightest?.count ?? keys.size,
|
|
425
|
-
max: tightest?.max ?? max,
|
|
426
|
-
queued: liveWaiters().filter((waiter) => !platformWalled(keys, waiter, {
|
|
427
|
-
...getPlatformDeviceLimits(),
|
|
428
|
-
[platform]: platformMax
|
|
429
|
-
})).length,
|
|
430
|
-
localLive: owned && workspaceHasLiveDevice({
|
|
431
|
-
platform,
|
|
432
|
-
project,
|
|
433
|
-
slot,
|
|
434
|
-
sims: inventory.sims,
|
|
435
|
-
adbEmulators: liveEmulatorPorts(inventory.adb)
|
|
436
|
-
}),
|
|
437
|
-
...tightest?.platform ? { limit: tightest.setting } : {}
|
|
438
|
-
};
|
|
439
|
-
} catch (error) {
|
|
440
|
-
if (!(0, ownership_claim_exports.isClaimRefusal)(error)) throw error;
|
|
441
|
-
return {
|
|
442
|
-
count: null,
|
|
443
|
-
max,
|
|
444
|
-
queued: 0,
|
|
445
|
-
localLive: false
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
const DEFAULT_DEVICE_SLOT_WAIT_MS = 6e5;
|
|
450
|
-
function deviceSlotWaitingLine({ count, max, holders, elapsedMs, queue, setting }) {
|
|
451
|
-
return `${"device".padEnd(11)} waiting for a device slot (${count}/${max} in use${setting ? ` by ${setting}` : ""}${holders.length ? `: ${holders.join(", ")}` : ""}), ${formatElapsed(elapsedMs)} elapsed -- stim guide lifecycle concurrency${queue ? `; ${queue}` : ""}`;
|
|
452
|
-
}
|
|
453
|
-
function unrecordedHolder(label) {
|
|
454
|
-
return {
|
|
455
|
-
label,
|
|
456
|
-
workspace: null,
|
|
457
|
-
slot: null
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
function slotHolders(inventory, keys) {
|
|
461
|
-
const owners = /* @__PURE__ */ new Map();
|
|
462
|
-
const projects = Object.entries(inventory.config?.projects ?? {});
|
|
463
|
-
const labels = workspaceLabels(projects.map(([root, project]) => ({
|
|
464
|
-
root,
|
|
465
|
-
label: project.label
|
|
466
|
-
})));
|
|
467
|
-
for (const [root, project] of projects) {
|
|
468
|
-
const label = labels.get(root) ?? basename(root);
|
|
469
|
-
for (const { slot, platforms } of projectDeviceSlots(project)) {
|
|
470
|
-
if (platforms.ios?.deviceUdid) owners.set(deviceKey("ios", platforms.ios.deviceUdid), {
|
|
471
|
-
label,
|
|
472
|
-
workspace: root,
|
|
473
|
-
slot
|
|
474
|
-
});
|
|
475
|
-
if (platforms.android?.avdName) owners.set(deviceKey("android", platforms.android.avdName), {
|
|
476
|
-
label,
|
|
477
|
-
workspace: root,
|
|
478
|
-
slot
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
for (const sim of inventory.sims) {
|
|
483
|
-
const key = deviceKey("ios", sim.udid);
|
|
484
|
-
if (!owners.has(key)) owners.set(key, unrecordedHolder(sim.name));
|
|
485
|
-
}
|
|
486
|
-
const booting = /* @__PURE__ */ new Set();
|
|
487
|
-
for (const device of inventory.booting) {
|
|
488
|
-
const key = deviceKey(device.platform, device.key);
|
|
489
|
-
booting.add(key);
|
|
490
|
-
if (!owners.has(key)) owners.set(key, unrecordedHolder(device.displayName ?? device.key));
|
|
491
|
-
}
|
|
492
|
-
return [...keys].map((key) => {
|
|
493
|
-
const { label, workspace, slot } = owners.get(key) ?? unrecordedHolder(key.slice(key.indexOf(":") + 1));
|
|
494
|
-
const platform = key.startsWith("android:") ? "android" : "ios";
|
|
495
|
-
return booting.has(key) ? {
|
|
496
|
-
platform,
|
|
497
|
-
label,
|
|
498
|
-
workspace,
|
|
499
|
-
slot,
|
|
500
|
-
booting: true
|
|
501
|
-
} : {
|
|
502
|
-
platform,
|
|
503
|
-
label,
|
|
504
|
-
workspace,
|
|
505
|
-
slot
|
|
506
|
-
};
|
|
507
|
-
}).toSorted((a, b) => a.label.localeCompare(b.label) || a.platform.localeCompare(b.platform));
|
|
508
|
-
}
|
|
509
|
-
function deviceHolders(inventory, keys) {
|
|
510
|
-
return [...new Set(slotHolders(inventory, keys).map((holder) => holder.label))].toSorted();
|
|
511
|
-
}
|
|
512
|
-
/**
|
|
513
|
-
* `concurrency.maxDevices` usage for status (the owned devices the cap counts and who holds each) and the usage of each
|
|
514
|
-
* per-platform cap that is set, from one inventory read. `sims` is the listing status already read; an unknown count
|
|
515
|
-
* reports why instead of failing.
|
|
516
|
-
*/
|
|
517
|
-
async function deviceSlotUsages(max, platformMax, sources = {}) {
|
|
518
|
-
const capped = [];
|
|
519
|
-
for (const platform of ["ios", "android"]) {
|
|
520
|
-
const cap = platformMax[platform] ?? 0;
|
|
521
|
-
if (cap > 0) capped.push([platform, cap]);
|
|
522
|
-
}
|
|
523
|
-
const platformDevices = (count) => capped.length ? { platformDevices: Object.fromEntries(capped.map(([platform, cap]) => [platform, {
|
|
524
|
-
max: cap,
|
|
525
|
-
...count(platform)
|
|
526
|
-
}])) } : {};
|
|
527
|
-
try {
|
|
528
|
-
const inventory = await readInventoryAsync(sources);
|
|
529
|
-
if ("code" in inventory) return {
|
|
530
|
-
...max > 0 ? { devices: {
|
|
531
|
-
max,
|
|
532
|
-
inUse: null,
|
|
533
|
-
unknown: inventory.message,
|
|
534
|
-
holders: []
|
|
535
|
-
} } : {},
|
|
536
|
-
...platformDevices(() => ({
|
|
537
|
-
inUse: null,
|
|
538
|
-
unknown: inventory.message
|
|
539
|
-
}))
|
|
540
|
-
};
|
|
541
|
-
const keys = inventoryKeys(inventory);
|
|
542
|
-
return {
|
|
543
|
-
...max > 0 ? { devices: {
|
|
544
|
-
max,
|
|
545
|
-
inUse: keys.size,
|
|
546
|
-
holders: slotHolders(inventory, keys)
|
|
547
|
-
} } : {},
|
|
548
|
-
...platformDevices((platform) => ({ inUse: platformCount(keys, platform) }))
|
|
549
|
-
};
|
|
550
|
-
} catch (error) {
|
|
551
|
-
if (!(0, ownership_claim_exports.isClaimRefusal)(error)) throw error;
|
|
552
|
-
return {
|
|
553
|
-
...max > 0 ? { devices: {
|
|
554
|
-
max,
|
|
555
|
-
inUse: null,
|
|
556
|
-
unknown: error.message,
|
|
557
|
-
holders: []
|
|
558
|
-
} } : {},
|
|
559
|
-
...platformDevices(() => ({
|
|
560
|
-
inUse: null,
|
|
561
|
-
unknown: error.message
|
|
562
|
-
}))
|
|
563
|
-
};
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
function liveWaiters() {
|
|
567
|
-
const root = join(getConfigDir(), "device-waits");
|
|
568
|
-
const survey = (0, ownership_claim_exports.inspectClaimSet)(root, { label: "device wait" });
|
|
569
|
-
if (survey.exclusive) throw new ownership_claim_exports.ClaimRefusedError({
|
|
570
|
-
root,
|
|
571
|
-
claimPath: survey.exclusive.path,
|
|
572
|
-
reason: "the wait queue is held exclusively",
|
|
573
|
-
label: "device wait"
|
|
574
|
-
});
|
|
575
|
-
return survey.shared.toSorted((a, b) => (Number(a.details.sequence) || 0) - (Number(b.details.sequence) || 0) || a.startedAt.localeCompare(b.startedAt) || a.claimId.localeCompare(b.claimId));
|
|
576
|
-
}
|
|
577
|
-
function takeWaitTicket(device, waiters) {
|
|
578
|
-
const root = join(getConfigDir(), "device-waits");
|
|
579
|
-
const attempt = (0, ownership_claim_exports.tryAcquireClaim)({
|
|
580
|
-
root,
|
|
581
|
-
mode: "shared",
|
|
582
|
-
label: "device wait",
|
|
583
|
-
details: {
|
|
584
|
-
...device,
|
|
585
|
-
sequence: Math.max(0, ...waiters.map((holder) => Number(holder.details.sequence) || 0)) + 1
|
|
586
|
-
}
|
|
587
|
-
});
|
|
588
|
-
if (attempt.acquired) return attempt.acquired;
|
|
589
|
-
throw new ownership_claim_exports.ClaimRefusedError({
|
|
590
|
-
root,
|
|
591
|
-
claimPath: attempt.held?.path ?? root,
|
|
592
|
-
reason: "the wait queue is held exclusively",
|
|
593
|
-
label: "device wait"
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
function takeBootMarker(device) {
|
|
597
|
-
const attempt = (0, ownership_claim_exports.tryAcquireClaim)({
|
|
598
|
-
root: bootingDevicesRoot(),
|
|
599
|
-
mode: "shared",
|
|
600
|
-
label: "device boot",
|
|
601
|
-
details: { ...device }
|
|
602
|
-
});
|
|
603
|
-
if (attempt.acquired) return attempt.acquired;
|
|
604
|
-
throw new DeviceAdmissionRefusal({
|
|
605
|
-
code: "STIM_NO_DEVICE",
|
|
606
|
-
message: `Another process holds ${bootingDevicesRoot()} exclusively, so this boot cannot be counted toward the device caps.`,
|
|
607
|
-
remedy: "Retry once that process finishes."
|
|
608
|
-
});
|
|
609
|
-
}
|
|
610
|
-
async function admissionTransaction(action, lockWaitMs, out, signal) {
|
|
611
|
-
const started = Date.now();
|
|
612
|
-
let lastLine = null;
|
|
613
|
-
const onHeld = () => {
|
|
614
|
-
const elapsed = Date.now() - started;
|
|
615
|
-
if (elapsed < LOCK_QUIET_MS || lastLine !== null && Date.now() - lastLine < LOCK_PROGRESS_MS) return;
|
|
616
|
-
lastLine = Date.now();
|
|
617
|
-
out(phaseLine("device", `waiting for other runs to finish counting booted devices (${formatElapsed(elapsed)})`));
|
|
618
|
-
};
|
|
619
|
-
try {
|
|
620
|
-
return await withWorkspaceProcessLock(getConfigDir(), ADMISSION_LOCK, action, {
|
|
621
|
-
external: true,
|
|
622
|
-
waitMs: lockWaitMs,
|
|
623
|
-
onHeld,
|
|
624
|
-
signal
|
|
625
|
-
});
|
|
626
|
-
} catch (error) {
|
|
627
|
-
if (workspaceProcessLockError(error) !== "timeout") throw error;
|
|
628
|
-
throw new DeviceAdmissionRefusal({
|
|
629
|
-
code: "STIM_NO_DEVICE",
|
|
630
|
-
message: `Waited ${formatElapsed(lockWaitMs)} for other runs to finish counting booted devices for the device caps.`,
|
|
631
|
-
remedy: "Listing devices is slow, which usually means the machine is overloaded. Retry once the load falls."
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
/** Boots an owned device under the Stim home's cap, reserving capacity before releasing admission. */
|
|
636
|
-
async function withDeviceBootAdmission(device, boot, { root, max = getConcurrencyLimits().maxDevices, platformMax = getPlatformDeviceLimits(), sources = {}, lockWaitMs = ADMISSION_LOCK_WAIT_MS, out = () => {}, waitMs = DEFAULT_DEVICE_SLOT_WAIT_MS, noWait = false, automatic = false, displayName = workspaceDisplayLabel(root, loadConfig$1()), reclaimIdleMinutes = deviceReclaimIdleMinutesSetting({}), now = Date.now, signal, sleep = (ms) => setTimeout(ms, void 0, { signal }), waitingFor = () => {}, onWait = () => {}, onBootStart = () => {} }) {
|
|
637
|
-
const requireMembership = () => {
|
|
638
|
-
if (automatic && !automaticMachineEnabled("device", "local")) throw new DeviceAdmissionRefusal({
|
|
639
|
-
code: "STIM_HOSTING_REFUSED",
|
|
640
|
-
message: "local is disabled in this requester's automatic device pool.",
|
|
641
|
-
remedy: "Enable local in remote.devicePoolDisabled or select a machine explicitly."
|
|
642
|
-
});
|
|
643
|
-
};
|
|
644
|
-
signal?.throwIfAborted();
|
|
645
|
-
requireMembership();
|
|
646
|
-
const ownCap = isCappedPlatform(device.platform) ? platformMax[device.platform] ?? 0 : 0;
|
|
647
|
-
if (!(max > 0) && !(ownCap > 0)) {
|
|
648
|
-
onBootStart();
|
|
649
|
-
return boot();
|
|
650
|
-
}
|
|
651
|
-
const workspace = workspaceId(root);
|
|
652
|
-
device = {
|
|
653
|
-
...device,
|
|
654
|
-
workspace,
|
|
655
|
-
displayName
|
|
656
|
-
};
|
|
657
|
-
let ticket;
|
|
658
|
-
let marker;
|
|
659
|
-
let started;
|
|
660
|
-
let lastLine = -Infinity;
|
|
661
|
-
let visibleWait = false;
|
|
662
|
-
let lastInventory;
|
|
663
|
-
let lastTurn = false;
|
|
664
|
-
let binding;
|
|
665
|
-
let lastListed = -Infinity;
|
|
666
|
-
let reclaimed = 0;
|
|
667
|
-
let lastReclaim = -Infinity;
|
|
668
|
-
const skippedReclaims = /* @__PURE__ */ new Set();
|
|
669
|
-
try {
|
|
670
|
-
try {
|
|
671
|
-
for (;;) {
|
|
672
|
-
const result = await admissionTransaction(async () => {
|
|
673
|
-
signal?.throwIfAborted();
|
|
674
|
-
requireMembership();
|
|
675
|
-
const waiters = liveWaiters();
|
|
676
|
-
const first = waiters[0];
|
|
677
|
-
const mine = waiters.findIndex((holder) => holder.claimId === ticket?.claimId);
|
|
678
|
-
const ahead = mine < 0 ? waiters : waiters.slice(0, mine);
|
|
679
|
-
const admit = () => {
|
|
680
|
-
const admitted = takeBootMarker(device);
|
|
681
|
-
if ((0, ownership_claim_exports.releaseClaim)(ticket)) ticket = void 0;
|
|
682
|
-
return { marker: admitted };
|
|
683
|
-
};
|
|
684
|
-
const wait = ({ limit, holders }, turn) => {
|
|
685
|
-
const ticketId = ticket?.claimId;
|
|
686
|
-
const position = ticketId ? waiters.findIndex((holder) => holder.claimId === ticketId) + 1 : waiters.length + 1;
|
|
687
|
-
const headName = first?.details.displayName ?? displayName;
|
|
688
|
-
const queue = `queue position ${position}${position > 1 ? " behind" : ", head"} ${headName}; queue: ${join(getConfigDir(), "device-waits")}`;
|
|
689
|
-
if (noWait || waitMs === 0) throw new DeviceAdmissionRefusal(atCapacityRefusal(limit));
|
|
690
|
-
if (started !== void 0 && now() - started >= waitMs) throw new DeviceAdmissionRefusal({
|
|
691
|
-
code: "STIM_AT_CAPACITY",
|
|
692
|
-
message: `Waited ${formatElapsed(now() - started)} for a device slot; ${limit.count}/${limit.max} Stim ${limit.noun}s are in use (${limit.setting}); ${queue}.`,
|
|
693
|
-
remedy: `Stop an environment (stim stop), retry with a longer --wait <seconds>, or raise ${limit.setting}.`
|
|
694
|
-
});
|
|
695
|
-
if (!ticket) {
|
|
696
|
-
ticket = takeWaitTicket(device, waiters);
|
|
697
|
-
started = now();
|
|
698
|
-
}
|
|
699
|
-
const firstOfPlatform = ahead.every((holder) => holder.details.platform !== device.platform);
|
|
700
|
-
return {
|
|
701
|
-
limit,
|
|
702
|
-
holders,
|
|
703
|
-
queue,
|
|
704
|
-
reclaim: turn && firstOfPlatform && limit.count >= limit.max
|
|
705
|
-
};
|
|
706
|
-
};
|
|
707
|
-
if (ticket && lastInventory && ahead.length > 0 && !lastTurn && now() - lastListed < INVENTORY_REFRESH_MS) {
|
|
708
|
-
const booting = sources.booting ?? readBootingDevices;
|
|
709
|
-
const own = deviceKey(device.platform, device.key);
|
|
710
|
-
if ((typeof booting === "function" ? booting() : booting).some((entry) => deviceKey(entry.platform, entry.key) === own)) return admit();
|
|
711
|
-
return wait(lastInventory, false);
|
|
712
|
-
}
|
|
713
|
-
const inventory = await readInventoryAsync(sources);
|
|
714
|
-
if ("code" in inventory) throw new DeviceAdmissionRefusal(inventory);
|
|
715
|
-
const keys = inventoryKeys(inventory);
|
|
716
|
-
const own = deviceKey(device.platform, device.key);
|
|
717
|
-
const ownBooting = inventory.booting.some((entry) => deviceKey(entry.platform, entry.key) === own);
|
|
718
|
-
if (device.platform === "ios" && keys.has(own) || ownBooting) return admit();
|
|
719
|
-
keys.delete(own);
|
|
720
|
-
const limits = deviceLimits(keys, device.platform, max, ownCap);
|
|
721
|
-
const turn = ahead.every((waiter) => platformWalled(keys, waiter, platformMax));
|
|
722
|
-
if (turn && limits.every((limit) => limit.count < limit.max)) return admit();
|
|
723
|
-
lastListed = now();
|
|
724
|
-
lastTurn = turn;
|
|
725
|
-
const limit = bindingLimit(limits);
|
|
726
|
-
if (!limit) return admit();
|
|
727
|
-
binding = limit;
|
|
728
|
-
lastInventory = {
|
|
729
|
-
limit,
|
|
730
|
-
holders: deviceHolders(inventory, limit.platform ? new Set([...keys].filter((key) => key.startsWith(`${limit.platform}:`))) : keys)
|
|
731
|
-
};
|
|
732
|
-
return wait(lastInventory, turn);
|
|
733
|
-
}, lockWaitMs, out, signal);
|
|
734
|
-
if ("marker" in result) {
|
|
735
|
-
marker = result.marker;
|
|
736
|
-
signal?.throwIfAborted();
|
|
737
|
-
break;
|
|
738
|
-
}
|
|
739
|
-
signal?.throwIfAborted();
|
|
740
|
-
const elapsedMs = now() - started;
|
|
741
|
-
visibleWait = true;
|
|
742
|
-
waitingFor({
|
|
743
|
-
kind: "device-slot",
|
|
744
|
-
inUse: result.limit.count,
|
|
745
|
-
max: result.limit.max,
|
|
746
|
-
since: new Date(started).toISOString()
|
|
747
|
-
});
|
|
748
|
-
if (now() - lastLine >= 1e4) {
|
|
749
|
-
lastLine = now();
|
|
750
|
-
out(deviceSlotWaitingLine({
|
|
751
|
-
count: result.limit.count,
|
|
752
|
-
max: result.limit.max,
|
|
753
|
-
holders: result.holders,
|
|
754
|
-
elapsedMs,
|
|
755
|
-
queue: result.queue,
|
|
756
|
-
...result.limit.platform ? { setting: result.limit.setting } : {}
|
|
757
|
-
}));
|
|
758
|
-
}
|
|
759
|
-
signal?.throwIfAborted();
|
|
760
|
-
if (result.reclaim && now() - lastReclaim >= 15e3) {
|
|
761
|
-
lastReclaim = now();
|
|
762
|
-
try {
|
|
763
|
-
if (reclaimIdleMinutes > 0) reclaimed += await reclaimIdleDevice(root, reclaimIdleMinutes * 6e4, out, now, skippedReclaims, result.limit.platform);
|
|
764
|
-
} catch (error) {
|
|
765
|
-
out(phaseLine("device", `could not reclaim an idle device: ${error?.message || error}`));
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
await sleep(Math.min(2e3, Math.max(0, waitMs - elapsedMs)));
|
|
769
|
-
}
|
|
770
|
-
} catch (error) {
|
|
771
|
-
if (error instanceof DeviceAdmissionRefusal && error.code === "STIM_AT_CAPACITY") {
|
|
772
|
-
const at = now();
|
|
773
|
-
await withConfigLockAsync$1(() => recordCapacityRefusal({
|
|
774
|
-
platform: device.platform,
|
|
775
|
-
max: binding?.max ?? max,
|
|
776
|
-
workspace
|
|
777
|
-
}, at)).catch(() => {});
|
|
778
|
-
}
|
|
779
|
-
throw error;
|
|
780
|
-
} finally {
|
|
781
|
-
(0, ownership_claim_exports.releaseClaim)(ticket);
|
|
782
|
-
if (visibleWait) waitingFor(null);
|
|
783
|
-
if (started !== void 0) {
|
|
784
|
-
const ms = Math.max(0, Math.round(now() - started));
|
|
785
|
-
const at = now();
|
|
786
|
-
await withConfigLockAsync$1(() => recordCapacityWait({
|
|
787
|
-
platform: device.platform,
|
|
788
|
-
ms,
|
|
789
|
-
max: binding?.max ?? max,
|
|
790
|
-
workspace,
|
|
791
|
-
...reclaimed ? { reclaimed } : {}
|
|
792
|
-
}, at)).catch(() => {});
|
|
793
|
-
onWait(ms);
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
requireMembership();
|
|
797
|
-
onBootStart();
|
|
798
|
-
return await boot();
|
|
799
|
-
} finally {
|
|
800
|
-
(0, ownership_claim_exports.releaseClaim)(marker);
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
function deviceTypeMismatch(recordedTypeId, requestedName, deviceTypes) {
|
|
804
|
-
if (!requestedName || !recordedTypeId) return null;
|
|
805
|
-
const wanted = (deviceTypes || []).find((d) => d.name === requestedName);
|
|
806
|
-
if (!wanted) return null;
|
|
807
|
-
if (wanted.identifier === recordedTypeId) return null;
|
|
808
|
-
const recorded = (deviceTypes || []).find((d) => d.identifier === recordedTypeId);
|
|
809
|
-
return `this project's sim is ${recorded ? recorded.name : recordedTypeId}, but --device-type asked for ${requestedName}`;
|
|
810
|
-
}
|
|
811
|
-
function runtimeMismatch(recordedRuntimeId, requested, runtimes) {
|
|
812
|
-
if (!requested || !recordedRuntimeId) return null;
|
|
813
|
-
const wanted = runtimes.find((r) => iosRuntimeMatches(r, requested));
|
|
814
|
-
if (!wanted || wanted.identifier === recordedRuntimeId) return null;
|
|
815
|
-
return `this project's sim runs iOS ${parseRuntimeVersion(recordedRuntimeId)}, but --runtime asked for ${requested}`;
|
|
816
|
-
}
|
|
817
|
-
function installedNames(names) {
|
|
818
|
-
const unique = [...new Set(names.filter((n) => typeof n === "string" && n !== ""))];
|
|
819
|
-
return unique.length > 0 ? unique.join(", ") : "none";
|
|
820
|
-
}
|
|
821
|
-
/** Appends which settings layer supplied an unresolved device selector, so the user knows where to fix it. */
|
|
822
|
-
function layerNote(refusal, key, flag, origin) {
|
|
823
|
-
if (flag !== void 0 && flag !== null) return refusal;
|
|
824
|
-
if (!origin) return refusal;
|
|
825
|
-
return {
|
|
826
|
-
message: `${refusal.message} ${key} is set at the ${origin} layer.`,
|
|
827
|
-
remedy: `${refusal.remedy} Fix it with \`stim settings set ${key} <value> --scope ${origin}\` or \`stim settings unset ${key} --scope ${origin}\`.`
|
|
828
|
-
};
|
|
829
|
-
}
|
|
830
|
-
function unknownIosRuntimeRefusal(requested, runtimes) {
|
|
831
|
-
if (!requested) return null;
|
|
832
|
-
if ((runtimes || []).some((r) => iosRuntimeMatches(r, requested))) return null;
|
|
833
|
-
return {
|
|
834
|
-
message: `No installed simulator runtime matches "${requested}". Installed runtimes: ${installedNames((runtimes || []).map((r) => r.version))}.`,
|
|
835
|
-
remedy: "Pass `--runtime` (or set ios.runtime) a version printed above (\"26.5\") or a runtime's full name (\"iOS 26.5\"); nothing else matches. Install more runtimes through Xcode."
|
|
836
|
-
};
|
|
837
|
-
}
|
|
838
|
-
function creatableIosDeviceTypeNames(runtimes, runtime) {
|
|
839
|
-
return (runtime ? (runtimes || []).filter((r) => iosRuntimeMatches(r, runtime)) : runtimes || []).flatMap((r) => (r.supportedDeviceTypes || []).map((d) => d.name));
|
|
840
|
-
}
|
|
841
|
-
function unknownIosDeviceTypeRefusal(requested, runtimes, runtime) {
|
|
842
|
-
if (!requested) return null;
|
|
843
|
-
const creatable = creatableIosDeviceTypeNames(runtimes, runtime);
|
|
844
|
-
if (creatable.includes(requested)) return null;
|
|
845
|
-
return {
|
|
846
|
-
message: `No device type named "${requested}" can be created on ${runtime ? `runtime ${runtime}` : "any installed simulator runtime"}. ${runtime ? `Device types runtime ${runtime} supports` : "Device types the installed runtimes support"}: ${installedNames(creatable)}.`,
|
|
847
|
-
remedy: "Pass `--device-type` (or set ios.deviceType) one of the names printed above, exactly as `xcrun simctl list devicetypes` spells it. `xcrun simctl list devicetypes` also lists watchOS, tvOS and visionOS models, which no iOS runtime can create. Install more models through Xcode."
|
|
848
|
-
};
|
|
849
|
-
}
|
|
850
|
-
function unknownAndroidSystemImageRefusal(requested, images) {
|
|
851
|
-
if (!requested) return null;
|
|
852
|
-
if ((images || []).some((i) => i.pkg === requested)) return null;
|
|
853
|
-
return {
|
|
854
|
-
message: `No installed Android system image is named "${requested}". Installed system images: ${installedNames((images || []).map((i) => i.pkg))}.`,
|
|
855
|
-
remedy: "Pass `--system-image` (or set android.systemImage) to one of the package ids printed above."
|
|
856
|
-
};
|
|
857
|
-
}
|
|
858
|
-
function unknownAndroidDeviceProfileRefusal(requested, profiles) {
|
|
859
|
-
if (!requested) return null;
|
|
860
|
-
if (profiles.includes(requested)) return null;
|
|
861
|
-
return {
|
|
862
|
-
message: `No Android hardware profile is named "${requested}". Profiles avdmanager offers: ${installedNames(profiles)}.`,
|
|
863
|
-
remedy: "Pass `--device-profile` (or set android.deviceProfile) to one of the ids printed above, exactly as `avdmanager list device -c` spells it, e.g. \"pixel_fold\" or \"pixel_tablet\"."
|
|
864
|
-
};
|
|
865
|
-
}
|
|
866
|
-
//#endregion
|
|
867
|
-
export { withDeviceBootAdmission as _, countLiveOwnedDevicesAsync as a, deviceSlotUsages as c, peekDeviceSlots as d, runtimeMismatch as f, unknownIosRuntimeRefusal as g, unknownIosDeviceTypeRefusal as h, countLiveOwnedDevices as i, deviceTypeMismatch as l, unknownAndroidSystemImageRefusal as m, DeviceAdmissionRefusal as n, countLiveOwnedDevicesByPlatform as o, unknownAndroidDeviceProfileRefusal as p, checkDeviceCapacity as r, countLiveOwnedDevicesFrom as s, DEFAULT_DEVICE_SLOT_WAIT_MS as t, layerNote as u };
|