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