stim 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/dist/{android-CC5bUer7.mjs → android-BW8i_YxU.mjs} +1 -1
- package/dist/android-BqVIJoG7.mjs +1368 -0
- package/dist/{android-DnZlT3aU.mjs → android-CqcW1hrP.mjs} +205 -58
- package/dist/{android-cas-DGlTStWu.mjs → android-cas-BR5YJBYv.mjs} +6 -6
- package/dist/android-cas-compiler.mjs +1 -1
- package/dist/{app-install-oZIQRbdD.mjs → app-install-DIp9riNx.mjs} +29 -21
- package/dist/{build-slots-CgacA9zQ.mjs → build-slots-C3-R4XQr.mjs} +3 -3
- package/dist/{cache-manifest-oeH18ERr.mjs → cache-manifest-CXR-Y8pH.mjs} +1 -1
- package/dist/cache-manifest.mjs +1 -1
- package/dist/cli.mjs +13 -13
- package/dist/collector-run.d.mts +4 -1
- package/dist/collector-run.mjs +36 -11
- package/dist/{config-D7uu8Gbq.mjs → config-CcefMY2Q.mjs} +94 -13
- package/dist/{deps-64wVnpZc.mjs → deps-Spz8ozgo.mjs} +2 -2
- package/dist/{dev-client-Bvky1hya.mjs → dev-client-BjisHDB5.mjs} +250 -10
- package/dist/{device-DJKv5Qej.mjs → device-BkiW-gkk.mjs} +19 -11
- package/dist/{device-lease-D-G3SgVe.mjs → device-lease-BJYybF-2.mjs} +107 -83
- package/dist/{device-pool-BlHXnYYs.mjs → device-pool-CPbLGfrB.mjs} +19 -13
- package/dist/{device-remote-CCgkG82J.mjs → device-remote-AkYCKS8U.mjs} +8 -8
- package/dist/{doctor-DlVxTtMq.mjs → doctor-Bmv86UUW.mjs} +18 -26
- package/dist/{doctor-BRF9aAJ3.mjs → doctor-fAMXC4vl.mjs} +10 -10
- package/dist/{error-diagnostics-B5IJwv3-.mjs → error-diagnostics-DHC31rXA.mjs} +65 -55
- package/dist/{exec-bsN9MJXb.mjs → exec-CyylIdq9.mjs} +4 -2
- package/dist/{gc-DhXdgSdt.mjs → gc-GGu-8ULN.mjs} +90 -67
- package/dist/{guide-DrhrDBo5.mjs → guide-92CPA21s.mjs} +414 -47
- package/dist/{ios-CK1Vrk6Q.mjs → ios-BSXFeO5H.mjs} +200 -59
- package/dist/{ios-BZxKPuqP.mjs → ios-Deh5js5V.mjs} +147 -40
- package/dist/{ios-device-CdSm0GDw.mjs → ios-device-CWOM_S44.mjs} +2 -2
- package/dist/{ios-device-CYbuRNXR.mjs → ios-device-D3pzTKQE.mjs} +1 -1
- package/dist/{logs-Dxoy16Qv.mjs → logs-DPNF4C9s.mjs} +9 -6
- package/dist/{logs-query-oqn47Ffx.mjs → logs-query-R-Cqc6Qv.mjs} +22 -5
- package/dist/{metro-B01H3EuD.mjs → metro-CORqXyTL.mjs} +3 -3
- package/dist/{ndjson-DcAtEx_K.mjs → ndjson-BZwI1NBv.mjs} +5 -2
- package/dist/{ownership-D8HmSv2E.mjs → ownership-8O073GIC.mjs} +3 -3
- package/dist/{ownership-B9QIdQOw.mjs → ownership-BpCKiRlQ.mjs} +4 -4
- package/dist/{ownership-claim-CeJLEpjD.mjs → ownership-claim-Bf2CAfw3.mjs} +4 -3
- package/dist/{project-COj6nzzh.mjs → project-fKmuIaMI.mjs} +2 -2
- package/dist/{reclaim-BPvN2HlP.mjs → reclaim-iW9Cad7k.mjs} +80 -73
- package/dist/{reload-DYZEBbZy.mjs → reload-DazHDSFA.mjs} +32 -24
- package/dist/{server-bare-BYHqEeyw.mjs → server-bare-DEzPZLvf.mjs} +7 -2
- package/dist/server-expo-44em5Pmg.mjs +2 -0
- package/dist/{server-expo-BEMr_m6p.mjs → server-expo-DizT4OMk.mjs} +5 -5
- package/dist/{settings-BSwrY4FZ.mjs → settings-DEJf6_kJ.mjs} +26 -5
- package/dist/slot-launch-cci0xvmC.mjs +19 -0
- package/dist/{start-ByCylYtB.mjs → start-mObjQH2W.mjs} +12 -12
- package/dist/{state-IYEY-zi8.mjs → state-B3XA2wp2.mjs} +9 -9
- package/dist/{stats-wqxPm7Tv.mjs → stats-BR-kwj0B.mjs} +1 -1
- package/dist/{stats-DZ4Q5J-7.mjs → stats-CaaQr4yN.mjs} +3 -3
- package/dist/{status-Dia0XTqG.mjs → status-BxQNqu9t.mjs} +86 -25
- package/dist/{stop-D6o5zAdm.mjs → stop-CrHGcjrV.mjs} +2 -2
- package/dist/{stop-BApyaF21.mjs → stop-DimGXEKY.mjs} +93 -42
- package/dist/supervisor-run.d.mts +1 -2
- package/dist/supervisor-run.mjs +5 -5
- package/dist/{remote-cache-DOi5-zLj.mjs → teardown-ClFp1Aax.mjs} +889 -830
- package/dist/{workspace-process-lock-CxOO9-pf.mjs → workspace-process-lock-3NcQnZ0X.mjs} +1 -1
- package/dist/{worktree-DxEmUI_n.mjs → worktree-DxTgN1CJ.mjs} +16 -16
- package/dist/{worktree-Dx3ZC5lS.mjs → worktree-Eq6TLH8N.mjs} +7 -3
- package/dist/{xcode-CDB6fytw.mjs → xcode-D7sAp-Zy.mjs} +256 -103
- package/package.json +5 -5
- package/shim/bundle-response.cjs +5 -0
- package/dist/android-xmyGBNy5.mjs +0 -665
- package/dist/build-lock-vE45ZzJZ.mjs +0 -645
- package/dist/server-expo-rb0k-0w7.mjs +0 -2
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
import { t as getExecutor } from "./exec-
|
|
2
|
-
import { C as withConfigLock, _ as releaseAndroidConsolePort, b as setDevice, g as loadConfig, i as clearDevice, t as allConsolePortsAndSerials, u as getConfigDir, y as saveConfig } from "./config-
|
|
3
|
-
import { r as register } from "./cache-manifest-
|
|
4
|
-
import { E as workspaceId, S as sharedCompilationCache, w as workspaceDerivedData } from "./state-
|
|
5
|
-
import { s as ownedDeviceLabel } from "./project-
|
|
1
|
+
import { t as getExecutor } from "./exec-CyylIdq9.mjs";
|
|
2
|
+
import { C as withConfigLock, E as inspectProcessIdentity, L as deviceSlotPlatforms, _ as releaseAndroidConsolePort, b as setDevice, g as loadConfig, i as clearDevice, t as allConsolePortsAndSerials, u as getConfigDir, w as captureProcessIdentity, y as saveConfig, z as projectDeviceSlots } from "./config-CcefMY2Q.mjs";
|
|
3
|
+
import { r as register } from "./cache-manifest-CXR-Y8pH.mjs";
|
|
4
|
+
import { E as workspaceId, S as sharedCompilationCache, w as workspaceDerivedData } from "./state-B3XA2wp2.mjs";
|
|
5
|
+
import { s as ownedDeviceLabel } from "./project-fKmuIaMI.mjs";
|
|
6
6
|
import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-C7IlIzd7.mjs";
|
|
7
|
-
import { r as pidExists } from "./metro-
|
|
8
|
-
import { C as selectParked, S as readParked, _ as teardownParkedIosSim, b as dropParked, g as teardownParkedAvd, m as teardownOwnedAvd, x as parkedMaxSetting, y as adoptParked } from "./remote-cache-DOi5-zLj.mjs";
|
|
9
|
-
import { H as resolveOptimizations, l as androidDataPartitionSizeGbSetting, o as androidAvdConfigSetting, v as iosSimSlimProfileSetting } from "./settings-BSwrY4FZ.mjs";
|
|
7
|
+
import { r as pidExists } from "./metro-CORqXyTL.mjs";
|
|
10
8
|
import { a as formatElapsed, l as phaseLine } from "./command-output-8wHMEde0.mjs";
|
|
11
|
-
import {
|
|
12
|
-
import { A as
|
|
13
|
-
import {
|
|
14
|
-
import { t as cleanLine } from "./server-expo-
|
|
9
|
+
import { f as releaseClaim, h as tryAcquireClaim, i as claimRemoveCommand, l as markClaimChildPending, n as ClaimRefusedError, p as setClaimChild, u as processGroupAlive } from "./ownership-claim-Bf2CAfw3.mjs";
|
|
10
|
+
import { A as pickDefaultSystemImage, D as ownedAvdName, E as ownedAvdMatchesConfiguration, L as waitForBoot, N as resolveOwnedAvdSerial, O as ownedAvdSystemImage, b as listAvds, c as avdPoolConfiguration, d as configureNewOwnedAvd, f as createOwnedAvd, o as assertOwnedAvdStopped, u as bootAndroidEmulator, v as getAvdNameForSerial, w as nextConsolePort, x as listInstalledSystemImages, y as listAdbDevices } from "./android-BqVIJoG7.mjs";
|
|
11
|
+
import { U as resolveOptimizations, l as androidDataPartitionSizeGbSetting, o as androidAvdConfigSetting, v as iosSimSlimProfileSetting } from "./settings-DEJf6_kJ.mjs";
|
|
12
|
+
import { t as cleanLine } from "./server-expo-DizT4OMk.mjs";
|
|
13
|
+
import { c as parkedMaxSetting, i as teardownParkedIosSim, l as readParked, o as adoptParked, r as teardownParkedAvd, s as dropParked, t as teardownOwnedAvd, u as selectParked } from "./teardown-ClFp1Aax.mjs";
|
|
14
|
+
import { t as withWorkspaceProcessLock } from "./workspace-process-lock-3NcQnZ0X.mjs";
|
|
15
|
+
import { E as readHostMemoryPressure, T as hostMemoryPressureAdvice, _ as renameIosSim, b as resolveIosCreation, c as iosRuntimeMatches, d as listIosDeviceTypes, g as parseRuntimeVersion, i as createOwnedIosSim, l as iosSimulatorFailureAdvice, m as ownedSimName, n as bootIosSim, u as listAllIosSims, v as resetIosKeychain, x as resolveOwnedIosSim, y as resetIosPrivacy } from "./ios-Deh5js5V.mjs";
|
|
15
16
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
16
17
|
import { createHash, randomUUID } from "node:crypto";
|
|
17
18
|
import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
18
19
|
import chalk from "chalk";
|
|
19
20
|
//#region src/engine/simslim.ts
|
|
20
|
-
async function reconcileSimSlim({ udid, profile, previouslyManaged = false, out = () => {}, spawn }) {
|
|
21
|
+
async function reconcileSimSlim({ udid, profile, previouslyManaged = false, out = () => {}, spawn, timeoutMs = 72e4, cleanupMs = 1e4 }) {
|
|
21
22
|
if (!profile && !previouslyManaged) return {
|
|
22
23
|
managed: false,
|
|
23
24
|
profile: null
|
|
@@ -39,25 +40,107 @@ async function reconcileSimSlim({ udid, profile, previouslyManaged = false, out
|
|
|
39
40
|
};
|
|
40
41
|
const stdout = createLineReader(onLine);
|
|
41
42
|
const stderr = createLineReader(onLine);
|
|
43
|
+
const root = join(getConfigDir(), "simslim-locks", `${encodeURIComponent(udid.toLowerCase())}.lock`);
|
|
44
|
+
const label = `SimSlim for ${udid}`;
|
|
45
|
+
const attempt = tryAcquireClaim({
|
|
46
|
+
root,
|
|
47
|
+
mode: "exclusive",
|
|
48
|
+
label
|
|
49
|
+
});
|
|
50
|
+
if (attempt.pending) releaseClaim(attempt.pending);
|
|
51
|
+
const claim = attempt.acquired;
|
|
52
|
+
if (!claim) throw new ClaimRefusedError({
|
|
53
|
+
root,
|
|
54
|
+
claimPath: attempt.held?.path ?? root,
|
|
55
|
+
label,
|
|
56
|
+
reason: "another SimSlim operation is still using this simulator"
|
|
57
|
+
});
|
|
42
58
|
let child;
|
|
59
|
+
let result;
|
|
60
|
+
let identity;
|
|
61
|
+
let cancellation;
|
|
62
|
+
const stopGroup = () => {
|
|
63
|
+
if (child?.pid !== void 0 && identity && inspectProcessIdentity(identity) === "same") try {
|
|
64
|
+
process.kill(-child.pid, "SIGKILL");
|
|
65
|
+
} catch {}
|
|
66
|
+
};
|
|
67
|
+
const cancel = (signal) => {
|
|
68
|
+
cancellation ??= signal;
|
|
69
|
+
stopGroup();
|
|
70
|
+
};
|
|
71
|
+
const onInterrupt = () => cancel("SIGINT");
|
|
72
|
+
const onTerminate = () => cancel("SIGTERM");
|
|
73
|
+
const groupAlive = () => child?.pid !== void 0 && processGroupAlive(child.pid);
|
|
74
|
+
const settled = () => result !== void 0 && !groupAlive();
|
|
75
|
+
const waitUntil = async (deadline, cancellable = false) => {
|
|
76
|
+
while (!settled() && Date.now() < deadline) {
|
|
77
|
+
if (cancellable && cancellation) break;
|
|
78
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(50, deadline - Date.now())));
|
|
79
|
+
}
|
|
80
|
+
return settled();
|
|
81
|
+
};
|
|
82
|
+
process.on("SIGINT", onInterrupt);
|
|
83
|
+
process.on("SIGTERM", onTerminate);
|
|
84
|
+
process.on("exit", stopGroup);
|
|
43
85
|
try {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
86
|
+
markClaimChildPending(claim);
|
|
87
|
+
try {
|
|
88
|
+
child = (spawn ?? ((cmd, childArgs, opts) => getExecutor().spawn(cmd, childArgs, opts)))("simslim", args, {
|
|
89
|
+
stdio: [
|
|
90
|
+
"ignore",
|
|
91
|
+
"pipe",
|
|
92
|
+
"pipe"
|
|
93
|
+
],
|
|
94
|
+
detached: true
|
|
95
|
+
});
|
|
96
|
+
} catch (error) {
|
|
97
|
+
throw simslimLaunchError(error);
|
|
98
|
+
}
|
|
99
|
+
child.stdout?.on("data", (chunk) => stdout.push(chunk));
|
|
100
|
+
child.stderr?.on("data", (chunk) => stderr.push(chunk));
|
|
101
|
+
waitForChild(child).then((value) => {
|
|
102
|
+
result = value;
|
|
103
|
+
});
|
|
104
|
+
if (child.pid !== void 0) {
|
|
105
|
+
const captured = captureProcessIdentity(child.pid);
|
|
106
|
+
if (captured.ok) {
|
|
107
|
+
identity = {
|
|
108
|
+
pid: child.pid,
|
|
109
|
+
processToken: captured.token
|
|
110
|
+
};
|
|
111
|
+
try {
|
|
112
|
+
setClaimChild(claim, identity);
|
|
113
|
+
} catch {}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const completed = await waitUntil(Date.now() + timeoutMs, true);
|
|
117
|
+
if (cancellation || !completed) {
|
|
118
|
+
stopGroup();
|
|
119
|
+
const stopped = await waitUntil(Date.now() + cleanupMs);
|
|
120
|
+
const detail = lines.length ? ` ${lines.join(" | ")}` : "";
|
|
121
|
+
const recovery = stopped ? "Its process group has stopped. Retry stim ios to reconcile the retained simulator settings." : `Its process group could not be confirmed stopped. The claim remains at ${claim.path}. Inspect the SimSlim process group ${child.pid ?? "unknown"} before retrying. Only when nothing is using it, remove the claim: ${claimRemoveCommand(claim.path)}`;
|
|
122
|
+
const message = `SimSlim ${action} ${cancellation ? `interrupted by ${cancellation}` : `exceeded ${Math.round(timeoutMs / 1e3)}s`}. ${recovery}${detail}`;
|
|
123
|
+
if (cancellation) out(message);
|
|
124
|
+
throw Object.assign(new Error(message), { code: cancellation ? "EINTR" : "ETIMEDOUT" });
|
|
125
|
+
}
|
|
126
|
+
} finally {
|
|
127
|
+
if (!child || settled()) releaseClaim(claim);
|
|
128
|
+
else {
|
|
129
|
+
child.stdout?.destroy?.();
|
|
130
|
+
child.stderr?.destroy?.();
|
|
131
|
+
child.unref();
|
|
132
|
+
}
|
|
133
|
+
process.off("SIGINT", onInterrupt);
|
|
134
|
+
process.off("SIGTERM", onTerminate);
|
|
135
|
+
process.off("exit", stopGroup);
|
|
136
|
+
if (cancellation) process.exit(cancellation === "SIGINT" ? 130 : 143);
|
|
51
137
|
}
|
|
52
|
-
child.stdout?.on("data", (chunk) => stdout.push(chunk));
|
|
53
|
-
child.stderr?.on("data", (chunk) => stderr.push(chunk));
|
|
54
|
-
const result = await waitForChild(child);
|
|
55
138
|
stdout.flush();
|
|
56
139
|
stderr.flush();
|
|
57
|
-
if (result
|
|
58
|
-
if (result
|
|
140
|
+
if (result?.error) throw simslimLaunchError(result.error);
|
|
141
|
+
if (result?.code !== 0) {
|
|
59
142
|
const detail = lines.length ? ` ${lines.join(" | ")}` : "";
|
|
60
|
-
throw new Error(`SimSlim ${action} failed with exit code ${result
|
|
143
|
+
throw new Error(`SimSlim ${action} failed with exit code ${result?.code ?? "unknown"}.${detail}`);
|
|
61
144
|
}
|
|
62
145
|
return {
|
|
63
146
|
managed: Boolean(profile),
|
|
@@ -78,9 +161,12 @@ function simslimIsOnPath() {
|
|
|
78
161
|
}
|
|
79
162
|
//#endregion
|
|
80
163
|
//#region src/engine/device.ts
|
|
81
|
-
function startIosBoot(udid, configure) {
|
|
164
|
+
function startIosBoot(udid, configure, label, out) {
|
|
82
165
|
const done = (async () => {
|
|
83
|
-
await bootIosSim(udid
|
|
166
|
+
await bootIosSim(udid, {
|
|
167
|
+
label,
|
|
168
|
+
out
|
|
169
|
+
});
|
|
84
170
|
await configure();
|
|
85
171
|
})();
|
|
86
172
|
done.catch(() => {});
|
|
@@ -89,11 +175,13 @@ function startIosBoot(udid, configure) {
|
|
|
89
175
|
done
|
|
90
176
|
};
|
|
91
177
|
}
|
|
92
|
-
async function ensureOwnedDevice({ platform, project, projectPath, settingsRoot = projectPath, label = ownedDeviceLabel(projectPath), settings, flags = {}, note = () => {}, out = () => {}, logFile = null, alive = pidExists, configureAvd = configureNewOwnedAvd, teardownAvd = teardownOwnedAvd, reconcileIosSimulator = reconcileSimSlim }) {
|
|
93
|
-
|
|
178
|
+
async function ensureOwnedDevice({ platform, project, projectPath, slot, settingsRoot = projectPath, label = ownedDeviceLabel(projectPath), settings, flags = {}, note = () => {}, out = () => {}, logFile = null, alive = pidExists, configureAvd = configureNewOwnedAvd, teardownAvd = teardownOwnedAvd, reconcileIosSimulator = reconcileSimSlim }) {
|
|
179
|
+
if (slot && slot !== "default") label = `${label}-${slot}`;
|
|
180
|
+
const record = deviceSlotPlatforms(project, slot)?.[platform] ?? null;
|
|
94
181
|
if (platform === "ios") return ensureOwnedIosDevice({
|
|
95
182
|
record,
|
|
96
183
|
projectPath,
|
|
184
|
+
slot,
|
|
97
185
|
settingsRoot,
|
|
98
186
|
label,
|
|
99
187
|
settings,
|
|
@@ -105,6 +193,7 @@ async function ensureOwnedDevice({ platform, project, projectPath, settingsRoot
|
|
|
105
193
|
return ensureOwnedAndroidDevice({
|
|
106
194
|
record,
|
|
107
195
|
projectPath,
|
|
196
|
+
slot,
|
|
108
197
|
settingsRoot,
|
|
109
198
|
label,
|
|
110
199
|
settings,
|
|
@@ -117,7 +206,9 @@ async function ensureOwnedDevice({ platform, project, projectPath, settingsRoot
|
|
|
117
206
|
teardownAvd
|
|
118
207
|
});
|
|
119
208
|
}
|
|
120
|
-
async function ensureOwnedIosDevice({ record, projectPath, settingsRoot, label, settings, flags, note, out, reconcileIosSimulator }) {
|
|
209
|
+
async function ensureOwnedIosDevice({ record, projectPath, slot, settingsRoot, label, settings, flags, note, out, reconcileIosSimulator }) {
|
|
210
|
+
const memoryAdvice = hostMemoryPressureAdvice(readHostMemoryPressure());
|
|
211
|
+
if (memoryAdvice) out(chalk.yellow(phaseLine("memory", memoryAdvice)));
|
|
121
212
|
const simslimProfile = iosSimSlimProfileSetting(settings, settingsRoot);
|
|
122
213
|
if (record?.deviceUdid) {
|
|
123
214
|
if (record.owned) {
|
|
@@ -148,6 +239,7 @@ async function ensureOwnedIosDevice({ record, projectPath, settingsRoot, label,
|
|
|
148
239
|
return configureOwnedIosSim({
|
|
149
240
|
record: updated,
|
|
150
241
|
projectPath,
|
|
242
|
+
slot,
|
|
151
243
|
profile: simslimProfile,
|
|
152
244
|
out,
|
|
153
245
|
reconcileIosSimulator
|
|
@@ -161,7 +253,7 @@ async function ensureOwnedIosDevice({ record, projectPath, settingsRoot, label,
|
|
|
161
253
|
out(chalk.dim(phaseLine("device", `booting ${name} (${sim.udid})`)));
|
|
162
254
|
return {
|
|
163
255
|
...updated,
|
|
164
|
-
booting: startIosBoot(sim.udid, configure),
|
|
256
|
+
booting: startIosBoot(sim.udid, configure, name, out),
|
|
165
257
|
...facts
|
|
166
258
|
};
|
|
167
259
|
}
|
|
@@ -180,6 +272,12 @@ async function ensureOwnedIosDevice({ record, projectPath, settingsRoot, label,
|
|
|
180
272
|
return record;
|
|
181
273
|
}
|
|
182
274
|
}
|
|
275
|
+
withConfigLock(() => {
|
|
276
|
+
const current = deviceSlotPlatforms(loadConfig()?.projects?.[projectPath], slot)?.ios;
|
|
277
|
+
if (!current) return;
|
|
278
|
+
if (current.deviceUdid !== record.deviceUdid || current.owned !== record.owned) throw new Error("The simulator assignment changed during recovery. Run `stim ios` again.");
|
|
279
|
+
clearDevice(projectPath, "ios", slot);
|
|
280
|
+
});
|
|
183
281
|
}
|
|
184
282
|
const choice = resolveIosCreation({
|
|
185
283
|
deviceType: flags.deviceType || settings.ios?.deviceType,
|
|
@@ -187,6 +285,7 @@ async function ensureOwnedIosDevice({ record, projectPath, settingsRoot, label,
|
|
|
187
285
|
});
|
|
188
286
|
const adopted = parkedMaxSetting("ios").max > 0 ? await withIosDeviceNameLock(() => takeParkedIosSim({
|
|
189
287
|
projectPath,
|
|
288
|
+
slot,
|
|
190
289
|
label,
|
|
191
290
|
choice,
|
|
192
291
|
out
|
|
@@ -198,11 +297,12 @@ async function ensureOwnedIosDevice({ record, projectPath, settingsRoot, label,
|
|
|
198
297
|
await configureOwnedIosSim({
|
|
199
298
|
record: adopted,
|
|
200
299
|
projectPath,
|
|
300
|
+
slot,
|
|
201
301
|
profile: simslimProfile,
|
|
202
302
|
out,
|
|
203
303
|
reconcileIosSimulator
|
|
204
304
|
});
|
|
205
|
-
});
|
|
305
|
+
}, adopted.deviceName, out);
|
|
206
306
|
return {
|
|
207
307
|
...adopted,
|
|
208
308
|
booting,
|
|
@@ -220,7 +320,7 @@ async function ensureOwnedIosDevice({ record, projectPath, settingsRoot, label,
|
|
|
220
320
|
deviceUdid: result.udid,
|
|
221
321
|
owned: true,
|
|
222
322
|
deviceName: result.name
|
|
223
|
-
});
|
|
323
|
+
}, slot);
|
|
224
324
|
return result;
|
|
225
325
|
});
|
|
226
326
|
const newRecord = {
|
|
@@ -231,10 +331,11 @@ async function ensureOwnedIosDevice({ record, projectPath, settingsRoot, label,
|
|
|
231
331
|
const booting = startIosBoot(created.udid, () => configureOwnedIosSim({
|
|
232
332
|
record: newRecord,
|
|
233
333
|
projectPath,
|
|
334
|
+
slot,
|
|
234
335
|
profile: simslimProfile,
|
|
235
336
|
out,
|
|
236
337
|
reconcileIosSimulator
|
|
237
|
-
}));
|
|
338
|
+
}), created.name, out);
|
|
238
339
|
return {
|
|
239
340
|
...newRecord,
|
|
240
341
|
created: true,
|
|
@@ -248,8 +349,8 @@ function withIosDeviceNameLock(fn) {
|
|
|
248
349
|
}
|
|
249
350
|
function ownedIosNameSuffix(label, model, projectPath, udid) {
|
|
250
351
|
const names = new Set(listAllIosSims({ includeUnavailable: true }).filter((sim) => sim.udid !== udid).map((sim) => sim.name));
|
|
251
|
-
for (const project of Object.values(loadConfig()?.projects ?? {})) {
|
|
252
|
-
const record =
|
|
352
|
+
for (const project of Object.values(loadConfig()?.projects ?? {})) for (const { platforms } of projectDeviceSlots(project)) {
|
|
353
|
+
const record = platforms.ios;
|
|
253
354
|
if (record?.deviceUdid !== udid && record?.deviceName) names.add(record.deviceName);
|
|
254
355
|
}
|
|
255
356
|
for (const parked of readParked("ios")) if (parked.udid !== udid) names.add(parked.name);
|
|
@@ -272,7 +373,7 @@ function resetAdoptedSim(udid, out) {
|
|
|
272
373
|
resetIosPrivacy(udid);
|
|
273
374
|
resetIosKeychain(udid);
|
|
274
375
|
}
|
|
275
|
-
function takeParkedIosSim({ projectPath, label, choice, out }) {
|
|
376
|
+
function takeParkedIosSim({ projectPath, slot, label, choice, out }) {
|
|
276
377
|
const candidates = selectParked(readParked("ios"), {
|
|
277
378
|
deviceTypeIdentifier: choice.deviceTypeId,
|
|
278
379
|
runtimeIdentifier: choice.runtimeId
|
|
@@ -308,6 +409,7 @@ function takeParkedIosSim({ projectPath, label, choice, out }) {
|
|
|
308
409
|
if (!adoptParked({
|
|
309
410
|
platform: "ios",
|
|
310
411
|
projectPath,
|
|
412
|
+
slot,
|
|
311
413
|
udid: parked.udid,
|
|
312
414
|
device
|
|
313
415
|
})) continue;
|
|
@@ -318,10 +420,10 @@ function takeParkedIosSim({ projectPath, label, choice, out }) {
|
|
|
318
420
|
}
|
|
319
421
|
return null;
|
|
320
422
|
}
|
|
321
|
-
function clearIosAdoptionPending(projectPath) {
|
|
423
|
+
function clearIosAdoptionPending(projectPath, slot = "default") {
|
|
322
424
|
withConfigLock(() => {
|
|
323
425
|
const cfg = loadConfig();
|
|
324
|
-
const ios = cfg?.projects?.[projectPath]?.
|
|
426
|
+
const ios = deviceSlotPlatforms(cfg?.projects?.[projectPath], slot)?.ios;
|
|
325
427
|
if (!cfg || !ios?.adoptionPending) return;
|
|
326
428
|
delete ios.adopted;
|
|
327
429
|
delete ios.adoptionPending;
|
|
@@ -329,7 +431,7 @@ function clearIosAdoptionPending(projectPath) {
|
|
|
329
431
|
saveConfig(cfg);
|
|
330
432
|
});
|
|
331
433
|
}
|
|
332
|
-
async function configureOwnedIosSim({ record, projectPath, profile, out, reconcileIosSimulator }) {
|
|
434
|
+
async function configureOwnedIosSim({ record, projectPath, slot, profile, out, reconcileIosSimulator }) {
|
|
333
435
|
if (profile) out(chalk.dim(phaseLine("device", `applying the SimSlim profile to ${record.deviceUdid}`)));
|
|
334
436
|
else if (record.simslimManaged) out(chalk.dim(phaseLine("device", `restoring stock simulator services on ${record.deviceUdid}`)));
|
|
335
437
|
const previouslyManaged = Boolean(record.simslimManaged);
|
|
@@ -338,7 +440,7 @@ async function configureOwnedIosSim({ record, projectPath, profile, out, reconci
|
|
|
338
440
|
...record,
|
|
339
441
|
simslimManaged: true
|
|
340
442
|
};
|
|
341
|
-
setDevice(projectPath, "ios", pending);
|
|
443
|
+
setDevice(projectPath, "ios", pending, slot);
|
|
342
444
|
record = pending;
|
|
343
445
|
}
|
|
344
446
|
const result = await reconcileIosSimulator({
|
|
@@ -350,24 +452,31 @@ async function configureOwnedIosSim({ record, projectPath, profile, out, reconci
|
|
|
350
452
|
const updated = { ...record };
|
|
351
453
|
if (result.managed) updated.simslimManaged = true;
|
|
352
454
|
else delete updated.simslimManaged;
|
|
353
|
-
setDevice(projectPath, "ios", updated);
|
|
455
|
+
setDevice(projectPath, "ios", updated, slot);
|
|
354
456
|
return updated;
|
|
355
457
|
}
|
|
356
|
-
function findOtherProjectOwningAvd(avdName, projectPath) {
|
|
458
|
+
function findOtherProjectOwningAvd(avdName, projectPath, selectedSlot = "default") {
|
|
357
459
|
const cfg = loadConfig();
|
|
358
|
-
for (const [path, proj] of Object.entries(cfg?.projects || {})) {
|
|
359
|
-
if (path === projectPath) continue;
|
|
360
|
-
if (proj?.platforms?.android?.avdName === avdName) return path;
|
|
361
|
-
}
|
|
460
|
+
for (const [path, proj] of Object.entries(cfg?.projects || {})) if (projectDeviceSlots(proj).some(({ slot, platforms }) => (path !== projectPath || slot !== selectedSlot) && platforms.android?.avdName?.toLowerCase() === avdName.toLowerCase())) return path;
|
|
362
461
|
return null;
|
|
363
462
|
}
|
|
364
|
-
|
|
463
|
+
var AvdRecoveryError = class extends Error {
|
|
464
|
+
remedy;
|
|
465
|
+
constructor(message, remedy, cause) {
|
|
466
|
+
super(message, { cause });
|
|
467
|
+
this.remedy = remedy;
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoot, label, settings, flags, note, out, logFile, alive, configureAvd, teardownAvd }) {
|
|
365
471
|
const avdConfig = androidAvdConfigSetting(settings, settingsRoot);
|
|
366
472
|
const configuration = avdPoolConfiguration(androidDataPartitionSizeGbSetting(settings), avdConfig);
|
|
367
473
|
if (record?.setupIncomplete && record.avdName) {
|
|
368
|
-
const cleanup = teardownAvd(record.avdName, {
|
|
474
|
+
const cleanup = teardownAvd(record.avdName, {
|
|
475
|
+
del: true,
|
|
476
|
+
owner: { projectPath }
|
|
477
|
+
});
|
|
369
478
|
if (cleanup.status === "failed" || cleanup.status === "skipped") throw new Error(`Owned AVD ${record.avdName} has incomplete setup and could not be deleted (${cleanup.reason || cleanup.status}). Fix the cause, then retry; Stim kept the device record for cleanup.`);
|
|
370
|
-
clearDevice(projectPath, "android");
|
|
479
|
+
clearDevice(projectPath, "android", slot);
|
|
371
480
|
record = null;
|
|
372
481
|
}
|
|
373
482
|
if (record?.avdName) {
|
|
@@ -376,6 +485,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
|
|
|
376
485
|
if (resolved.notOwned) note(chalk.yellow(`Note: recorded AVD ${record.avdName} is not Stim-owned by name -- creating a fresh owned AVD instead of reusing it.`));
|
|
377
486
|
else if (resolved.serial) {
|
|
378
487
|
const consolePort = Number(resolved.serial.replace(/^emulator-/, ""));
|
|
488
|
+
if (record.consolePort && record.consolePort !== consolePort) out(phaseLine("device", `${record.avdName} changed serial (emulator-${record.consolePort} -> ${resolved.serial}); reconnecting this run, reopen agent-device on ${resolved.serial}`));
|
|
379
489
|
const updated = {
|
|
380
490
|
...record,
|
|
381
491
|
avdName: record.avdName,
|
|
@@ -383,7 +493,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
|
|
|
383
493
|
owned: true,
|
|
384
494
|
deviceName: record.deviceName ?? record.avdName
|
|
385
495
|
};
|
|
386
|
-
setDevice(projectPath, "android", updated);
|
|
496
|
+
setDevice(projectPath, "android", updated, slot);
|
|
387
497
|
return {
|
|
388
498
|
...updated,
|
|
389
499
|
systemImage: ownedAvdSystemImage(record.avdName)
|
|
@@ -395,6 +505,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
|
|
|
395
505
|
avdName: record.avdName,
|
|
396
506
|
metadata: record,
|
|
397
507
|
projectPath,
|
|
508
|
+
slot,
|
|
398
509
|
deviceName: record.deviceName,
|
|
399
510
|
out,
|
|
400
511
|
logFile,
|
|
@@ -421,7 +532,8 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
|
|
|
421
532
|
if (parked.deletionClaim !== void 0) continue;
|
|
422
533
|
const resolved = resolveOwnedAvdSerial(parked.name);
|
|
423
534
|
if (resolved.missing) {
|
|
424
|
-
|
|
535
|
+
const result = teardownParkedAvd(parked.name);
|
|
536
|
+
if (result.status === "failed") out(phaseLine("device", `kept ${parked.name}: ${result.reason}`));
|
|
425
537
|
continue;
|
|
426
538
|
}
|
|
427
539
|
if (resolved.notOwned || resolved.serial) continue;
|
|
@@ -446,6 +558,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
|
|
|
446
558
|
if (!adoptParked({
|
|
447
559
|
platform: "android",
|
|
448
560
|
projectPath,
|
|
561
|
+
slot,
|
|
449
562
|
udid: parked.udid,
|
|
450
563
|
device: adopted
|
|
451
564
|
})) continue;
|
|
@@ -453,6 +566,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
|
|
|
453
566
|
...await bootOwnedAvdOnFreshPort({
|
|
454
567
|
avdName: parked.name,
|
|
455
568
|
projectPath,
|
|
569
|
+
slot,
|
|
456
570
|
metadata: adopted,
|
|
457
571
|
out,
|
|
458
572
|
logFile,
|
|
@@ -467,9 +581,9 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
|
|
|
467
581
|
let fresh = false;
|
|
468
582
|
try {
|
|
469
583
|
created = withConfigLock(() => {
|
|
470
|
-
const current = loadConfig()?.projects?.[projectPath]?.
|
|
584
|
+
const current = deviceSlotPlatforms(loadConfig()?.projects?.[projectPath], slot)?.android;
|
|
471
585
|
if (current?.avdName && current.avdName !== record?.avdName) throw new Error(`Another Stim run assigned AVD ${current.avdName} to this workspace. Retry to use it.`);
|
|
472
|
-
if (readParked("android").some((entry) => entry.name === ownedAvdName(label)) || findOtherProjectOwningAvd(ownedAvdName(label), projectPath)) label = `${label}-${randomUUID().slice(0, 8)}`;
|
|
586
|
+
if (readParked("android").some((entry) => entry.name === ownedAvdName(label)) || findOtherProjectOwningAvd(ownedAvdName(label), projectPath, slot)) label = `${label}-${randomUUID().slice(0, 8)}`;
|
|
473
587
|
const result = createOwnedAvd(label, { systemImage: flags.systemImage || settings.android?.systemImage });
|
|
474
588
|
setDevice(projectPath, "android", {
|
|
475
589
|
avdName: result.avdName,
|
|
@@ -477,35 +591,52 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
|
|
|
477
591
|
deviceName: result.avdName,
|
|
478
592
|
setupIncomplete: true,
|
|
479
593
|
poolConfiguration: configuration
|
|
480
|
-
});
|
|
594
|
+
}, slot);
|
|
481
595
|
return result;
|
|
482
596
|
});
|
|
483
597
|
fresh = true;
|
|
484
598
|
} catch (e) {
|
|
485
599
|
const message = String(e?.message || e);
|
|
486
600
|
const avdName = ownedAvdName(label);
|
|
487
|
-
if (message.includes("already exists")
|
|
488
|
-
|
|
489
|
-
if (
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
601
|
+
if (message.includes("already exists")) {
|
|
602
|
+
try {
|
|
603
|
+
if (!listAvds().includes(avdName)) throw new AvdRecoveryError(`AVD ${avdName} already exists on disk but is not listed by the emulator. ${message}`, "Run `npx stim gc` to inspect orphaned owned AVDs, then `npx stim gc --delete` to reclaim those safe to delete. Retry `stim android` after cleanup; keep any AVD that GC cannot verify.", e);
|
|
604
|
+
created = withConfigLock(() => {
|
|
605
|
+
if (readParked("android").some((entry) => entry.name === avdName)) throw new Error(`AVD ${avdName} was parked by another Stim run. Retry to adopt it safely.`, { cause: e });
|
|
606
|
+
const owner = findOtherProjectOwningAvd(avdName, projectPath, slot);
|
|
607
|
+
if (owner) throw new Error(`AVD ${avdName} already exists and is owned by another project (${owner}). Retry to allocate a distinct owned emulator.`, { cause: e });
|
|
608
|
+
const current = deviceSlotPlatforms(loadConfig()?.projects?.[projectPath], slot)?.android;
|
|
609
|
+
if (current?.avdName) {
|
|
610
|
+
const state = current.setupIncomplete ? "has incomplete setup" : "was registered";
|
|
611
|
+
throw new Error(`AVD ${current.avdName} ${state} by another concurrent Stim run. Retry after that run finishes so the recorded device is resolved safely.`, { cause: e });
|
|
612
|
+
}
|
|
613
|
+
const resolved = resolveOwnedAvdSerial(avdName);
|
|
614
|
+
if (resolved.missing || resolved.notOwned) throw new Error(`AVD ${avdName} could not be verified for recovery. Retry after checking its registration.`, { cause: e });
|
|
615
|
+
if (!resolved.serial) assertOwnedAvdStopped(avdName);
|
|
616
|
+
const recovered = {
|
|
617
|
+
avdName,
|
|
618
|
+
owned: true,
|
|
619
|
+
deviceName: avdName,
|
|
620
|
+
...resolved.serial ? { consolePort: Number(resolved.serial.replace(/^emulator-/, "")) } : {}
|
|
621
|
+
};
|
|
622
|
+
setDevice(projectPath, "android", recovered, slot);
|
|
623
|
+
return {
|
|
624
|
+
...recovered,
|
|
625
|
+
systemImage: ownedAvdSystemImage(avdName),
|
|
626
|
+
serial: resolved.serial
|
|
627
|
+
};
|
|
505
628
|
});
|
|
506
|
-
|
|
507
|
-
|
|
629
|
+
} catch (error) {
|
|
630
|
+
if (error instanceof AvdRecoveryError) throw error;
|
|
631
|
+
throw new AvdRecoveryError(`Could not recover owned AVD ${avdName}: ${String(error?.message || error)}`, "Inspect `npx stim status` and `adb devices`. Wait for any other Stim run using this AVD to finish, then retry `stim android`. Keep the AVD and its process locks while its state is unverified.", error);
|
|
632
|
+
}
|
|
508
633
|
out(chalk.dim(phaseLine("device", `recovered ${avdName} (unrecorded from a prior run)`)));
|
|
634
|
+
if (created.serial) return {
|
|
635
|
+
...created,
|
|
636
|
+
owned: true,
|
|
637
|
+
deviceName: avdName,
|
|
638
|
+
created: false
|
|
639
|
+
};
|
|
509
640
|
} else throw e;
|
|
510
641
|
}
|
|
511
642
|
if (fresh) try {
|
|
@@ -514,9 +645,12 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
|
|
|
514
645
|
avdConfig
|
|
515
646
|
});
|
|
516
647
|
} catch (error) {
|
|
517
|
-
const cleanup = teardownAvd(created.avdName, {
|
|
648
|
+
const cleanup = teardownAvd(created.avdName, {
|
|
649
|
+
del: true,
|
|
650
|
+
owner: { projectPath }
|
|
651
|
+
});
|
|
518
652
|
const kept = cleanup.status === "failed" || cleanup.status === "skipped";
|
|
519
|
-
if (!kept) clearDevice(projectPath, "android");
|
|
653
|
+
if (!kept) clearDevice(projectPath, "android", slot);
|
|
520
654
|
const orphan = kept ? ` The owned AVD remains tracked for cleanup (${cleanup.reason || cleanup.status}); fix the cause, then retry or run \`stim gc --delete\`.` : "";
|
|
521
655
|
throw new Error(`Created owned AVD ${created.avdName}, but could not configure its AVD settings: ${String(error?.message || error)}${orphan}`, { cause: error });
|
|
522
656
|
}
|
|
@@ -525,16 +659,17 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
|
|
|
525
659
|
avdName: created.avdName,
|
|
526
660
|
metadata: fresh ? { poolConfiguration: configuration } : void 0,
|
|
527
661
|
projectPath,
|
|
662
|
+
slot,
|
|
528
663
|
deviceName: created.avdName,
|
|
529
664
|
out,
|
|
530
665
|
logFile,
|
|
531
666
|
alive
|
|
532
667
|
}),
|
|
533
|
-
created:
|
|
668
|
+
created: fresh,
|
|
534
669
|
systemImage: created.systemImage
|
|
535
670
|
};
|
|
536
671
|
}
|
|
537
|
-
function claimAndroidConsolePort({ projectPath, avdName, deviceName, livePorts = [], metadata }, { lock = withConfigLock, recordedPorts = () => allConsolePortsAndSerials().androidConsolePorts, record = setDevice } = {}) {
|
|
672
|
+
function claimAndroidConsolePort({ projectPath, slot, avdName, deviceName, livePorts = [], metadata }, { lock = withConfigLock, recordedPorts = () => allConsolePortsAndSerials().androidConsolePorts, record = setDevice } = {}) {
|
|
538
673
|
return lock(() => {
|
|
539
674
|
const consolePort = nextConsolePort([...recordedPorts(), ...livePorts]);
|
|
540
675
|
const claim = {
|
|
@@ -545,7 +680,7 @@ function claimAndroidConsolePort({ projectPath, avdName, deviceName, livePorts =
|
|
|
545
680
|
owned: true,
|
|
546
681
|
deviceName: deviceName ?? avdName
|
|
547
682
|
};
|
|
548
|
-
record(projectPath, "android", claim);
|
|
683
|
+
record(projectPath, "android", claim, slot);
|
|
549
684
|
return claim;
|
|
550
685
|
});
|
|
551
686
|
}
|
|
@@ -553,9 +688,10 @@ function liveAndroidConsolePorts() {
|
|
|
553
688
|
const adbLive = listAdbDevices();
|
|
554
689
|
return [...adbLive.emulators.map((e) => e.consolePort), ...adbLive.unhealthy.map((u) => u.consolePort).filter((p) => p != null)];
|
|
555
690
|
}
|
|
556
|
-
async function bootOwnedAvdOnFreshPort({ avdName, metadata, projectPath, deviceName, out, logFile = null, alive = pidExists }) {
|
|
691
|
+
async function bootOwnedAvdOnFreshPort({ avdName, metadata, projectPath, slot, deviceName, out, logFile = null, alive = pidExists }) {
|
|
557
692
|
const claim = claimAndroidConsolePort({
|
|
558
693
|
projectPath,
|
|
694
|
+
slot,
|
|
559
695
|
avdName,
|
|
560
696
|
deviceName,
|
|
561
697
|
livePorts: liveAndroidConsolePorts(),
|
|
@@ -574,7 +710,7 @@ async function bootOwnedAvdOnFreshPort({ avdName, metadata, projectPath, deviceN
|
|
|
574
710
|
serial
|
|
575
711
|
};
|
|
576
712
|
} catch (error) {
|
|
577
|
-
releaseAndroidConsolePort(projectPath, claim.consolePort);
|
|
713
|
+
releaseAndroidConsolePort(projectPath, claim.consolePort, slot);
|
|
578
714
|
throw error;
|
|
579
715
|
}
|
|
580
716
|
}
|
|
@@ -589,25 +725,26 @@ function liveOwnedDeviceCount({ sims = [], adbEmulators = [], config = null } =
|
|
|
589
725
|
let count = 0;
|
|
590
726
|
for (const sim of sims) if (sim?.state === "Booted" && sim.name?.startsWith("stim-")) count++;
|
|
591
727
|
const livePorts = new Set(adbEmulators.map((e) => e.consolePort));
|
|
592
|
-
for (const proj of Object.values(config?.projects || {})) {
|
|
593
|
-
const android =
|
|
728
|
+
for (const proj of Object.values(config?.projects || {})) for (const { platforms } of projectDeviceSlots(proj)) {
|
|
729
|
+
const android = platforms.android;
|
|
594
730
|
if (android?.owned && android.avdName && typeof android.consolePort === "number" && livePorts.has(android.consolePort)) count++;
|
|
595
731
|
}
|
|
596
732
|
return count;
|
|
597
733
|
}
|
|
598
|
-
function workspaceHasLiveDevice({ platform, project, sims = [], adbEmulators = [] } = {}) {
|
|
734
|
+
function workspaceHasLiveDevice({ platform, project, slot = "default", sims = [], adbEmulators = [] } = {}) {
|
|
599
735
|
if (!platform) return false;
|
|
600
|
-
const record = project?.
|
|
736
|
+
const record = deviceSlotPlatforms(project, slot)?.[platform];
|
|
601
737
|
if (!record) return false;
|
|
602
738
|
if (platform === "ios") return sims.some((s) => s.udid === record.deviceUdid && s.state === "Booted");
|
|
603
739
|
return typeof record.consolePort === "number" && adbEmulators.some((e) => e.consolePort === record.consolePort);
|
|
604
740
|
}
|
|
605
|
-
function deviceCapacityRefusal({ platform, project, max, sims = [], adb = null, config = null } = {}) {
|
|
741
|
+
function deviceCapacityRefusal({ platform, project, slot = "default", max, sims = [], adb = null, config = null } = {}) {
|
|
606
742
|
if (!max || max <= 0) return null;
|
|
607
743
|
const adbEmulators = adb?.emulators || [];
|
|
608
744
|
if (workspaceHasLiveDevice({
|
|
609
745
|
platform,
|
|
610
746
|
project,
|
|
747
|
+
slot,
|
|
611
748
|
sims,
|
|
612
749
|
adbEmulators
|
|
613
750
|
})) return null;
|
|
@@ -623,7 +760,7 @@ function deviceCapacityRefusal({ platform, project, max, sims = [], adb = null,
|
|
|
623
760
|
remedy: "stop an environment (stim stop) or raise concurrency.maxDevices"
|
|
624
761
|
};
|
|
625
762
|
}
|
|
626
|
-
function checkDeviceCapacity({ platform, project, max, sims = listAllIosSims, adb = listAdbDevices, config = loadConfig } = {}) {
|
|
763
|
+
function checkDeviceCapacity({ platform, project, slot = "default", max, sims = listAllIosSims, adb = listAdbDevices, config = loadConfig } = {}) {
|
|
627
764
|
if (!max || max <= 0) return null;
|
|
628
765
|
let simList = [];
|
|
629
766
|
let adbRes = {
|
|
@@ -644,6 +781,7 @@ function checkDeviceCapacity({ platform, project, max, sims = listAllIosSims, ad
|
|
|
644
781
|
return deviceCapacityRefusal({
|
|
645
782
|
platform,
|
|
646
783
|
project,
|
|
784
|
+
slot,
|
|
647
785
|
max,
|
|
648
786
|
sims: simList,
|
|
649
787
|
adb: adbRes,
|
|
@@ -716,6 +854,26 @@ async function ensureIosBooted({ device, timeoutMs, pollMs, out }) {
|
|
|
716
854
|
failed: true,
|
|
717
855
|
reason: "No iOS simulator is recorded for this project."
|
|
718
856
|
};
|
|
857
|
+
const ready = () => {
|
|
858
|
+
try {
|
|
859
|
+
getExecutor().runFile("xcrun", [
|
|
860
|
+
"simctl",
|
|
861
|
+
"spawn",
|
|
862
|
+
udid,
|
|
863
|
+
"launchctl",
|
|
864
|
+
"list"
|
|
865
|
+
], { timeoutMs: 3e4 });
|
|
866
|
+
return {
|
|
867
|
+
ok: true,
|
|
868
|
+
udid
|
|
869
|
+
};
|
|
870
|
+
} catch (error) {
|
|
871
|
+
return {
|
|
872
|
+
failed: true,
|
|
873
|
+
reason: `Simulator ${udid} failed its process-spawn readiness check: ${error?.message || error}. ${iosSimulatorFailureAdvice()}`
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
};
|
|
719
877
|
const booting = device?.booting;
|
|
720
878
|
if (booting?.udid === udid) {
|
|
721
879
|
try {
|
|
@@ -726,10 +884,7 @@ async function ensureIosBooted({ device, timeoutMs, pollMs, out }) {
|
|
|
726
884
|
reason: `Could not boot simulator ${udid}: ${e?.message || e}`
|
|
727
885
|
};
|
|
728
886
|
}
|
|
729
|
-
return
|
|
730
|
-
ok: true,
|
|
731
|
-
udid
|
|
732
|
-
};
|
|
887
|
+
return ready();
|
|
733
888
|
}
|
|
734
889
|
let resolved;
|
|
735
890
|
try {
|
|
@@ -749,14 +904,15 @@ async function ensureIosBooted({ device, timeoutMs, pollMs, out }) {
|
|
|
749
904
|
reason: `Simulator ${udid} is now named "${resolved.notOwned}" and is not Stim-owned; refusing to boot it.`
|
|
750
905
|
};
|
|
751
906
|
const sim = resolved.sim;
|
|
752
|
-
if (sim.state === "Booted") return
|
|
753
|
-
ok: true,
|
|
754
|
-
udid
|
|
755
|
-
};
|
|
907
|
+
if (sim.state === "Booted") return ready();
|
|
756
908
|
out(chalk.dim(phaseLine("device", `booting ${sim.name} (${udid})`)));
|
|
757
909
|
const bootDeadline = Date.now() + timeoutMs;
|
|
758
910
|
try {
|
|
759
|
-
await bootIosSim(udid, {
|
|
911
|
+
await bootIosSim(udid, {
|
|
912
|
+
timeoutMs,
|
|
913
|
+
label: sim.name,
|
|
914
|
+
out
|
|
915
|
+
});
|
|
760
916
|
} catch (e) {
|
|
761
917
|
return {
|
|
762
918
|
failed: true,
|
|
@@ -770,10 +926,7 @@ async function ensureIosBooted({ device, timeoutMs, pollMs, out }) {
|
|
|
770
926
|
try {
|
|
771
927
|
state = listAllIosSims({ timeoutMs: 3e4 }).find((s) => s.udid === udid)?.state ?? null;
|
|
772
928
|
} catch {}
|
|
773
|
-
if (state === "Booted") return
|
|
774
|
-
ok: true,
|
|
775
|
-
udid
|
|
776
|
-
};
|
|
929
|
+
if (state === "Booted") return ready();
|
|
777
930
|
}
|
|
778
931
|
return {
|
|
779
932
|
failed: true,
|
|
@@ -1702,4 +1855,4 @@ async function buildIos({ root, udid = null, logWriter, project = null, scheme =
|
|
|
1702
1855
|
};
|
|
1703
1856
|
}
|
|
1704
1857
|
//#endregion
|
|
1705
|
-
export {
|
|
1858
|
+
export { unknownAndroidSystemImageRefusal as C, simslimIsOnPath as E, liveOwnedDeviceCount as S, unknownIosRuntimeRefusal as T, AvdRecoveryError as _, ccacheEnabled as a, ensureBooted as b, discoverXcodeProject as c, resolveScheme as d, startBuildHeartbeat as f, podInstallCommand as g, bundlerPin as h, buildIos as i, readBundleExecutable as l, describeDiagnostic as m, COMPILATION_CACHE_UNAVAILABLE as n, compilationCacheActivityLine as o, tailLines as p, HEARTBEAT_INTERVAL_MS as r, detectXcodeMajor as s, COMPILATION_CACHE_NOT_RUN as t, readBundleId as u, checkDeviceCapacity as v, unknownIosDeviceTypeRefusal as w, ensureOwnedDevice as x, clearIosAdoptionPending as y };
|