stim 1.16.0 → 1.17.1
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/{activity-BxwdD81S.mjs → activity-IRvJY_ru.mjs} +23 -3
- package/dist/{agent-device-usage-output-Dpih1Tfa.mjs → agent-device-usage-output-BK41vnzN.mjs} +3 -3
- package/dist/{android-Ift4kVvt.mjs → android-B6rDWrTm.mjs} +681 -319
- package/dist/{android-B_NumVyQ.mjs → android-B_YNZy61.mjs} +7 -43
- package/dist/{android-r2rBL8kV.mjs → android-DE4v59Mb.mjs} +12 -3
- package/dist/{android-cas-8U4DPqeR.mjs → android-cas-ZFpjJBL_.mjs} +6 -5
- package/dist/{app-install-CKWdbXIX.mjs → app-install-VPkI5WEg.mjs} +6 -5
- package/dist/{budget-DjrURfQn.mjs → budget-CUDz870h.mjs} +62 -25
- package/dist/{build-plan-DjeSb2Xl.mjs → build-plan-BJSXzsCL.mjs} +345 -86
- package/dist/{idle-DYVymG92.mjs → build-progress-D0WkjCPd.mjs} +170 -233
- package/dist/{build-slots-rUk77xqh.mjs → build-slots-41v_d2x9.mjs} +35 -25
- package/dist/{cli-BkYZ_6hy.mjs → cli-BLQqDGlX.mjs} +17 -17
- package/dist/cli.mjs +1 -1
- package/dist/{client-BTY686R6.mjs → client-CxNotugx.mjs} +313 -44
- package/dist/collector-run.mjs +3 -3
- package/dist/{command-output-DnJMdEY-.mjs → command-output-CDAIFD-8.mjs} +3 -3
- package/dist/{created-devices-D5owjN4C.mjs → created-devices-CpvzBL25.mjs} +1 -1
- package/dist/{dependency-state-B6cMXZab.mjs → dependency-state-CLcBHH2M.mjs} +1 -1
- package/dist/{deps-urbmfXig.mjs → deps-CVxlsRFn.mjs} +1 -1
- package/dist/{dev-client-CroAIjjj.mjs → dev-client-B-XC4uRH.mjs} +2 -2
- package/dist/{device-DZQjF7H5.mjs → device-C8IOoJPv.mjs} +7 -7
- package/dist/device-capacity-ZC4XSZ4R.mjs +580 -0
- package/dist/device-host-worker.mjs +436 -154
- package/dist/{device-ios-BadmeL2V.mjs → device-ios-C2Um-XUR.mjs} +75 -44
- package/dist/{device-lease-D-hLqx1b.mjs → device-lease-DpoS36_u.mjs} +2 -2
- package/dist/{device-lease-run-CqjShqWW.mjs → device-lease-run-B8YltIH_.mjs} +3 -3
- package/dist/{device-pool-CUaEWMMm.mjs → device-pool-3o7s5lo4.mjs} +2 -2
- package/dist/{device-remote-D5WVWqOq.mjs → device-remote-hFSkpfQu.mjs} +12 -12
- package/dist/{doctor-Cimf-gGB.mjs → doctor-BF_435OC.mjs} +40 -58
- package/dist/{error-diagnostics-B9L4Ae0x.mjs → error-diagnostics-C-NMnhrk.mjs} +7 -7
- package/dist/{gc-Bqy4bjEu.mjs → gc-BsWKa_-d.mjs} +313 -30
- package/dist/{gradle-BsO1MdsH.mjs → gradle-bsBRy6i9.mjs} +3 -3
- package/dist/{guide-B_uyKTxW.mjs → guide-DC3n6-NH.mjs} +1776 -655
- package/dist/{guide-status-CjEZMAD4.mjs → guide-status-Du-ZQ3FD.mjs} +1 -1
- package/dist/hosted-android-DgU31wY3.mjs +32 -0
- package/dist/{hosted-client-a-WmqE2L.mjs → hosted-client-CdSoH-G6.mjs} +5 -6
- package/dist/hosted-ios-BTaVj4Jb.mjs +15 -0
- package/dist/{hosted-logs-D5g041Q3.mjs → hosted-logs-SxfBvSvc.mjs} +16 -16
- package/dist/{hosted-macos-BSZyIb3y.mjs → hosted-macos-CT9E3meL.mjs} +4 -4
- package/dist/{hosted-ios-CIWelIqf.mjs → hosted-native-CZLOrMrN.mjs} +77 -42
- package/dist/idle-BXG__1BA.mjs +176 -0
- package/dist/{idle-shutdown-CTsDnAde.mjs → idle-shutdown-IGyNV8Cr.mjs} +25 -17
- package/dist/{in-use-DvBw0Kxo.mjs → in-use-CvGOUn-x.mjs} +11 -11
- package/dist/{ios-aq3QoGI3.mjs → ios-C7JuT1bI.mjs} +173 -91
- package/dist/{ios-BaInn3sW.mjs → ios-DSfnPfse.mjs} +4 -4
- package/dist/{ios-device-FfCN_C8p.mjs → ios-device-pAN4t54-.mjs} +1 -1
- package/dist/ios-state-8L3lgum6.mjs +89 -0
- package/dist/{launch-verify-2DBRizjo.mjs → launch-verify-lTnWZikR.mjs} +2 -2
- package/dist/{logs-CHEVK8zD.mjs → logs-DNxKcok9.mjs} +36 -23
- package/dist/{macos-CSYEAFmF.mjs → macos-BEs8cwKx.mjs} +75 -34
- package/dist/macos-run.mjs +1 -1
- package/dist/maintenance-run.mjs +2 -2
- package/dist/{metro-BeK1ZPz1.mjs → metro-g8NqiJp-.mjs} +1 -1
- package/dist/{metro-gateway-BJ-rxQAB.mjs → metro-gateway-CHqK0p-a.mjs} +22 -3
- package/dist/{named-ports-Bk_YXEmE.mjs → named-ports-C8m8EQs2.mjs} +2 -2
- package/dist/{native-run-NaBrRFtQ.mjs → native-run-D1Q56rAD.mjs} +17 -6
- package/dist/{native-runtime-DBzRnEKS.mjs → native-runtime-YhaSxP4v.mjs} +6 -6
- package/dist/offload-worker.mjs +9 -9
- package/dist/{teardown-CL6vDs2Y.mjs → ownership-BMLqyBgJ.mjs} +603 -26
- package/dist/{ownership-DiA16AsN.mjs → ownership-DyRQru0q.mjs} +2 -2
- package/dist/{ports-R2k4Knz3.mjs → ports-4nYqxPWC.mjs} +2 -2
- package/dist/{prebuild-D2fsca_Z.mjs → prebuild-D7RUMkwN.mjs} +3 -3
- package/dist/{preview-DhVbvPAZ.mjs → preview-C_JjkEy4.mjs} +7 -7
- package/dist/{project-Du91Ilvc.mjs → project-D47Rh0ly.mjs} +7 -2
- package/dist/{recordings-BIXOzVnr.mjs → recordings-BNgsD1Tr.mjs} +1 -1
- package/dist/{reload-rM9jV1fz.mjs → reload-byOjE3P4.mjs} +62 -35
- package/dist/{remote-cache-C8-V13XN.mjs → remote-cache-Ci4DQYYj.mjs} +3 -3
- package/dist/{run-f4vI6EnK.mjs → run-CkE9y0nc.mjs} +1 -1
- package/dist/{server-bare-CpPGmnmO.mjs → server-bare-BkrSxSsZ.mjs} +1 -1
- package/dist/{server-expo-MC0rHlOC.mjs → server-expo-CHTyXD0G.mjs} +2 -2
- package/dist/server-expo-CeakhI8P.mjs +2 -0
- package/dist/{settings-oTSkkNrM.mjs → settings-Cfd-0r2d.mjs} +10 -9
- package/dist/{settings-ZkDzkzvB.mjs → settings-SR9OmW9h.mjs} +3 -3
- package/dist/settings.schema.json +123 -76
- package/dist/{simslim-B8-7AoTI.mjs → simslim-BWNrc3M3.mjs} +3 -3
- package/dist/{slot-launch-B8VrHxn1.mjs → slot-launch-DmjBmpQV.mjs} +8 -8
- package/dist/{start-CX8Ig-U2.mjs → start-BEP-XmEA.mjs} +1 -1
- package/dist/{start-ClDfwXa4.mjs → start-BHIersSm.mjs} +11 -11
- package/dist/{state-DcW6vOd_.mjs → state-BKa8wNN5.mjs} +1 -1
- package/dist/{state-3DwSQIBK.mjs → state-DyaNTgfR.mjs} +1 -1
- package/dist/{state-C1jB7Q8q.mjs → state-PeQiJuIu.mjs} +2 -2
- package/dist/{stats-CEZ_i5je.mjs → stats-Bq4fJOdB.mjs} +7 -4
- package/dist/{status-Cs7VLg9D.mjs → status-CqeWbIWW.mjs} +128 -38
- package/dist/{status-BuvBFeII.mjs → status-CzS4bPO9.mjs} +4 -4
- package/dist/{stop-Bffozsac.mjs → stop-Cj01je8D.mjs} +48 -27
- package/dist/{stop-B0Yxtmiy.mjs → stop-D03prQnW.mjs} +4 -4
- package/dist/{stop-Dt_dM16A.mjs → stop-DOxwkH4j.mjs} +2 -2
- package/dist/{stop-cause-CidxIqup.mjs → stop-cause-rhmGfwvt.mjs} +1 -1
- package/dist/supervisor-run.d.mts +7 -4
- package/dist/supervisor-run.mjs +26 -28
- package/dist/{support-D4aYJniO.mjs → support-BE-tbOP9.mjs} +45 -20
- package/dist/{support-CAP8qDUF.mjs → support-WvD__tSV.mjs} +7 -7
- package/dist/{toolchain-DkUflduf.mjs → toolchain-CdrxgUeA.mjs} +8 -8
- package/dist/{warm-progress-CpGv3Wb6.mjs → warm-progress-DF4JRvYI.mjs} +3 -3
- package/dist/watchman-APYjXSQa.mjs +48 -0
- package/dist/{web-DWUaMp8P.mjs → web-DddUdJzr.mjs} +12 -13
- package/dist/web-run.mjs +4 -4
- package/dist/{workspace-process-lock-BL7Q0HCO.mjs → workspace-process-lock-2mNzOlaB.mjs} +3 -2
- package/dist/{workspace-state-B8-aJ1_5.mjs → workspace-state-DqcCbpE7.mjs} +2 -2
- package/dist/{workspaces-BV4pdHFz.mjs → workspaces-Bcib97-6.mjs} +3 -3
- package/dist/{worktree-K-xj9Db0.mjs → worktree-AK_m-d_m.mjs} +141 -40
- package/dist/{worktree-jnG8Iph5.mjs → worktree-CsAjSmEo.mjs} +1 -1
- package/package.json +5 -5
- package/dist/device-capacity-BHwxNBlu.mjs +0 -136
- package/dist/ios-state-DS5UpYTH.mjs +0 -48
- package/dist/machines-BIBUdhpj.mjs +0 -265
- package/dist/ownership-hCkPzGmr.mjs +0 -571
- package/dist/server-expo-CVqO2c35.mjs +0 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as getExecutor } from "./exec-CaErVv7t.mjs";
|
|
2
|
-
import { gt as repoRoot } from "./settings-
|
|
2
|
+
import { gt as repoRoot } from "./settings-Cfd-0r2d.mjs";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { dirname, join, relative } from "node:path";
|
|
5
5
|
//#region src/dependency-state.ts
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as getExecutor } from "./exec-CaErVv7t.mjs";
|
|
2
2
|
import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
|
|
3
3
|
import { a as spawnDeclared } from "./spawn-claims-D0XfSe8u.mjs";
|
|
4
|
-
import { S as bundlerPin, u as HEARTBEAT_INTERVAL_MS, y as startBuildHeartbeat } from "./toolchain-
|
|
4
|
+
import { S as bundlerPin, u as HEARTBEAT_INTERVAL_MS, y as startBuildHeartbeat } from "./toolchain-CdrxgUeA.mjs";
|
|
5
5
|
import { existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import { delimiter, dirname, join, relative, resolve, sep } from "node:path";
|
|
7
7
|
import { homedir } from "node:os";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { l as isPackageResolvable, m as resolvePackageJson } from "./project-D47Rh0ly.mjs";
|
|
2
|
+
import { E as readBundleSchemes, T as pickDevClientScheme } from "./app-install-VPkI5WEg.mjs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { isJsonObject, readJsonObject } from "@stim-cli/core/state";
|
|
5
5
|
//#region src/commands/dev-client.ts
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { H as projectDeviceSlots, R as deviceSlotKey, V as parseDeviceSlotOption, p as getProject } from "./config-D0DbpZtM.mjs";
|
|
2
|
-
import { r as clockTime } from "./command-output-
|
|
3
|
-
import {
|
|
4
|
-
import { n as fileLeaseIo, o as parseLeaseDuration, p as takeLease, u as releaseWorkspaceLeases } from "./device-lease-
|
|
5
|
-
import { E as getAvdNameForSerial, G as probeEmulatorSerial, N as memoizeEmulatorProbe, O as listAdbDevices, U as physicalDeviceModel, Y as resolvePhysicalDevice, c as androidPoolNoCandidatesRefusal, s as androidPoolCandidates } from "./android-
|
|
6
|
-
import { l as parseDeviceWait, p as waitForDevice } from "./device-lease-run-
|
|
7
|
-
import { t as selectFromPool } from "./device-pool-
|
|
8
|
-
import { a as iosPoolCandidates, c as isWirelessIosDevice, f as resolveIosPhysicalDevice, l as listIosDevices, o as iosPoolNoCandidatesRefusal } from "./ios-device-
|
|
2
|
+
import { r as clockTime } from "./command-output-CDAIFD-8.mjs";
|
|
3
|
+
import { s as findProjectRoot, t as NO_PROJECT_REFUSAL } from "./project-D47Rh0ly.mjs";
|
|
4
|
+
import { n as fileLeaseIo, o as parseLeaseDuration, p as takeLease, u as releaseWorkspaceLeases } from "./device-lease-DpoS36_u.mjs";
|
|
5
|
+
import { E as getAvdNameForSerial, G as probeEmulatorSerial, N as memoizeEmulatorProbe, O as listAdbDevices, U as physicalDeviceModel, Y as resolvePhysicalDevice, c as androidPoolNoCandidatesRefusal, s as androidPoolCandidates } from "./android-B_YNZy61.mjs";
|
|
6
|
+
import { l as parseDeviceWait, p as waitForDevice } from "./device-lease-run-B8YltIH_.mjs";
|
|
7
|
+
import { t as selectFromPool } from "./device-pool-3o7s5lo4.mjs";
|
|
8
|
+
import { a as iosPoolCandidates, c as isWirelessIosDevice, f as resolveIosPhysicalDevice, l as listIosDevices, o as iosPoolNoCandidatesRefusal } from "./ios-device-pAN4t54-.mjs";
|
|
9
9
|
import chalk from "chalk";
|
|
10
10
|
//#region src/commands/device.ts
|
|
11
11
|
const DEFAULT_FOR = "5m";
|
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
import { H as projectDeviceSlots, _ as loadConfig$1, d as getConfigDir, u as getConcurrencyLimits, z as deviceSlotPlatforms } from "./config-D0DbpZtM.mjs";
|
|
2
|
+
import { n as ownership_claim_exports } from "./process-identity-BmRm1qV0.mjs";
|
|
3
|
+
import { a as formatElapsed, l as phaseLine } from "./command-output-CDAIFD-8.mjs";
|
|
4
|
+
import { E as projectDeviceReclaimIdleMinutes } from "./settings-Cfd-0r2d.mjs";
|
|
5
|
+
import { x as workspaceLogsDir$1 } from "./workspace-state-DqcCbpE7.mjs";
|
|
6
|
+
import { n as withIdleWorkspace, o as canonicalPath } from "./in-use-CvGOUn-x.mjs";
|
|
7
|
+
import { O as listAdbDevices } from "./android-B_YNZy61.mjs";
|
|
8
|
+
import { _ as parseRuntimeVersion, c as iosRuntimeMatches, u as listAllIosSims } from "./ios-DSfnPfse.mjs";
|
|
9
|
+
import { n as workspaceProcessLockError, t as withWorkspaceProcessLock } from "./workspace-process-lock-2mNzOlaB.mjs";
|
|
10
|
+
import { h as recordCapacityWait, m as recordCapacityRefusal } from "./build-progress-D0WkjCPd.mjs";
|
|
11
|
+
import { r as createNdjsonWriter } from "./ndjson-PclOIwVY.mjs";
|
|
12
|
+
import { n as dueIdleDevices, r as shutDownIdleDevices } from "./idle-shutdown-IGyNV8Cr.mjs";
|
|
13
|
+
import { realpathSync } from "node:fs";
|
|
14
|
+
import { basename, join } from "node:path";
|
|
15
|
+
import { LOG_ROTATE_BYTES, workspaceId } from "@stim-cli/core";
|
|
16
|
+
import { DEVICE_RECLAIM_REASON } from "@stim-cli/core/state";
|
|
17
|
+
import { setTimeout } from "node:timers/promises";
|
|
18
|
+
//#region src/devices/queue-reclaim.ts
|
|
19
|
+
async function reclaimIdleDevice(root, idleMs, out, now, skipped) {
|
|
20
|
+
try {
|
|
21
|
+
const self = canonicalPath(root);
|
|
22
|
+
const candidate = dueIdleDevices(void 0, idleMs, now()).filter(({ device }) => {
|
|
23
|
+
try {
|
|
24
|
+
return realpathSync(device.project) !== self;
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}).toSorted((a, b) => b.idleForMs - a.idleForMs).find(({ device }) => !skipped.has(`${device.kind}:${device.id}`));
|
|
29
|
+
if (!candidate) return 0;
|
|
30
|
+
const { device, idleForMs } = candidate;
|
|
31
|
+
const label = loadConfig$1()?.projects[device.project]?.label || basename(device.project);
|
|
32
|
+
const log = (event) => {
|
|
33
|
+
const writer = createNdjsonWriter(join(workspaceLogsDir$1(device.project), "metro.ndjson"), { maxBytes: LOG_ROTATE_BYTES });
|
|
34
|
+
writer.write({
|
|
35
|
+
src: "metro",
|
|
36
|
+
...event
|
|
37
|
+
});
|
|
38
|
+
writer.close();
|
|
39
|
+
if (event.level === "warn") out(`${"device".padEnd(11)} ${event.msg}`);
|
|
40
|
+
};
|
|
41
|
+
skipped.add(`${device.kind}:${device.id}`);
|
|
42
|
+
const run = await withIdleWorkspace(device.project, () => shutDownIdleDevices(device.project, idleMs, log, now(), {
|
|
43
|
+
only: device,
|
|
44
|
+
reason: DEVICE_RECLAIM_REASON
|
|
45
|
+
}), {
|
|
46
|
+
purpose: "device queue reclaim",
|
|
47
|
+
supervisor: false,
|
|
48
|
+
managedLocks: false
|
|
49
|
+
});
|
|
50
|
+
if (!run.ran) {
|
|
51
|
+
log({
|
|
52
|
+
level: "warn",
|
|
53
|
+
event: "device_idle_shutdown_failed",
|
|
54
|
+
msg: `could not reclaim ${device.name}: ${run.reasons.join("; ")}`
|
|
55
|
+
});
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
if (!run.value) return 0;
|
|
59
|
+
skipped.delete(`${device.kind}:${device.id}`);
|
|
60
|
+
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`);
|
|
61
|
+
return run.value;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
out(`${"device".padEnd(11)} could not reclaim an idle device: ${error?.message || error}`);
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/engine/device-capacity.ts
|
|
69
|
+
const LIVE_SIM_STATES = /* @__PURE__ */ new Set(["Booted", "Booting"]);
|
|
70
|
+
const ADMISSION_LOCK = "device-admission";
|
|
71
|
+
const ADMISSION_LOCK_WAIT_MS = 3e5;
|
|
72
|
+
const LOCK_QUIET_MS = 5e3;
|
|
73
|
+
const LOCK_PROGRESS_MS = 3e4;
|
|
74
|
+
const INVENTORY_REFRESH_MS = 1e4;
|
|
75
|
+
const LISTING_TIMEOUT_MS = 3e4;
|
|
76
|
+
const NO_ADB = {
|
|
77
|
+
emulators: [],
|
|
78
|
+
physical: [],
|
|
79
|
+
unhealthy: []
|
|
80
|
+
};
|
|
81
|
+
var DeviceAdmissionRefusal = class extends Error {
|
|
82
|
+
code;
|
|
83
|
+
remedy;
|
|
84
|
+
constructor(refusal) {
|
|
85
|
+
super(refusal.message);
|
|
86
|
+
this.code = refusal.code;
|
|
87
|
+
this.remedy = refusal.remedy;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
var DeviceCountUnavailable = class extends Error {};
|
|
91
|
+
function deviceKey(platform, key) {
|
|
92
|
+
return `${platform}:${key}`;
|
|
93
|
+
}
|
|
94
|
+
function bootingDevicesRoot() {
|
|
95
|
+
return join(getConfigDir(), "device-boots");
|
|
96
|
+
}
|
|
97
|
+
function liveEmulatorPorts(adb) {
|
|
98
|
+
return [...adb.emulators, ...adb.unhealthy.filter((entry) => entry.kind === "emulator")];
|
|
99
|
+
}
|
|
100
|
+
function readBootingDevices() {
|
|
101
|
+
const survey = (0, ownership_claim_exports.readClaimSet)(bootingDevicesRoot());
|
|
102
|
+
const unresolved = survey.unresolved[0];
|
|
103
|
+
if (unresolved) throw new ownership_claim_exports.ClaimRefusedError({
|
|
104
|
+
claimPath: unresolved.path,
|
|
105
|
+
root: bootingDevicesRoot(),
|
|
106
|
+
reason: unresolved.reason,
|
|
107
|
+
label: "device boot"
|
|
108
|
+
});
|
|
109
|
+
const booting = [];
|
|
110
|
+
for (const holder of survey.live) {
|
|
111
|
+
const { platform, key } = holder.details;
|
|
112
|
+
if (typeof platform === "string" && typeof key === "string") booting.push({
|
|
113
|
+
platform,
|
|
114
|
+
key,
|
|
115
|
+
displayName: typeof holder.details.displayName === "string" ? holder.details.displayName : void 0
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return booting;
|
|
119
|
+
}
|
|
120
|
+
function listSimsForCount() {
|
|
121
|
+
return process.platform === "darwin" ? listAllIosSims({ timeoutMs: LISTING_TIMEOUT_MS }) : [];
|
|
122
|
+
}
|
|
123
|
+
function listAdbForCount() {
|
|
124
|
+
return listAdbDevices({ timeoutMs: LISTING_TIMEOUT_MS });
|
|
125
|
+
}
|
|
126
|
+
function toolAbsent(error) {
|
|
127
|
+
const { code, status, stderr } = error ?? {};
|
|
128
|
+
return code === "ENOENT" || status === 127 || /unable to find utility|invalid active developer path|Xcode license/.test(String(stderr ?? ""));
|
|
129
|
+
}
|
|
130
|
+
function recordsOwnedEmulator(config) {
|
|
131
|
+
return Object.values(config?.projects || {}).some((project) => projectDeviceSlots(project).some(({ platforms }) => platforms.android?.owned && typeof platforms.android.consolePort === "number"));
|
|
132
|
+
}
|
|
133
|
+
function liveOwnedDeviceKeys({ sims = [], adbEmulators = [], config = null, booting = [], exclude = [] }) {
|
|
134
|
+
const keys = /* @__PURE__ */ new Set();
|
|
135
|
+
for (const sim of sims) {
|
|
136
|
+
if (!sim?.name?.startsWith("stim-")) continue;
|
|
137
|
+
if (exclude.some((device) => device.platform === "ios" && device.key === sim.udid)) continue;
|
|
138
|
+
if (LIVE_SIM_STATES.has(sim.state)) keys.add(deviceKey("ios", sim.udid));
|
|
139
|
+
}
|
|
140
|
+
const livePorts = new Set(adbEmulators.map((e) => e.consolePort));
|
|
141
|
+
for (const proj of Object.values(config?.projects || {})) for (const { platforms } of projectDeviceSlots(proj)) {
|
|
142
|
+
const android = platforms.android;
|
|
143
|
+
if (android?.owned && android.avdName && typeof android.consolePort === "number" && livePorts.has(android.consolePort)) keys.add(deviceKey("android", android.avdName));
|
|
144
|
+
}
|
|
145
|
+
for (const device of booting) if (!exclude.some((excluded) => excluded.platform === device.platform && excluded.key === device.key)) keys.add(deviceKey(device.platform, device.key));
|
|
146
|
+
return keys;
|
|
147
|
+
}
|
|
148
|
+
function inventoryKeys(inventory, exclude = []) {
|
|
149
|
+
return liveOwnedDeviceKeys({
|
|
150
|
+
...inventory,
|
|
151
|
+
adbEmulators: liveEmulatorPorts(inventory.adb),
|
|
152
|
+
exclude
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
function atCapacityRefusal(count, max) {
|
|
156
|
+
return {
|
|
157
|
+
code: "STIM_AT_CAPACITY",
|
|
158
|
+
message: count < max ? `${count}/${max} Stim devices are in use, but another run is ahead in the device slot queue.` : `${count} Stim device(s) are already booted and concurrency.maxDevices is ${max}, so booting another would exceed the cap.`,
|
|
159
|
+
remedy: "stop an environment (stim stop) or raise concurrency.maxDevices"
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function uncountedRefusal(error) {
|
|
163
|
+
return {
|
|
164
|
+
code: "STIM_NO_DEVICE",
|
|
165
|
+
message: `concurrency.maxDevices is set, and Stim could not count the booted devices: ${error?.message || error}`,
|
|
166
|
+
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.`
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function listed(source, empty, absent = () => false) {
|
|
170
|
+
try {
|
|
171
|
+
return (typeof source === "function" ? source() : source) ?? empty;
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (absent(error)) return empty;
|
|
174
|
+
throw new DeviceCountUnavailable("", { cause: error });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function readInventory({ sims = listSimsForCount, adb = listAdbForCount, config = loadConfig$1, booting = readBootingDevices }) {
|
|
178
|
+
try {
|
|
179
|
+
const recorded = listed(config, null);
|
|
180
|
+
return {
|
|
181
|
+
booting: listed(booting, []),
|
|
182
|
+
sims: listed(sims, [], toolAbsent),
|
|
183
|
+
adb: recordsOwnedEmulator(recorded) ? listed(adb, NO_ADB, toolAbsent) : NO_ADB,
|
|
184
|
+
config: recorded
|
|
185
|
+
};
|
|
186
|
+
} catch (error) {
|
|
187
|
+
if (error instanceof DeviceCountUnavailable) {
|
|
188
|
+
if ((0, ownership_claim_exports.isClaimRefusal)(error.cause)) throw error.cause;
|
|
189
|
+
return uncountedRefusal(error.cause);
|
|
190
|
+
}
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Owned simulators that are booted or booting, owned emulators adb lists in any state, and devices another
|
|
196
|
+
* run is booting under `concurrency.maxDevices`, each counted once unless excluded; when a listing fails, why the count is unknown.
|
|
197
|
+
*/
|
|
198
|
+
function countLiveOwnedDevices(sources = {}, { exclude = [] } = {}) {
|
|
199
|
+
try {
|
|
200
|
+
const inventory = readInventory(sources);
|
|
201
|
+
return "code" in inventory ? { unknown: inventory.message } : inventoryKeys(inventory, exclude).size;
|
|
202
|
+
} catch (error) {
|
|
203
|
+
if ((0, ownership_claim_exports.isClaimRefusal)(error)) return { unknown: error.message };
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function workspaceHasLiveDevice({ platform, project, slot = "default", sims = [], adbEmulators = [] } = {}) {
|
|
208
|
+
if (!platform) return false;
|
|
209
|
+
const record = deviceSlotPlatforms(project, slot)?.[platform];
|
|
210
|
+
if (!record) return false;
|
|
211
|
+
if (platform === "ios") return sims.some((s) => s.udid === record.deviceUdid && LIVE_SIM_STATES.has(s.state));
|
|
212
|
+
return typeof record.consolePort === "number" && adbEmulators.some((e) => e.consolePort === record.consolePort);
|
|
213
|
+
}
|
|
214
|
+
function deviceCapacityRefusal({ platform, project, slot = "default", max, sims = [], adb = null, config = null, booting = [] } = {}) {
|
|
215
|
+
if (!max || max <= 0) return null;
|
|
216
|
+
const adbEmulators = liveEmulatorPorts(adb ?? NO_ADB);
|
|
217
|
+
if (workspaceHasLiveDevice({
|
|
218
|
+
platform,
|
|
219
|
+
project,
|
|
220
|
+
slot,
|
|
221
|
+
sims,
|
|
222
|
+
adbEmulators
|
|
223
|
+
})) return null;
|
|
224
|
+
const count = liveOwnedDeviceKeys({
|
|
225
|
+
sims,
|
|
226
|
+
adbEmulators,
|
|
227
|
+
config,
|
|
228
|
+
booting
|
|
229
|
+
}).size;
|
|
230
|
+
return count < max ? null : atCapacityRefusal(count, max);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* The early `concurrency.maxDevices` check, before Metro starts or a device is created. It is advisory, so an
|
|
234
|
+
* unknown count passes: `withDeviceBootAdmission` makes the binding decision when the boot starts.
|
|
235
|
+
*/
|
|
236
|
+
function checkDeviceCapacity({ platform, project, slot = "default", max, ...sources } = {}) {
|
|
237
|
+
if (!max || max <= 0) return null;
|
|
238
|
+
let inventory;
|
|
239
|
+
try {
|
|
240
|
+
inventory = readInventory(sources);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
if ((0, ownership_claim_exports.isClaimRefusal)(error)) return null;
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
if ("code" in inventory) return null;
|
|
246
|
+
return deviceCapacityRefusal({
|
|
247
|
+
platform,
|
|
248
|
+
project,
|
|
249
|
+
slot,
|
|
250
|
+
max,
|
|
251
|
+
...inventory
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
function peekDeviceSlots({ platform, project, slot = "default", max = getConcurrencyLimits().maxDevices, sources = {} }) {
|
|
255
|
+
try {
|
|
256
|
+
const inventory = readInventory(sources);
|
|
257
|
+
if ("code" in inventory) return {
|
|
258
|
+
count: null,
|
|
259
|
+
max,
|
|
260
|
+
queued: 0,
|
|
261
|
+
localLive: false
|
|
262
|
+
};
|
|
263
|
+
const owned = deviceSlotPlatforms(project, slot)?.[platform]?.owned === true;
|
|
264
|
+
return {
|
|
265
|
+
count: inventoryKeys(inventory).size,
|
|
266
|
+
max,
|
|
267
|
+
queued: liveWaiters().length,
|
|
268
|
+
localLive: owned && workspaceHasLiveDevice({
|
|
269
|
+
platform,
|
|
270
|
+
project,
|
|
271
|
+
slot,
|
|
272
|
+
sims: inventory.sims,
|
|
273
|
+
adbEmulators: liveEmulatorPorts(inventory.adb)
|
|
274
|
+
})
|
|
275
|
+
};
|
|
276
|
+
} catch (error) {
|
|
277
|
+
if (!(0, ownership_claim_exports.isClaimRefusal)(error)) throw error;
|
|
278
|
+
return {
|
|
279
|
+
count: null,
|
|
280
|
+
max,
|
|
281
|
+
queued: 0,
|
|
282
|
+
localLive: false
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const DEFAULT_DEVICE_SLOT_WAIT_MS = 6e5;
|
|
287
|
+
function deviceSlotWaitingLine({ count, max, holders, elapsedMs, queue }) {
|
|
288
|
+
return `${"device".padEnd(11)} waiting for a device slot (${count}/${max} in use${holders.length ? `: ${holders.join(", ")}` : ""}), ${formatElapsed(elapsedMs)} elapsed -- stim guide lifecycle concurrency${queue ? `; ${queue}` : ""}`;
|
|
289
|
+
}
|
|
290
|
+
function deviceHolders(inventory, keys) {
|
|
291
|
+
const names = /* @__PURE__ */ new Map();
|
|
292
|
+
for (const [root, project] of Object.entries(inventory.config?.projects ?? {})) for (const { platforms } of projectDeviceSlots(project)) {
|
|
293
|
+
if (platforms.ios?.deviceUdid) names.set(deviceKey("ios", platforms.ios.deviceUdid), project.label || basename(root));
|
|
294
|
+
if (platforms.android?.avdName) names.set(deviceKey("android", platforms.android.avdName), project.label || basename(root));
|
|
295
|
+
}
|
|
296
|
+
for (const sim of inventory.sims) {
|
|
297
|
+
const key = deviceKey("ios", sim.udid);
|
|
298
|
+
if (!names.has(key)) names.set(key, sim.name);
|
|
299
|
+
}
|
|
300
|
+
for (const device of inventory.booting) {
|
|
301
|
+
const key = deviceKey(device.platform, device.key);
|
|
302
|
+
if (!names.has(key)) names.set(key, device.displayName ?? device.key);
|
|
303
|
+
}
|
|
304
|
+
return [...new Set([...keys].map((key) => names.get(key) ?? key.slice(key.indexOf(":") + 1)))].toSorted();
|
|
305
|
+
}
|
|
306
|
+
function liveWaiters() {
|
|
307
|
+
const root = join(getConfigDir(), "device-waits");
|
|
308
|
+
const survey = (0, ownership_claim_exports.inspectClaimSet)(root, { label: "device wait" });
|
|
309
|
+
if (survey.exclusive) throw new ownership_claim_exports.ClaimRefusedError({
|
|
310
|
+
root,
|
|
311
|
+
claimPath: survey.exclusive.path,
|
|
312
|
+
reason: "the wait queue is held exclusively",
|
|
313
|
+
label: "device wait"
|
|
314
|
+
});
|
|
315
|
+
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));
|
|
316
|
+
}
|
|
317
|
+
function takeWaitTicket(device, waiters) {
|
|
318
|
+
const root = join(getConfigDir(), "device-waits");
|
|
319
|
+
const attempt = (0, ownership_claim_exports.tryAcquireClaim)({
|
|
320
|
+
root,
|
|
321
|
+
mode: "shared",
|
|
322
|
+
label: "device wait",
|
|
323
|
+
details: {
|
|
324
|
+
...device,
|
|
325
|
+
sequence: Math.max(0, ...waiters.map((holder) => Number(holder.details.sequence) || 0)) + 1
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
if (attempt.acquired) return attempt.acquired;
|
|
329
|
+
throw new ownership_claim_exports.ClaimRefusedError({
|
|
330
|
+
root,
|
|
331
|
+
claimPath: attempt.held?.path ?? root,
|
|
332
|
+
reason: "the wait queue is held exclusively",
|
|
333
|
+
label: "device wait"
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
function takeBootMarker(device) {
|
|
337
|
+
const attempt = (0, ownership_claim_exports.tryAcquireClaim)({
|
|
338
|
+
root: bootingDevicesRoot(),
|
|
339
|
+
mode: "shared",
|
|
340
|
+
label: "device boot",
|
|
341
|
+
details: { ...device }
|
|
342
|
+
});
|
|
343
|
+
if (attempt.acquired) return attempt.acquired;
|
|
344
|
+
throw new DeviceAdmissionRefusal({
|
|
345
|
+
code: "STIM_NO_DEVICE",
|
|
346
|
+
message: `Another process holds ${bootingDevicesRoot()} exclusively, so this boot cannot be counted toward concurrency.maxDevices.`,
|
|
347
|
+
remedy: "Retry once that process finishes."
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
async function admissionTransaction(action, lockWaitMs, out, signal) {
|
|
351
|
+
const started = Date.now();
|
|
352
|
+
let lastLine = null;
|
|
353
|
+
const onHeld = () => {
|
|
354
|
+
const elapsed = Date.now() - started;
|
|
355
|
+
if (elapsed < LOCK_QUIET_MS || lastLine !== null && Date.now() - lastLine < LOCK_PROGRESS_MS) return;
|
|
356
|
+
lastLine = Date.now();
|
|
357
|
+
out(phaseLine("device", `waiting for other runs to finish counting booted devices (${formatElapsed(elapsed)})`));
|
|
358
|
+
};
|
|
359
|
+
try {
|
|
360
|
+
return await withWorkspaceProcessLock(getConfigDir(), ADMISSION_LOCK, action, {
|
|
361
|
+
external: true,
|
|
362
|
+
waitMs: lockWaitMs,
|
|
363
|
+
onHeld,
|
|
364
|
+
signal
|
|
365
|
+
});
|
|
366
|
+
} catch (error) {
|
|
367
|
+
if (workspaceProcessLockError(error) !== "timeout") throw error;
|
|
368
|
+
throw new DeviceAdmissionRefusal({
|
|
369
|
+
code: "STIM_NO_DEVICE",
|
|
370
|
+
message: `Waited ${formatElapsed(lockWaitMs)} for other runs to finish counting booted devices for concurrency.maxDevices.`,
|
|
371
|
+
remedy: "Listing devices is slow, which usually means the machine is overloaded. Retry once the load falls."
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
/** Boots an owned device under the Stim home's cap, reserving capacity before releasing admission. */
|
|
376
|
+
async function withDeviceBootAdmission(device, boot, { root, max = getConcurrencyLimits().maxDevices, sources = {}, lockWaitMs = ADMISSION_LOCK_WAIT_MS, out = () => {}, waitMs = DEFAULT_DEVICE_SLOT_WAIT_MS, noWait = false, displayName = basename(root), now = Date.now, signal, sleep = (ms) => setTimeout(ms, void 0, { signal }), waitingFor = () => {}, onWait = () => {} }) {
|
|
377
|
+
signal?.throwIfAborted();
|
|
378
|
+
if (!max || max <= 0) return boot();
|
|
379
|
+
const workspace = workspaceId(root);
|
|
380
|
+
device = {
|
|
381
|
+
...device,
|
|
382
|
+
workspace,
|
|
383
|
+
displayName
|
|
384
|
+
};
|
|
385
|
+
let ticket;
|
|
386
|
+
let marker;
|
|
387
|
+
let started;
|
|
388
|
+
let lastLine = -Infinity;
|
|
389
|
+
let visibleWait = false;
|
|
390
|
+
let lastInventory = {
|
|
391
|
+
count: 0,
|
|
392
|
+
holders: []
|
|
393
|
+
};
|
|
394
|
+
let lastListed = -Infinity;
|
|
395
|
+
let reclaimed = 0;
|
|
396
|
+
let lastReclaim = -Infinity;
|
|
397
|
+
const skippedReclaims = /* @__PURE__ */ new Set();
|
|
398
|
+
try {
|
|
399
|
+
try {
|
|
400
|
+
for (;;) {
|
|
401
|
+
const result = await admissionTransaction(async () => {
|
|
402
|
+
signal?.throwIfAborted();
|
|
403
|
+
const waiters = liveWaiters();
|
|
404
|
+
const first = waiters[0];
|
|
405
|
+
const turn = !first || first.claimId === ticket?.claimId;
|
|
406
|
+
const admit = () => {
|
|
407
|
+
const admitted = takeBootMarker(device);
|
|
408
|
+
if ((0, ownership_claim_exports.releaseClaim)(ticket)) ticket = void 0;
|
|
409
|
+
return { marker: admitted };
|
|
410
|
+
};
|
|
411
|
+
const wait = ({ count, holders }) => {
|
|
412
|
+
const ticketId = ticket?.claimId;
|
|
413
|
+
const position = ticketId ? waiters.findIndex((holder) => holder.claimId === ticketId) + 1 : waiters.length + 1;
|
|
414
|
+
const headName = first?.details.displayName ?? displayName;
|
|
415
|
+
const queue = `queue position ${position}${position > 1 ? " behind" : ", head"} ${headName}; queue: ${join(getConfigDir(), "device-waits")}`;
|
|
416
|
+
if (noWait || waitMs === 0) throw new DeviceAdmissionRefusal(atCapacityRefusal(count, max));
|
|
417
|
+
if (started !== void 0 && now() - started >= waitMs) throw new DeviceAdmissionRefusal({
|
|
418
|
+
code: "STIM_AT_CAPACITY",
|
|
419
|
+
message: `Waited ${formatElapsed(now() - started)} for a device slot; ${count}/${max} Stim devices are in use; ${queue}.`,
|
|
420
|
+
remedy: "Stop an environment (stim stop), retry with a longer --wait <seconds>, or raise concurrency.maxDevices."
|
|
421
|
+
});
|
|
422
|
+
if (!ticket) {
|
|
423
|
+
ticket = takeWaitTicket(device, waiters);
|
|
424
|
+
started = now();
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
count,
|
|
428
|
+
holders,
|
|
429
|
+
queue,
|
|
430
|
+
reclaim: turn && count >= max
|
|
431
|
+
};
|
|
432
|
+
};
|
|
433
|
+
if (ticket && !turn && now() - lastListed < INVENTORY_REFRESH_MS) {
|
|
434
|
+
const booting = sources.booting ?? readBootingDevices;
|
|
435
|
+
const own = deviceKey(device.platform, device.key);
|
|
436
|
+
if ((typeof booting === "function" ? booting() : booting).some((entry) => deviceKey(entry.platform, entry.key) === own)) return admit();
|
|
437
|
+
return wait(lastInventory);
|
|
438
|
+
}
|
|
439
|
+
const inventory = readInventory(sources);
|
|
440
|
+
if ("code" in inventory) throw new DeviceAdmissionRefusal(inventory);
|
|
441
|
+
const keys = inventoryKeys(inventory);
|
|
442
|
+
const own = deviceKey(device.platform, device.key);
|
|
443
|
+
const ownBooting = inventory.booting.some((entry) => deviceKey(entry.platform, entry.key) === own);
|
|
444
|
+
if (device.platform === "ios" && keys.has(own) || ownBooting) return admit();
|
|
445
|
+
keys.delete(own);
|
|
446
|
+
if (keys.size < max && turn) return admit();
|
|
447
|
+
lastListed = now();
|
|
448
|
+
lastInventory = {
|
|
449
|
+
count: keys.size,
|
|
450
|
+
holders: deviceHolders(inventory, keys)
|
|
451
|
+
};
|
|
452
|
+
return wait(lastInventory);
|
|
453
|
+
}, lockWaitMs, out, signal);
|
|
454
|
+
if ("marker" in result) {
|
|
455
|
+
marker = result.marker;
|
|
456
|
+
signal?.throwIfAborted();
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
signal?.throwIfAborted();
|
|
460
|
+
const elapsedMs = now() - started;
|
|
461
|
+
visibleWait = true;
|
|
462
|
+
waitingFor({
|
|
463
|
+
kind: "device-slot",
|
|
464
|
+
inUse: result.count,
|
|
465
|
+
max,
|
|
466
|
+
since: new Date(started).toISOString()
|
|
467
|
+
});
|
|
468
|
+
if (now() - lastLine >= 1e4) {
|
|
469
|
+
lastLine = now();
|
|
470
|
+
out(deviceSlotWaitingLine({
|
|
471
|
+
count: result.count,
|
|
472
|
+
max,
|
|
473
|
+
holders: result.holders,
|
|
474
|
+
elapsedMs,
|
|
475
|
+
queue: result.queue
|
|
476
|
+
}));
|
|
477
|
+
}
|
|
478
|
+
if (result.reclaim && now() - lastReclaim >= 15e3) {
|
|
479
|
+
lastReclaim = now();
|
|
480
|
+
try {
|
|
481
|
+
const minutes = projectDeviceReclaimIdleMinutes(canonicalPath(root));
|
|
482
|
+
if (minutes > 0) reclaimed += await reclaimIdleDevice(root, minutes * 6e4, out, now, skippedReclaims);
|
|
483
|
+
} catch (error) {
|
|
484
|
+
out(phaseLine("device", `could not reclaim an idle device: ${error?.message || error}`));
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
await sleep(Math.min(2e3, Math.max(0, waitMs - elapsedMs)));
|
|
488
|
+
}
|
|
489
|
+
} catch (error) {
|
|
490
|
+
if (error instanceof DeviceAdmissionRefusal && error.code === "STIM_AT_CAPACITY") recordCapacityRefusal({
|
|
491
|
+
platform: device.platform,
|
|
492
|
+
max,
|
|
493
|
+
workspace
|
|
494
|
+
}, now());
|
|
495
|
+
throw error;
|
|
496
|
+
} finally {
|
|
497
|
+
(0, ownership_claim_exports.releaseClaim)(ticket);
|
|
498
|
+
if (visibleWait) waitingFor(null);
|
|
499
|
+
if (started !== void 0) {
|
|
500
|
+
const ms = Math.max(0, Math.round(now() - started));
|
|
501
|
+
recordCapacityWait({
|
|
502
|
+
platform: device.platform,
|
|
503
|
+
ms,
|
|
504
|
+
max,
|
|
505
|
+
workspace,
|
|
506
|
+
...reclaimed ? { reclaimed } : {}
|
|
507
|
+
}, now());
|
|
508
|
+
onWait(ms);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return await boot();
|
|
512
|
+
} finally {
|
|
513
|
+
(0, ownership_claim_exports.releaseClaim)(marker);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
function deviceTypeMismatch(recordedTypeId, requestedName, deviceTypes) {
|
|
517
|
+
if (!requestedName || !recordedTypeId) return null;
|
|
518
|
+
const wanted = (deviceTypes || []).find((d) => d.name === requestedName);
|
|
519
|
+
if (!wanted) return null;
|
|
520
|
+
if (wanted.identifier === recordedTypeId) return null;
|
|
521
|
+
const recorded = (deviceTypes || []).find((d) => d.identifier === recordedTypeId);
|
|
522
|
+
return `this project's sim is ${recorded ? recorded.name : recordedTypeId}, but --device-type asked for ${requestedName}`;
|
|
523
|
+
}
|
|
524
|
+
function runtimeMismatch(recordedRuntimeId, requested, runtimes) {
|
|
525
|
+
if (!requested || !recordedRuntimeId) return null;
|
|
526
|
+
const wanted = runtimes.find((r) => iosRuntimeMatches(r, requested));
|
|
527
|
+
if (!wanted || wanted.identifier === recordedRuntimeId) return null;
|
|
528
|
+
return `this project's sim runs iOS ${parseRuntimeVersion(recordedRuntimeId)}, but --runtime asked for ${requested}`;
|
|
529
|
+
}
|
|
530
|
+
function installedNames(names) {
|
|
531
|
+
const unique = [...new Set(names.filter((n) => typeof n === "string" && n !== ""))];
|
|
532
|
+
return unique.length > 0 ? unique.join(", ") : "none";
|
|
533
|
+
}
|
|
534
|
+
/** Appends which settings layer supplied an unresolved device selector, so the user knows where to fix it. */
|
|
535
|
+
function layerNote(refusal, key, flag, origin) {
|
|
536
|
+
if (flag !== void 0 && flag !== null) return refusal;
|
|
537
|
+
if (!origin) return refusal;
|
|
538
|
+
return {
|
|
539
|
+
message: `${refusal.message} ${key} is set at the ${origin} layer.`,
|
|
540
|
+
remedy: `${refusal.remedy} Fix it with \`stim settings set ${key} <value> --scope ${origin}\` or \`stim settings unset ${key} --scope ${origin}\`.`
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
function unknownIosRuntimeRefusal(requested, runtimes) {
|
|
544
|
+
if (!requested) return null;
|
|
545
|
+
if ((runtimes || []).some((r) => iosRuntimeMatches(r, requested))) return null;
|
|
546
|
+
return {
|
|
547
|
+
message: `No installed simulator runtime matches "${requested}". Installed runtimes: ${installedNames((runtimes || []).map((r) => r.version))}.`,
|
|
548
|
+
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."
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
function creatableIosDeviceTypeNames(runtimes, runtime) {
|
|
552
|
+
return (runtime ? (runtimes || []).filter((r) => iosRuntimeMatches(r, runtime)) : runtimes || []).flatMap((r) => (r.supportedDeviceTypes || []).map((d) => d.name));
|
|
553
|
+
}
|
|
554
|
+
function unknownIosDeviceTypeRefusal(requested, runtimes, runtime) {
|
|
555
|
+
if (!requested) return null;
|
|
556
|
+
const creatable = creatableIosDeviceTypeNames(runtimes, runtime);
|
|
557
|
+
if (creatable.includes(requested)) return null;
|
|
558
|
+
return {
|
|
559
|
+
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)}.`,
|
|
560
|
+
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."
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function unknownAndroidSystemImageRefusal(requested, images) {
|
|
564
|
+
if (!requested) return null;
|
|
565
|
+
if ((images || []).some((i) => i.pkg === requested)) return null;
|
|
566
|
+
return {
|
|
567
|
+
message: `No installed Android system image is named "${requested}". Installed system images: ${installedNames((images || []).map((i) => i.pkg))}.`,
|
|
568
|
+
remedy: "Pass `--system-image` (or set android.systemImage) to one of the package ids printed above. Install more with `sdkmanager \"system-images;android-36;google_apis;arm64-v8a\"`."
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
function unknownAndroidDeviceProfileRefusal(requested, profiles) {
|
|
572
|
+
if (!requested) return null;
|
|
573
|
+
if (profiles.includes(requested)) return null;
|
|
574
|
+
return {
|
|
575
|
+
message: `No Android hardware profile is named "${requested}". Profiles avdmanager offers: ${installedNames(profiles)}.`,
|
|
576
|
+
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\"."
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
//#endregion
|
|
580
|
+
export { deviceTypeMismatch as a, runtimeMismatch as c, unknownIosDeviceTypeRefusal as d, unknownIosRuntimeRefusal as f, countLiveOwnedDevices as i, unknownAndroidDeviceProfileRefusal as l, DeviceAdmissionRefusal as n, layerNote as o, withDeviceBootAdmission as p, checkDeviceCapacity as r, peekDeviceSlots as s, DEFAULT_DEVICE_SLOT_WAIT_MS as t, unknownAndroidSystemImageRefusal as u };
|