stim 1.2.0 → 1.3.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/README.md +34 -3
- package/dist/{android-Bcmev2s2.mjs → android-CT-PkXUh.mjs} +157 -62
- package/dist/{android-cas-D6HPZ6UQ.mjs → android-cas-DXorPCi_.mjs} +5 -5
- package/dist/android-hMj83VpN.mjs +1374 -0
- package/dist/{app-install-NFxUahnH.mjs → app-install-DIp9riNx.mjs} +6 -6
- package/dist/{build-slots-D0b6sJyo.mjs → build-slots-C4AouyNz.mjs} +2 -2
- package/dist/{cache-manifest-Cu4_Znw9.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 +3 -1
- package/dist/collector-run.mjs +33 -8
- package/dist/{config-CMtIaTk9.mjs → config-CcefMY2Q.mjs} +93 -12
- package/dist/{deps-BLBsTNDS.mjs → deps-0Yt0Inly.mjs} +1 -1
- package/dist/{dev-client-RpvXxCXV.mjs → dev-client-VfSZNLT4.mjs} +186 -9
- package/dist/{device-CoCPmazS.mjs → device-Bbv45Z9Z.mjs} +19 -11
- package/dist/{device-lease-Dpik41RY.mjs → device-lease-CJwK7OZr.mjs} +107 -83
- package/dist/{device-pool-B5pEVa3t.mjs → device-pool-CcBNTvM7.mjs} +18 -12
- package/dist/{device-remote-E81KAdXf.mjs → device-remote-DAA0Zm6Z.mjs} +7 -7
- package/dist/{doctor-CqPeMsSm.mjs → doctor-B4EITgQB.mjs} +11 -12
- package/dist/{doctor-D_CX7ib5.mjs → doctor-BueDfLYG.mjs} +4 -4
- package/dist/{error-diagnostics-Dw8Gn3yw.mjs → error-diagnostics-DwhNBFDU.mjs} +62 -52
- package/dist/{gc-B7KkDinF.mjs → gc-BRu62v4o.mjs} +66 -57
- package/dist/{guide-BS2Xc24o.mjs → guide-D95E0u3i.mjs} +307 -74
- package/dist/{ios-B7KLgIp5.mjs → ios-DAju3TVy.mjs} +183 -54
- package/dist/{ios-D1h1Di39.mjs → ios-Deh5js5V.mjs} +78 -40
- package/dist/{ios-device-xNt0Lg6v.mjs → ios-device-CWOM_S44.mjs} +1 -1
- package/dist/{logs-DcX9AvRF.mjs → logs-CAdwW6c2.mjs} +34 -6
- package/dist/{logs-query-oqn47Ffx.mjs → logs-query-Cdp5aaCH.mjs} +23 -6
- package/dist/{metro-COslRK_F.mjs → metro-CORqXyTL.mjs} +2 -2
- package/dist/{ndjson-DcAtEx_K.mjs → ndjson-BZwI1NBv.mjs} +5 -2
- package/dist/{ownership-CGPart6T.mjs → ownership-8O073GIC.mjs} +3 -3
- package/dist/{ownership-CYOyqxUo.mjs → ownership-DXcol4oU.mjs} +3 -3
- package/dist/{ownership-claim-7zRxxTfZ.mjs → ownership-claim-BtPtJVmq.mjs} +38 -23
- package/dist/{project-BBTVABUz.mjs → project-fKmuIaMI.mjs} +2 -2
- package/dist/{reclaim-C5Jmuahx.mjs → reclaim-CUmwucWG.mjs} +80 -74
- package/dist/{reload-Z8nKifAk.mjs → reload-CWpXoOgN.mjs} +32 -24
- package/dist/{server-bare-DEk37Mz2.mjs → server-bare-DEzPZLvf.mjs} +2 -2
- package/dist/server-expo-44em5Pmg.mjs +2 -0
- package/dist/{server-expo-C3ktAwUG.mjs → server-expo-DizT4OMk.mjs} +4 -4
- package/dist/{settings-DwU1_U_q.mjs → settings-DEJf6_kJ.mjs} +2 -2
- package/dist/slot-launch-CZklomj6.mjs +19 -0
- package/dist/{start-BrY4EuIg.mjs → start-B5G4NjiA.mjs} +11 -11
- package/dist/{state-CZ-ER-8d.mjs → state-B3XA2wp2.mjs} +9 -9
- package/dist/{stats-BCWEVLrM.mjs → stats-BR-kwj0B.mjs} +1 -1
- package/dist/{stats-BsF15Bti.mjs → stats-CaaQr4yN.mjs} +3 -3
- package/dist/{status-DFI1Hfpg.mjs → status-CI9clRSb.mjs} +55 -24
- package/dist/{stop-B7DR4awK.mjs → stop-CQhffpmX.mjs} +2 -2
- package/dist/{stop-Btg770vV.mjs → stop-CjlgDk0a.mjs} +93 -42
- package/dist/supervisor-run.d.mts +1 -2
- package/dist/supervisor-run.mjs +5 -5
- package/dist/{remote-cache-DDmJ1Roq.mjs → teardown--gESs9ck.mjs} +985 -887
- package/dist/{workspace-process-lock-BKfdys1q.mjs → workspace-process-lock-IBj1Towr.mjs} +1 -1
- package/dist/{worktree-BPTbI-LN.mjs → worktree-DtQGO9-s.mjs} +121 -111
- package/dist/{worktree-C79xx_kM.mjs → worktree-Eq6TLH8N.mjs} +1 -1
- package/dist/{xcode-DeKCribl.mjs → xcode-xnxyvJkD.mjs} +159 -96
- package/package.json +5 -5
- package/dist/android-K63_0UfF.mjs +0 -727
- package/dist/build-lock-BsVoLDaq.mjs +0 -645
- package/dist/server-expo-Drd7ksHu.mjs +0 -2
|
@@ -1,727 +0,0 @@
|
|
|
1
|
-
import { t as getExecutor } from "./exec-CyylIdq9.mjs";
|
|
2
|
-
import { r as pidExists } from "./metro-COslRK_F.mjs";
|
|
3
|
-
import { c as androidDataPartitionSizeBytes } from "./settings-DwU1_U_q.mjs";
|
|
4
|
-
import { existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "fs";
|
|
5
|
-
import { homedir } from "os";
|
|
6
|
-
import { dirname, isAbsolute, join, resolve } from "path";
|
|
7
|
-
//#region src/sim/android.ts
|
|
8
|
-
const ANDROID_ABIS = /* @__PURE__ */ new Set([
|
|
9
|
-
"armeabi-v7a",
|
|
10
|
-
"arm64-v8a",
|
|
11
|
-
"x86",
|
|
12
|
-
"x86_64"
|
|
13
|
-
]);
|
|
14
|
-
const DEFAULT_AVD_DEVICE = "pixel_6";
|
|
15
|
-
function androidHome() {
|
|
16
|
-
return process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT || join(homedir(), "Library", "Android", "sdk");
|
|
17
|
-
}
|
|
18
|
-
const SDK_TOOL_LOCATIONS = {
|
|
19
|
-
emulator: ["emulator", "emulator"],
|
|
20
|
-
adb: ["platform-tools", "adb"],
|
|
21
|
-
avdmanager: [
|
|
22
|
-
"cmdline-tools",
|
|
23
|
-
"latest",
|
|
24
|
-
"bin",
|
|
25
|
-
"avdmanager"
|
|
26
|
-
]
|
|
27
|
-
};
|
|
28
|
-
function androidToolPath(tool) {
|
|
29
|
-
const abs = join(androidHome(), ...SDK_TOOL_LOCATIONS[tool]);
|
|
30
|
-
return existsSync(abs) ? abs : tool;
|
|
31
|
-
}
|
|
32
|
-
function newestBuildTools(names) {
|
|
33
|
-
return [...Array.isArray(names) ? names : []].filter((n) => /^\d+(\.\d+)*(-\w+)?$/.test(String(n))).toSorted((a, b) => {
|
|
34
|
-
const pa = String(a).split(/[.-]/).map(Number);
|
|
35
|
-
const pb = String(b).split(/[.-]/).map(Number);
|
|
36
|
-
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
37
|
-
const d = (pb[i] || 0) - (pa[i] || 0);
|
|
38
|
-
if (d) return d;
|
|
39
|
-
}
|
|
40
|
-
return 0;
|
|
41
|
-
})[0] ?? null;
|
|
42
|
-
}
|
|
43
|
-
function buildToolsMajor(version) {
|
|
44
|
-
const major = Number(String(version ?? "").split(/[.-]/)[0]);
|
|
45
|
-
return Number.isFinite(major) ? major : 0;
|
|
46
|
-
}
|
|
47
|
-
function findBuildTool(tools, { home = androidHome(), readDir = readdirSync, exists = existsSync } = {}) {
|
|
48
|
-
const root = join(home, "build-tools");
|
|
49
|
-
let versions = [];
|
|
50
|
-
try {
|
|
51
|
-
versions = readDir(root);
|
|
52
|
-
} catch {
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
while (versions.length) {
|
|
56
|
-
const version = newestBuildTools(versions);
|
|
57
|
-
if (!version) return null;
|
|
58
|
-
for (const tool of tools) {
|
|
59
|
-
const path = join(root, version, tool);
|
|
60
|
-
if (exists(path)) return {
|
|
61
|
-
path,
|
|
62
|
-
tool,
|
|
63
|
-
version,
|
|
64
|
-
major: buildToolsMajor(version)
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
versions = versions.filter((v) => v !== version);
|
|
68
|
-
}
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
function androidTool(tool) {
|
|
72
|
-
const resolved = androidToolPath(tool);
|
|
73
|
-
return resolved === tool ? tool : `"${resolved}"`;
|
|
74
|
-
}
|
|
75
|
-
function listInstalledSystemImages() {
|
|
76
|
-
const root = join(androidHome(), "system-images");
|
|
77
|
-
const images = [];
|
|
78
|
-
if (!existsSync(root)) return images;
|
|
79
|
-
for (const apiDir of safeList(root)) {
|
|
80
|
-
const m = apiDir.match(/^android-(\d+)$/);
|
|
81
|
-
if (!m) continue;
|
|
82
|
-
const apiPath = join(root, apiDir);
|
|
83
|
-
for (const tag of safeList(apiPath)) for (const arch of safeList(join(apiPath, tag))) images.push({
|
|
84
|
-
api: Number(m[1]),
|
|
85
|
-
tag,
|
|
86
|
-
arch,
|
|
87
|
-
pkg: `system-images;${apiDir};${tag};${arch}`
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
return images;
|
|
91
|
-
}
|
|
92
|
-
function safeList(dir) {
|
|
93
|
-
try {
|
|
94
|
-
return readdirSync(dir);
|
|
95
|
-
} catch {
|
|
96
|
-
return [];
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
function pageSizeRank(image) {
|
|
100
|
-
return /(^|_)ps16k$/.test(String(image?.tag || "")) ? 1 : 0;
|
|
101
|
-
}
|
|
102
|
-
function hostSystemImageArch(arch = process.arch) {
|
|
103
|
-
return arch === "arm64" ? "arm64-v8a" : "x86_64";
|
|
104
|
-
}
|
|
105
|
-
function pickDefaultSystemImage(images, { systemImage, hostArch } = {}) {
|
|
106
|
-
if (systemImage) return images.find((i) => i.pkg === systemImage) || null;
|
|
107
|
-
const wanted = hostArch ?? hostSystemImageArch();
|
|
108
|
-
const matching = images.filter((i) => i.arch === wanted);
|
|
109
|
-
if (matching.length === 0) return null;
|
|
110
|
-
return matching.toSorted((a, b) => pageSizeRank(a) - pageSizeRank(b) || b.api - a.api || (b.tag === "google_apis" ? 1 : 0) - (a.tag === "google_apis" ? 1 : 0))[0] ?? null;
|
|
111
|
-
}
|
|
112
|
-
function createOwnedAvd(label, { systemImage } = {}) {
|
|
113
|
-
const pick = pickDefaultSystemImage(listInstalledSystemImages(), { systemImage });
|
|
114
|
-
if (!pick) {
|
|
115
|
-
const arch = hostSystemImageArch();
|
|
116
|
-
throw new Error(`No ${arch} Android system image is installed. Install one, e.g.: sdkmanager "system-images;android-36;google_apis;${arch}"`);
|
|
117
|
-
}
|
|
118
|
-
const avdName = ownedAvdName(label);
|
|
119
|
-
getExecutor().run(`echo no | ${androidTool("avdmanager")} create avd -n "${avdName}" -k "${pick.pkg}" --device "${DEFAULT_AVD_DEVICE}"`);
|
|
120
|
-
return {
|
|
121
|
-
avdName,
|
|
122
|
-
systemImage: pick.pkg
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
function parseAvdSystemImage(configIni) {
|
|
126
|
-
for (const line of String(configIni).split(/\r?\n/)) {
|
|
127
|
-
const separator = line.indexOf("=");
|
|
128
|
-
if (separator < 0) continue;
|
|
129
|
-
if (line.slice(0, separator).trim() !== "image.sysdir.1") continue;
|
|
130
|
-
const dir = line.slice(separator + 1).trim().replace(/\/+$/, "");
|
|
131
|
-
if (!dir) return null;
|
|
132
|
-
return dir.split("/").join(";");
|
|
133
|
-
}
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
function ownedAvdSystemImage(avdName, { avdDirectory = ownedAvdDirectory, readFile = (path) => readFileSync(path, "utf8") } = {}) {
|
|
137
|
-
const directory = avdDirectory(avdName);
|
|
138
|
-
if (!directory) return null;
|
|
139
|
-
try {
|
|
140
|
-
return parseAvdSystemImage(readFile(join(directory, "config.ini")));
|
|
141
|
-
} catch {
|
|
142
|
-
return null;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
function sanitizeAvdLabel(label) {
|
|
146
|
-
return String(label).replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
147
|
-
}
|
|
148
|
-
function ownedAvdName(label) {
|
|
149
|
-
const clean = sanitizeAvdLabel(label);
|
|
150
|
-
return `stim-${clean.startsWith("stim-") ? clean.slice(5) : clean}`;
|
|
151
|
-
}
|
|
152
|
-
function deleteAvd(avdName) {
|
|
153
|
-
if (!avdName?.startsWith("stim-")) throw new Error(`Refusing to delete AVD "${avdName}": not a Stim-owned AVD (name must start with "stim-").`);
|
|
154
|
-
getExecutor().run(`${androidTool("avdmanager")} delete avd -n "${avdName}"`);
|
|
155
|
-
}
|
|
156
|
-
function parseAvdList(text) {
|
|
157
|
-
return text.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("INFO") && !l.startsWith("WARNING"));
|
|
158
|
-
}
|
|
159
|
-
function parseAdbDevices(text) {
|
|
160
|
-
const lines = text.split("\n").slice(1);
|
|
161
|
-
const emulators = [];
|
|
162
|
-
const physical = [];
|
|
163
|
-
const unhealthy = [];
|
|
164
|
-
for (const line of lines) {
|
|
165
|
-
const trimmed = line.trim();
|
|
166
|
-
if (!trimmed) continue;
|
|
167
|
-
const match = trimmed.match(/^(\S+)\s+(.+)$/);
|
|
168
|
-
if (!match) continue;
|
|
169
|
-
const serial = match[1];
|
|
170
|
-
const status = match[2].trim();
|
|
171
|
-
const m = serial.match(/^emulator-(\d+)$/);
|
|
172
|
-
if (m) {
|
|
173
|
-
const consolePort = parseInt(m[1], 10);
|
|
174
|
-
if (status === "device") emulators.push({
|
|
175
|
-
serial,
|
|
176
|
-
consolePort
|
|
177
|
-
});
|
|
178
|
-
else unhealthy.push({
|
|
179
|
-
serial,
|
|
180
|
-
kind: "emulator",
|
|
181
|
-
consolePort,
|
|
182
|
-
status
|
|
183
|
-
});
|
|
184
|
-
} else if (status === "device") physical.push({ serial });
|
|
185
|
-
else unhealthy.push({
|
|
186
|
-
serial,
|
|
187
|
-
kind: "physical",
|
|
188
|
-
status
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
return {
|
|
192
|
-
emulators,
|
|
193
|
-
physical,
|
|
194
|
-
unhealthy
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
function listAvds({ timeoutMs } = {}) {
|
|
198
|
-
return parseAvdList(getExecutor().run(`${androidTool("emulator")} -list-avds`, { timeoutMs }));
|
|
199
|
-
}
|
|
200
|
-
function listAdbDevices({ timeoutMs } = {}) {
|
|
201
|
-
return parseAdbDevices(getExecutor().run(`${androidTool("adb")} devices`, { timeoutMs }));
|
|
202
|
-
}
|
|
203
|
-
const ADB_PROP_TIMEOUT_MS = 5e3;
|
|
204
|
-
const EMULATOR_HARDWARE = /* @__PURE__ */ new Set([
|
|
205
|
-
"ranchu",
|
|
206
|
-
"goldfish",
|
|
207
|
-
"vbox86",
|
|
208
|
-
"vbox86p"
|
|
209
|
-
]);
|
|
210
|
-
function adbProp(serial, name) {
|
|
211
|
-
try {
|
|
212
|
-
const out = getExecutor().runFile(androidToolPath("adb"), [
|
|
213
|
-
"-s",
|
|
214
|
-
serial,
|
|
215
|
-
"shell",
|
|
216
|
-
"getprop",
|
|
217
|
-
name
|
|
218
|
-
], { timeoutMs: ADB_PROP_TIMEOUT_MS });
|
|
219
|
-
return String(out ?? "").trim() || null;
|
|
220
|
-
} catch {
|
|
221
|
-
return null;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
function physicalDeviceModel(serial) {
|
|
225
|
-
return adbProp(serial, "ro.product.model");
|
|
226
|
-
}
|
|
227
|
-
function androidDeviceAbi(serial) {
|
|
228
|
-
const abi = adbProp(serial, "ro.product.cpu.abi");
|
|
229
|
-
return abi && ANDROID_ABIS.has(abi) ? abi : null;
|
|
230
|
-
}
|
|
231
|
-
function androidSystemImageAbi(systemImage) {
|
|
232
|
-
const abi = String(systemImage ?? "").split(";").at(-1) ?? "";
|
|
233
|
-
return ANDROID_ABIS.has(abi) ? abi : null;
|
|
234
|
-
}
|
|
235
|
-
function looksLikeEmulator({ kernelQemu, hardware }) {
|
|
236
|
-
if (String(kernelQemu ?? "").trim() === "1") return true;
|
|
237
|
-
return EMULATOR_HARDWARE.has(String(hardware ?? "").trim().toLowerCase());
|
|
238
|
-
}
|
|
239
|
-
function probeEmulatorSerial(serial) {
|
|
240
|
-
return looksLikeEmulator({
|
|
241
|
-
kernelQemu: adbProp(serial, "ro.kernel.qemu"),
|
|
242
|
-
hardware: adbProp(serial, "ro.hardware")
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
function unreachableDeviceRefusal(entry) {
|
|
246
|
-
return {
|
|
247
|
-
error: `${entry.serial} is connected but ${entry.status}, so adb cannot talk to it.`,
|
|
248
|
-
remedy: entry.status === "unauthorized" ? "Unlock the device, accept the USB debugging prompt, then retry." : "Reconnect the device or run `adb reconnect`, then retry."
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
function emulatorRefusal(serial) {
|
|
252
|
-
return {
|
|
253
|
-
error: `${serial} is an emulator, not a physical device.`,
|
|
254
|
-
remedy: "Run `stim android` without --device to use this workspace's owned emulator."
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
function memoizeEmulatorProbe(probe = probeEmulatorSerial) {
|
|
258
|
-
const seen = /* @__PURE__ */ new Map();
|
|
259
|
-
return (serial) => {
|
|
260
|
-
const cached = seen.get(serial);
|
|
261
|
-
if (cached !== void 0) return cached;
|
|
262
|
-
const probed = probe(serial);
|
|
263
|
-
seen.set(serial, probed);
|
|
264
|
-
return probed;
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
function androidPoolCandidates(adb, isEmulator = probeEmulatorSerial) {
|
|
268
|
-
return (adb?.physical ?? []).filter((entry) => !isEmulator(entry.serial));
|
|
269
|
-
}
|
|
270
|
-
function androidPoolNoCandidatesRefusal(adb, isEmulator = probeEmulatorSerial) {
|
|
271
|
-
const physical = adb?.physical ?? [];
|
|
272
|
-
if (physical.length > 0 && physical.every((entry) => isEmulator(entry.serial))) {
|
|
273
|
-
const reasons = physical.map((entry) => emulatorRefusal(entry.serial));
|
|
274
|
-
return {
|
|
275
|
-
error: reasons.map((reason) => reason.error).join(" "),
|
|
276
|
-
remedy: [...new Set(reasons.map((reason) => reason.remedy))].join(" ")
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
return resolvePhysicalDevice(null, adb, isEmulator);
|
|
280
|
-
}
|
|
281
|
-
function resolvePhysicalDevice(requested, adb, isEmulator = probeEmulatorSerial) {
|
|
282
|
-
const physical = adb?.physical ?? [];
|
|
283
|
-
const unhealthy = adb?.unhealthy ?? [];
|
|
284
|
-
if (requested) {
|
|
285
|
-
if (physical.some((p) => p.serial === requested)) return isEmulator(requested) ? emulatorRefusal(requested) : { serial: requested };
|
|
286
|
-
if (/^emulator-\d+$/.test(requested)) return emulatorRefusal(requested);
|
|
287
|
-
const unreachable = unhealthy.find((u) => u.serial === requested);
|
|
288
|
-
if (unreachable) return unreachableDeviceRefusal(unreachable);
|
|
289
|
-
const connected = physical.map((p) => p.serial);
|
|
290
|
-
return {
|
|
291
|
-
error: connected.length ? `${requested} is not connected. adb reports these physical devices: ${connected.join(", ")}.` : `${requested} is not connected, and adb reports no physical device at all.`,
|
|
292
|
-
remedy: "Check the cable and `adb devices`, then retry with a serial adb lists."
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
if (physical.length === 1) {
|
|
296
|
-
const only = physical[0].serial;
|
|
297
|
-
return isEmulator(only) ? emulatorRefusal(only) : { serial: only };
|
|
298
|
-
}
|
|
299
|
-
if (physical.length > 1) return {
|
|
300
|
-
error: `Several physical devices are connected: ${physical.map((p) => p.serial).join(", ")}.`,
|
|
301
|
-
remedy: "Name the one to build for with `stim android --device <serial>`."
|
|
302
|
-
};
|
|
303
|
-
const unreachable = unhealthy.find((u) => u.kind === "physical");
|
|
304
|
-
if (unreachable) return unreachableDeviceRefusal(unreachable);
|
|
305
|
-
return {
|
|
306
|
-
error: "No physical Android device is connected.",
|
|
307
|
-
remedy: "Plug the device in, accept the USB debugging prompt, then check `adb devices`."
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
function nextConsolePort(claimedPorts) {
|
|
311
|
-
if (claimedPorts.length === 0) return 5554;
|
|
312
|
-
return Math.max(...claimedPorts) + 2;
|
|
313
|
-
}
|
|
314
|
-
function headlessEmulatorArgs(env = process.env, platform = process.platform) {
|
|
315
|
-
const args = [];
|
|
316
|
-
if (platform === "linux" && !env.DISPLAY && !env.WAYLAND_DISPLAY) args.push("-no-window", "-noaudio", "-no-boot-anim", "-gpu", "swiftshader_indirect", "-no-snapshot-save", "-no-snapshot-load");
|
|
317
|
-
return args;
|
|
318
|
-
}
|
|
319
|
-
function parseAvdRootIni(contents) {
|
|
320
|
-
let path = null;
|
|
321
|
-
let relativePath = null;
|
|
322
|
-
for (const rawLine of contents.split(/\r?\n/)) {
|
|
323
|
-
const line = rawLine.trim();
|
|
324
|
-
if (!line || line.startsWith("#") || line.startsWith(";")) continue;
|
|
325
|
-
const separator = line.indexOf("=");
|
|
326
|
-
if (separator < 0) continue;
|
|
327
|
-
const key = line.slice(0, separator).trim();
|
|
328
|
-
const value = line.slice(separator + 1).trim();
|
|
329
|
-
if (!value) continue;
|
|
330
|
-
if (key === "path") path = value;
|
|
331
|
-
if (key === "path.rel") relativePath = value;
|
|
332
|
-
}
|
|
333
|
-
return {
|
|
334
|
-
path,
|
|
335
|
-
relativePath
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
function avdIniPaths(root, ini) {
|
|
339
|
-
const parsed = parseAvdRootIni(ini);
|
|
340
|
-
return [parsed.relativePath && !isAbsolute(parsed.relativePath) ? resolve(dirname(root), parsed.relativePath) : null, parsed.path && isAbsolute(parsed.path) ? parsed.path : null].filter((candidate) => candidate !== null);
|
|
341
|
-
}
|
|
342
|
-
function avdStorageRoots() {
|
|
343
|
-
const env = process.env;
|
|
344
|
-
return [...new Set([
|
|
345
|
-
env.ANDROID_AVD_HOME,
|
|
346
|
-
env.ANDROID_EMULATOR_HOME ? join(env.ANDROID_EMULATOR_HOME, "avd") : null,
|
|
347
|
-
env.ANDROID_USER_HOME ? join(env.ANDROID_USER_HOME, "avd") : null,
|
|
348
|
-
env.ANDROID_SDK_HOME ? join(env.ANDROID_SDK_HOME, ".android", "avd") : null,
|
|
349
|
-
env.ANDROID_SDK_HOME ? join(env.ANDROID_SDK_HOME, "avd") : null,
|
|
350
|
-
join(homedir(), ".android", "avd")
|
|
351
|
-
].filter((value) => Boolean(value)))];
|
|
352
|
-
}
|
|
353
|
-
function avdPathExists(path) {
|
|
354
|
-
try {
|
|
355
|
-
lstatSync(path);
|
|
356
|
-
return true;
|
|
357
|
-
} catch (error) {
|
|
358
|
-
if (error.code === "ENOENT") return false;
|
|
359
|
-
throw error;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
function listOrphanedAvdDirectories(avdName) {
|
|
363
|
-
const roots = avdStorageRoots();
|
|
364
|
-
const stores = roots.filter(avdPathExists).map((root) => ({
|
|
365
|
-
root,
|
|
366
|
-
canonicalRoot: realpathSync(root),
|
|
367
|
-
names: readdirSync(root)
|
|
368
|
-
}));
|
|
369
|
-
const registered = /* @__PURE__ */ new Set();
|
|
370
|
-
for (const { root, canonicalRoot, names } of stores) for (const name of names.filter((entry) => entry.endsWith(".ini"))) {
|
|
371
|
-
const path = join(root, name);
|
|
372
|
-
const ini = readFileSync(path, "utf8");
|
|
373
|
-
const targets = /* @__PURE__ */ new Set([...avdIniPaths(root, ini), ...avdIniPaths(canonicalRoot, ini)]);
|
|
374
|
-
if (!targets.size) throw new Error(`Cannot verify AVD registration at ${path}; its data was kept.`);
|
|
375
|
-
for (const target of targets) try {
|
|
376
|
-
registered.add(realpathSync(target));
|
|
377
|
-
} catch (error) {
|
|
378
|
-
const code = error.code;
|
|
379
|
-
if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
const orphaned = [];
|
|
383
|
-
for (const { canonicalRoot, names: entries } of stores) {
|
|
384
|
-
const names = avdName ? [`${avdName}.avd`] : entries;
|
|
385
|
-
for (const entry of names) {
|
|
386
|
-
if (!/^stim-[A-Za-z0-9._-]+\.avd$/.test(entry)) continue;
|
|
387
|
-
const name = entry.slice(0, -4);
|
|
388
|
-
if (roots.some((candidate) => avdPathExists(join(candidate, `${name}.ini`)))) continue;
|
|
389
|
-
const directory = join(canonicalRoot, entry);
|
|
390
|
-
if (registered.has(directory) || !avdPathExists(directory)) continue;
|
|
391
|
-
const stat = lstatSync(directory);
|
|
392
|
-
if (!stat.isDirectory() || stat.isSymbolicLink() || realpathSync(directory) !== directory) throw new Error(`Cannot verify AVD data at ${directory}; it was kept.`);
|
|
393
|
-
if (!orphaned.some((candidate) => candidate.directory === directory)) orphaned.push({
|
|
394
|
-
name,
|
|
395
|
-
directory,
|
|
396
|
-
dev: stat.dev,
|
|
397
|
-
ino: stat.ino
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
return orphaned;
|
|
402
|
-
}
|
|
403
|
-
function ownedAvdDirectory(avdName, { env = process.env, home = homedir(), readFile = (path) => readFileSync(path, "utf8"), realpath = realpathSync, isDirectory = (path) => statSync(path).isDirectory() } = {}) {
|
|
404
|
-
if (!/^stim-[A-Za-z0-9._-]+$/.test(avdName)) return null;
|
|
405
|
-
const roots = [
|
|
406
|
-
env.ANDROID_AVD_HOME,
|
|
407
|
-
env.ANDROID_SDK_HOME ? join(env.ANDROID_SDK_HOME, "avd") : null,
|
|
408
|
-
join(home, ".android", "avd")
|
|
409
|
-
];
|
|
410
|
-
for (const root of new Set(roots.filter((value) => Boolean(value)))) {
|
|
411
|
-
let ini;
|
|
412
|
-
try {
|
|
413
|
-
ini = readFile(join(root, `${avdName}.ini`));
|
|
414
|
-
} catch (error) {
|
|
415
|
-
const code = error.code;
|
|
416
|
-
if (code === "ENOENT" || code === "ENOTDIR") continue;
|
|
417
|
-
return null;
|
|
418
|
-
}
|
|
419
|
-
for (const candidate of avdIniPaths(root, ini)) try {
|
|
420
|
-
const canonical = realpath(candidate);
|
|
421
|
-
if (isDirectory(canonical)) return canonical;
|
|
422
|
-
} catch {}
|
|
423
|
-
return null;
|
|
424
|
-
}
|
|
425
|
-
return null;
|
|
426
|
-
}
|
|
427
|
-
function withAvdConfigOverrides(contents, overrides) {
|
|
428
|
-
const newline = contents.includes("\r\n") ? "\r\n" : "\n";
|
|
429
|
-
const trailingNewline = /\r?\n$/.test(contents);
|
|
430
|
-
const lines = contents.split(/\r?\n/);
|
|
431
|
-
if (trailingNewline) lines.pop();
|
|
432
|
-
const updated = [];
|
|
433
|
-
const remaining = new Map(Object.entries(overrides));
|
|
434
|
-
for (const line of lines) {
|
|
435
|
-
const separator = line.indexOf("=");
|
|
436
|
-
const key = separator >= 0 ? line.slice(0, separator).trim() : "";
|
|
437
|
-
if (!remaining.has(key)) {
|
|
438
|
-
if (!Object.hasOwn(overrides, key)) updated.push(line);
|
|
439
|
-
continue;
|
|
440
|
-
}
|
|
441
|
-
updated.push(`${key}=${remaining.get(key)}`);
|
|
442
|
-
remaining.delete(key);
|
|
443
|
-
}
|
|
444
|
-
for (const [key, value] of remaining) updated.push(`${key}=${value}`);
|
|
445
|
-
return updated.join(newline) + (trailingNewline ? newline : "");
|
|
446
|
-
}
|
|
447
|
-
let avdConfigWriteSequence = 0;
|
|
448
|
-
function configureNewOwnedAvd(avdName, { dataPartitionSizeGb, avdConfig = {} }, { avdDirectory = ownedAvdDirectory, readFile = (path) => readFileSync(path, "utf8"), writeFile = (path, contents) => writeFileSync(path, contents, {
|
|
449
|
-
encoding: "utf8",
|
|
450
|
-
flag: "wx"
|
|
451
|
-
}), rename = renameSync, remove = (path) => rmSync(path, { force: true }) } = {}) {
|
|
452
|
-
const sizeBytes = androidDataPartitionSizeBytes(dataPartitionSizeGb);
|
|
453
|
-
const directory = avdDirectory(avdName);
|
|
454
|
-
if (!directory) throw new Error(`Could not resolve the content directory for newly created AVD ${avdName}.`);
|
|
455
|
-
const configPath = join(directory, "config.ini");
|
|
456
|
-
const original = readFile(configPath);
|
|
457
|
-
const expected = {
|
|
458
|
-
...avdConfig,
|
|
459
|
-
"disk.dataPartition.size": String(sizeBytes)
|
|
460
|
-
};
|
|
461
|
-
const updated = withAvdConfigOverrides(original, expected);
|
|
462
|
-
const tempPath = join(directory, `.config.ini.stim-${process.pid}-${++avdConfigWriteSequence}.tmp`);
|
|
463
|
-
try {
|
|
464
|
-
writeFile(tempPath, updated);
|
|
465
|
-
rename(tempPath, configPath);
|
|
466
|
-
} catch (error) {
|
|
467
|
-
try {
|
|
468
|
-
remove(tempPath);
|
|
469
|
-
} catch {}
|
|
470
|
-
throw error;
|
|
471
|
-
}
|
|
472
|
-
const verified = readFile(configPath);
|
|
473
|
-
for (const [key, value] of Object.entries(expected)) if (!verified.split(/\r?\n/).includes(`${key}=${value}`)) throw new Error(`Could not verify ${key} in ${configPath}.`);
|
|
474
|
-
return configPath;
|
|
475
|
-
}
|
|
476
|
-
function avdPoolConfiguration(dataPartitionSizeGb, avdConfig) {
|
|
477
|
-
return JSON.stringify(Object.entries({
|
|
478
|
-
...avdConfig,
|
|
479
|
-
"hw.device.name": DEFAULT_AVD_DEVICE,
|
|
480
|
-
"disk.dataPartition.size": String(androidDataPartitionSizeBytes(dataPartitionSizeGb))
|
|
481
|
-
}).toSorted(([a], [b]) => a.localeCompare(b)));
|
|
482
|
-
}
|
|
483
|
-
function ownedAvdMatchesConfiguration(avdName, configuration) {
|
|
484
|
-
const directory = ownedAvdDirectory(avdName);
|
|
485
|
-
if (!directory) return false;
|
|
486
|
-
const contents = readFileSync(join(directory, "config.ini"), "utf8");
|
|
487
|
-
const expected = JSON.parse(configuration);
|
|
488
|
-
return Array.isArray(expected) && expected.length > 0 && expected.every((entry) => Array.isArray(entry) && entry.length === 2 && entry.every((value) => typeof value === "string") && contents.split(/\r?\n/).some((line) => line.trim() === `${entry[0]}=${entry[1]}`));
|
|
489
|
-
}
|
|
490
|
-
async function resetAdoptedAvd(avdName, serial, keepPackage) {
|
|
491
|
-
const exec = getExecutor();
|
|
492
|
-
let recoveryDeadline;
|
|
493
|
-
let firstFailure;
|
|
494
|
-
let unavailableTarget = false;
|
|
495
|
-
const commandTimeout = (timeoutMs) => recoveryDeadline === void 0 ? timeoutMs : Math.max(1, Math.min(timeoutMs, recoveryDeadline - Date.now()));
|
|
496
|
-
const assertTarget = () => {
|
|
497
|
-
const resolved = resolveOwnedAvdSerial(avdName, { timeoutMs: commandTimeout(3e4) });
|
|
498
|
-
unavailableTarget = Boolean(resolved.notRunning);
|
|
499
|
-
if (resolved.serial !== serial) throw new Error(`AVD ${avdName} is no longer running on ${serial}.`);
|
|
500
|
-
};
|
|
501
|
-
for (;;) {
|
|
502
|
-
unavailableTarget = false;
|
|
503
|
-
try {
|
|
504
|
-
assertTarget();
|
|
505
|
-
const packages = exec.runFile("adb", [
|
|
506
|
-
"-s",
|
|
507
|
-
serial,
|
|
508
|
-
"shell",
|
|
509
|
-
"pm",
|
|
510
|
-
"list",
|
|
511
|
-
"packages",
|
|
512
|
-
"-3"
|
|
513
|
-
], { timeoutMs: commandTimeout(3e4) }).split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
514
|
-
const match = /^package:([A-Za-z0-9_]+(?:\.[A-Za-z0-9_]+)+)$/.exec(line);
|
|
515
|
-
if (!match) throw new Error(`Could not read installed apps on ${avdName}: ${line}`);
|
|
516
|
-
return match[1];
|
|
517
|
-
});
|
|
518
|
-
for (const packageName of packages) {
|
|
519
|
-
assertTarget();
|
|
520
|
-
const args = packageName === keepPackage ? [
|
|
521
|
-
"shell",
|
|
522
|
-
"pm",
|
|
523
|
-
"clear",
|
|
524
|
-
packageName
|
|
525
|
-
] : ["uninstall", packageName];
|
|
526
|
-
const result = exec.runFile("adb", [
|
|
527
|
-
"-s",
|
|
528
|
-
serial,
|
|
529
|
-
...args
|
|
530
|
-
], { timeoutMs: commandTimeout(12e4) });
|
|
531
|
-
if (result.trim() !== "Success") throw new Error(`Could not clean ${packageName} on ${avdName}: ${result.trim()}`);
|
|
532
|
-
}
|
|
533
|
-
return;
|
|
534
|
-
} catch (error) {
|
|
535
|
-
const failed = error;
|
|
536
|
-
const detail = [
|
|
537
|
-
failed?.message || String(error),
|
|
538
|
-
failed?.stderr,
|
|
539
|
-
failed?.stdout
|
|
540
|
-
].filter(Boolean).map(String).join("\n");
|
|
541
|
-
firstFailure ??= detail;
|
|
542
|
-
const diagnostic = detail === firstFailure ? detail : `${firstFailure}\nRecovery failed: ${detail}`;
|
|
543
|
-
const transient = /(?:^|\n)(?:adb: )?(?:error: )?(?:device offline|closed)\s*(?:$|\n)/i.test(detail);
|
|
544
|
-
recoveryDeadline ??= Date.now() + 3e4;
|
|
545
|
-
if (!unavailableTarget && !transient || Date.now() >= recoveryDeadline) throw new Error(diagnostic, { cause: error });
|
|
546
|
-
const delayMs = Math.min(1e3, recoveryDeadline - Date.now());
|
|
547
|
-
await new Promise((done) => setTimeout(done, delayMs));
|
|
548
|
-
const ready = await waitForBoot(serial, recoveryDeadline - Date.now(), { commandTimeoutMs: 5e3 });
|
|
549
|
-
if (!ready.ok) throw new Error(`${diagnostic}\nEmulator did not recover: ${JSON.stringify(ready.diagnostic)}`, { cause: error });
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
function bootAndroidEmulator(avdName, consolePort, { logFile } = {}) {
|
|
554
|
-
const child = getExecutor().spawn(androidToolPath("emulator"), [
|
|
555
|
-
"-avd",
|
|
556
|
-
avdName,
|
|
557
|
-
"-port",
|
|
558
|
-
String(consolePort),
|
|
559
|
-
...headlessEmulatorArgs()
|
|
560
|
-
], {
|
|
561
|
-
detached: true,
|
|
562
|
-
stdio: emulatorStdio(logFile)
|
|
563
|
-
});
|
|
564
|
-
child?.unref?.();
|
|
565
|
-
return child?.pid ?? null;
|
|
566
|
-
}
|
|
567
|
-
function emulatorStdio(logFile) {
|
|
568
|
-
if (!logFile) return "ignore";
|
|
569
|
-
try {
|
|
570
|
-
mkdirSync(dirname(logFile), { recursive: true });
|
|
571
|
-
const fd = openSync(logFile, "w");
|
|
572
|
-
return [
|
|
573
|
-
"ignore",
|
|
574
|
-
fd,
|
|
575
|
-
fd
|
|
576
|
-
];
|
|
577
|
-
} catch {
|
|
578
|
-
return "ignore";
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
const EMULATOR_PIPE_SEVERITY = /^(?:\S+:\s+)?(FATAL|ERROR)\s*\|\s*\S/;
|
|
582
|
-
const EMULATOR_PANIC = /^(?:\S+:\s+)?PANIC:\s*\S/;
|
|
583
|
-
function extractEmulatorFailure(text) {
|
|
584
|
-
if (typeof text !== "string" || text.trim() === "") return [];
|
|
585
|
-
const matched = [];
|
|
586
|
-
const lines = text.split("\n");
|
|
587
|
-
for (let i = 0; i < lines.length; i++) {
|
|
588
|
-
const line = String(lines[i] ?? "").replace(/\r/g, "").trim();
|
|
589
|
-
if (!line) continue;
|
|
590
|
-
const pipe = EMULATOR_PIPE_SEVERITY.exec(line);
|
|
591
|
-
if (pipe) {
|
|
592
|
-
matched.push({
|
|
593
|
-
line,
|
|
594
|
-
fatal: pipe[1] !== "ERROR",
|
|
595
|
-
index: i
|
|
596
|
-
});
|
|
597
|
-
continue;
|
|
598
|
-
}
|
|
599
|
-
if (EMULATOR_PANIC.test(line)) matched.push({
|
|
600
|
-
line,
|
|
601
|
-
fatal: true,
|
|
602
|
-
index: i
|
|
603
|
-
});
|
|
604
|
-
}
|
|
605
|
-
const fatal = matched.filter((m) => m.fatal);
|
|
606
|
-
const pool = fatal.length > 0 ? fatal : matched;
|
|
607
|
-
const byText = /* @__PURE__ */ new Map();
|
|
608
|
-
for (const entry of pool) byText.set(entry.line.replace(/\s+/g, " ").toLowerCase(), entry);
|
|
609
|
-
return [...byText.values()].toSorted((a, b) => a.index - b.index).slice(-3).map((entry) => entry.line);
|
|
610
|
-
}
|
|
611
|
-
function emulatorDiskSpaceRemedy(lines) {
|
|
612
|
-
const text = (Array.isArray(lines) ? lines : []).join("\n");
|
|
613
|
-
if (!/ENOSPC|not enough space|no space left|disk (?:is )?full/i.test(text)) return null;
|
|
614
|
-
return "Free disk space (owned AVDs normally live under ~/.android/avd and can use several GB), then run `stim android` again.";
|
|
615
|
-
}
|
|
616
|
-
function emulatorFailureRemedy(lines) {
|
|
617
|
-
return emulatorDiskSpaceRemedy(lines) ?? "Fix what the emulator reported above, then run `stim android` again.";
|
|
618
|
-
}
|
|
619
|
-
function getprop(exec, serial, prop, timeoutMs) {
|
|
620
|
-
const out = exec.runQuiet(`${androidTool("adb")} -s ${serial} shell getprop ${prop}`, { timeoutMs });
|
|
621
|
-
return typeof out === "string" ? out.trim() : "";
|
|
622
|
-
}
|
|
623
|
-
async function waitForBoot(serial, timeoutMs = 6e4, { aborted = () => false, pollMs = 1e3, commandTimeoutMs } = {}) {
|
|
624
|
-
const exec = getExecutor();
|
|
625
|
-
const start = Date.now();
|
|
626
|
-
let exited = false;
|
|
627
|
-
const probeTimeout = () => commandTimeoutMs === void 0 ? void 0 : Math.max(1, Math.min(commandTimeoutMs, timeoutMs - (Date.now() - start)));
|
|
628
|
-
while (Date.now() - start < timeoutMs) {
|
|
629
|
-
if (getprop(exec, serial, "sys.boot_completed", probeTimeout()) === "1") return { ok: true };
|
|
630
|
-
if (getprop(exec, serial, "dev.bootcomplete", probeTimeout()) === "1") return { ok: true };
|
|
631
|
-
if (aborted()) {
|
|
632
|
-
exited = true;
|
|
633
|
-
break;
|
|
634
|
-
}
|
|
635
|
-
await new Promise((r) => setTimeout(r, commandTimeoutMs === void 0 ? pollMs : Math.min(pollMs, Math.max(0, timeoutMs - (Date.now() - start)))));
|
|
636
|
-
}
|
|
637
|
-
const devicesOut = exec.runQuiet(`${androidTool("adb")} devices`, { timeoutMs: probeTimeout() });
|
|
638
|
-
return {
|
|
639
|
-
ok: false,
|
|
640
|
-
...exited ? { exited: true } : {},
|
|
641
|
-
diagnostic: {
|
|
642
|
-
devices: typeof devicesOut === "string" ? devicesOut.trim() : "",
|
|
643
|
-
sysBoot: getprop(exec, serial, "sys.boot_completed", probeTimeout()),
|
|
644
|
-
devBoot: getprop(exec, serial, "dev.bootcomplete", probeTimeout()),
|
|
645
|
-
bootAnim: getprop(exec, serial, "init.svc.bootanim", probeTimeout())
|
|
646
|
-
}
|
|
647
|
-
};
|
|
648
|
-
}
|
|
649
|
-
const ANDROID_EMULATOR_SHUTDOWN_TIMEOUT_MS = 6e4;
|
|
650
|
-
const ANDROID_EMULATOR_SHUTDOWN_POLL_MS = 100;
|
|
651
|
-
function shutdownAndroidEmulator(serial, timeoutMs = ANDROID_EMULATOR_SHUTDOWN_TIMEOUT_MS) {
|
|
652
|
-
const exec = getExecutor();
|
|
653
|
-
const started = Date.now();
|
|
654
|
-
if (exec.runQuiet(`${androidTool("adb")} -s ${serial} shell sync`, { timeoutMs: Math.min(5e3, timeoutMs) }) === null) console.error(`warning: could not flush ${serial} before shutdown; shutting it down anyway`);
|
|
655
|
-
exec.runQuiet(`${androidTool("adb")} -s ${serial} emu kill`, { timeoutMs: Math.max(1, timeoutMs - (Date.now() - started)) });
|
|
656
|
-
}
|
|
657
|
-
function sleepSync(ms) {
|
|
658
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
659
|
-
}
|
|
660
|
-
function avdProcessLockPaths(avdDirectory, platform) {
|
|
661
|
-
return ["hardware-qemu.ini.lock", "userdata-qemu.img.lock"].map((name) => join(avdDirectory, name, ...platform === "win32" ? ["pid"] : []));
|
|
662
|
-
}
|
|
663
|
-
function readAvdProcessId(path) {
|
|
664
|
-
try {
|
|
665
|
-
const pid = Number(readFileSync(path, "utf8").split("\0")[0]);
|
|
666
|
-
if (!Number.isSafeInteger(pid) || pid <= 0) throw new Error(`Could not read the emulator PID from process lock ${path}.`);
|
|
667
|
-
return pid;
|
|
668
|
-
} catch (error) {
|
|
669
|
-
const code = error.code;
|
|
670
|
-
if (code === "ENOENT" || code === "ENOTDIR") return null;
|
|
671
|
-
throw error;
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
function resolveAvdProcess(avdName, { platform = process.platform, resolveDirectory = ownedAvdDirectory, readProcessId = readAvdProcessId } = {}) {
|
|
675
|
-
const directory = resolveDirectory(avdName);
|
|
676
|
-
if (!directory) throw new Error(`Could not resolve the content directory for owned AVD ${avdName}.`);
|
|
677
|
-
for (const lockPath of avdProcessLockPaths(directory, platform)) {
|
|
678
|
-
const processId = readProcessId(lockPath);
|
|
679
|
-
if (processId !== null) return {
|
|
680
|
-
directory,
|
|
681
|
-
processId
|
|
682
|
-
};
|
|
683
|
-
}
|
|
684
|
-
return {
|
|
685
|
-
directory,
|
|
686
|
-
processId: null
|
|
687
|
-
};
|
|
688
|
-
}
|
|
689
|
-
function assertOwnedAvdStopped(avdName, { processAlive = pidExists, ...resolveOptions } = {}) {
|
|
690
|
-
const { processId } = resolveAvdProcess(avdName, resolveOptions);
|
|
691
|
-
if (processId !== null && processAlive(processId)) throw new Error(`Owned AVD ${avdName} still has a live emulator process (${processId}).`);
|
|
692
|
-
}
|
|
693
|
-
function waitForAndroidEmulatorShutdown(avdName, shutdown, { timeoutMs = ANDROID_EMULATOR_SHUTDOWN_TIMEOUT_MS, pollMs = ANDROID_EMULATOR_SHUTDOWN_POLL_MS, platform = process.platform, resolveDirectory = ownedAvdDirectory, readProcessId = readAvdProcessId, processAlive = pidExists, directoryExists = (path) => statSync(path).isDirectory(), now = Date.now, sleep = sleepSync } = {}) {
|
|
694
|
-
const { directory, processId } = resolveAvdProcess(avdName, {
|
|
695
|
-
platform,
|
|
696
|
-
resolveDirectory,
|
|
697
|
-
readProcessId
|
|
698
|
-
});
|
|
699
|
-
if (processId === null) throw new Error(`Could not find the emulator process lock for owned AVD ${avdName}.`);
|
|
700
|
-
const deadline = now() + timeoutMs;
|
|
701
|
-
const shutdownTimeoutMs = deadline - now();
|
|
702
|
-
if (shutdownTimeoutMs <= 0) throw new Error(`Owned AVD ${avdName} did not finish shutting down within ${Math.ceil(timeoutMs / 1e3)}s.`);
|
|
703
|
-
shutdown(shutdownTimeoutMs);
|
|
704
|
-
while (processAlive(processId)) {
|
|
705
|
-
const remaining = deadline - now();
|
|
706
|
-
if (remaining <= 0) throw new Error(`Owned AVD ${avdName} did not finish shutting down within ${Math.ceil(timeoutMs / 1e3)}s.`);
|
|
707
|
-
sleep(Math.min(pollMs, remaining));
|
|
708
|
-
}
|
|
709
|
-
if (!directoryExists(directory)) throw new Error(`Could not verify the content directory for owned AVD ${avdName} after shutdown.`);
|
|
710
|
-
}
|
|
711
|
-
function getAvdNameForSerial(serial, { timeoutMs } = {}) {
|
|
712
|
-
const out = getExecutor().runQuiet(`${androidTool("adb")} -s ${serial} emu avd name`, { timeoutMs });
|
|
713
|
-
if (!out) return null;
|
|
714
|
-
return out.split("\n")[0]?.trim() || null;
|
|
715
|
-
}
|
|
716
|
-
function resolveOwnedAvdSerial(avdName, { timeoutMs } = {}) {
|
|
717
|
-
const started = Date.now();
|
|
718
|
-
const remaining = () => ({ timeoutMs: timeoutMs === void 0 ? void 0 : Math.max(1, timeoutMs - (Date.now() - started)) });
|
|
719
|
-
if (!listAvds(remaining()).includes(avdName)) return { missing: true };
|
|
720
|
-
if (!avdName?.startsWith("stim-")) return { notOwned: true };
|
|
721
|
-
const adb = listAdbDevices(remaining());
|
|
722
|
-
const match = [...adb.emulators, ...adb.unhealthy.filter((entry) => entry.kind === "emulator" && entry.consolePort !== void 0)].find((e) => getAvdNameForSerial(e.serial, remaining()) === avdName);
|
|
723
|
-
if (match) return { serial: match.serial };
|
|
724
|
-
return { notRunning: true };
|
|
725
|
-
}
|
|
726
|
-
//#endregion
|
|
727
|
-
export { pickDefaultSystemImage as A, memoizeEmulatorProbe as C, ownedAvdName as D, ownedAvdMatchesConfiguration as E, shutdownAndroidEmulator as F, waitForAndroidEmulatorShutdown as I, waitForBoot as L, resetAdoptedAvd as M, resolveOwnedAvdSerial as N, ownedAvdSystemImage as O, resolvePhysicalDevice as P, listOrphanedAvdDirectories as S, ownedAvdDirectory as T, findBuildTool as _, androidSystemImageAbi as a, listAvds as b, avdPoolConfiguration as c, configureNewOwnedAvd as d, createOwnedAvd as f, extractEmulatorFailure as g, emulatorFailureRemedy as h, androidPoolNoCandidatesRefusal as i, probeEmulatorSerial as j, physicalDeviceModel as k, avdStorageRoots as l, emulatorDiskSpaceRemedy as m, androidHome as n, assertOwnedAvdStopped as o, deleteAvd as p, androidPoolCandidates as r, avdPathExists as s, androidDeviceAbi as t, bootAndroidEmulator as u, getAvdNameForSerial as v, nextConsolePort as w, listInstalledSystemImages as x, listAdbDevices as y };
|