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
@@ -0,0 +1,1368 @@
1
+ import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
+ import { u as getConfigDir } from "./config-CcefMY2Q.mjs";
3
+ import { r as register } from "./cache-manifest-CXR-Y8pH.mjs";
4
+ import { b as sharedBuildCache } from "./state-B3XA2wp2.mjs";
5
+ import { r as pidExists } from "./metro-CORqXyTL.mjs";
6
+ import { a as formatElapsed } from "./command-output-8wHMEde0.mjs";
7
+ import { d as readClaimSet, f as releaseClaim, h as tryAcquireClaim, i as claimRemoveCommand, n as ClaimRefusedError } from "./ownership-claim-Bf2CAfw3.mjs";
8
+ import { c as androidDataPartitionSizeBytes } from "./settings-DEJf6_kJ.mjs";
9
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
10
+ import { createHash } from "node:crypto";
11
+ import { join } from "node:path";
12
+ import { existsSync as existsSync$1, lstatSync as lstatSync$1, mkdirSync as mkdirSync$1, openSync as openSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, realpathSync as realpathSync$1, renameSync as renameSync$1, rmSync as rmSync$1, statSync as statSync$1, utimesSync, writeFileSync as writeFileSync$1 } from "fs";
13
+ import { homedir } from "os";
14
+ import { basename as basename$1, dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1, resolve as resolve$1 } from "path";
15
+ import { buildCacheKey } from "@stim-cli/core";
16
+ import * as expoFingerprint from "@expo/fingerprint";
17
+ import { buildUploadTimeoutMs } from "@stim-cli/cache";
18
+ const ASSET_MANIFEST_FILE = "assets-manifest.json";
19
+ const MAX_DEPTH = 4;
20
+ function hashFile(file) {
21
+ try {
22
+ return createHash("sha256").update(readFileSync(file)).digest("hex");
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+ function readAssetManifest(root) {
28
+ if (!existsSync(root)) return null;
29
+ const assets = [];
30
+ let broken = false;
31
+ const walk = (abs, rel, depth) => {
32
+ if (depth > MAX_DEPTH || broken) return;
33
+ let names;
34
+ try {
35
+ names = readdirSync(abs);
36
+ } catch {
37
+ broken = true;
38
+ return;
39
+ }
40
+ for (const name of names) {
41
+ const child = join(abs, name);
42
+ let stat;
43
+ try {
44
+ stat = statSync(child);
45
+ } catch {
46
+ broken = true;
47
+ return;
48
+ }
49
+ const path = rel === "" ? name : `${rel}/${name}`;
50
+ if (stat.isDirectory()) {
51
+ walk(child, path, depth + 1);
52
+ continue;
53
+ }
54
+ if (!stat.isFile()) continue;
55
+ const sha256 = hashFile(child);
56
+ if (sha256 === null) {
57
+ broken = true;
58
+ return;
59
+ }
60
+ assets.push({
61
+ path,
62
+ sha256
63
+ });
64
+ }
65
+ };
66
+ walk(root, "", 0);
67
+ if (broken) return null;
68
+ assets.sort((a, b) => a.path.localeCompare(b.path));
69
+ return {
70
+ version: 1,
71
+ assets
72
+ };
73
+ }
74
+ function parseAssetManifest(text) {
75
+ let parsed;
76
+ try {
77
+ parsed = JSON.parse(String(text));
78
+ } catch {
79
+ return null;
80
+ }
81
+ const obj = parsed;
82
+ if (!obj || obj.version !== 1 || !Array.isArray(obj.assets)) return null;
83
+ const assets = [];
84
+ for (const raw of obj.assets) {
85
+ const entry = raw;
86
+ if (typeof entry?.path !== "string" || typeof entry?.sha256 !== "string") return null;
87
+ assets.push({
88
+ path: entry.path,
89
+ sha256: entry.sha256
90
+ });
91
+ }
92
+ return {
93
+ version: 1,
94
+ assets
95
+ };
96
+ }
97
+ function pickGeneratedResDir(names, variant, reactChildren = []) {
98
+ const wanted = (variant ?? "").trim().toLowerCase();
99
+ const modern = names.filter((n) => /^createBundle.+JsAndAssets$/.test(n)).toSorted();
100
+ if (wanted) {
101
+ const match = modern.find((n) => n.toLowerCase() === `createbundle${wanted}jsandassets`);
102
+ if (match) return match;
103
+ } else if (modern.length === 1) return modern[0];
104
+ if (!names.includes("react")) return null;
105
+ const children = reactChildren.toSorted();
106
+ if (wanted) {
107
+ const match = children.find((n) => n.toLowerCase() === wanted);
108
+ if (match) return `react/${match}`;
109
+ } else if (children.length === 1) return `react/${children[0]}`;
110
+ return null;
111
+ }
112
+ function androidModuleDirs(root, { list = safeList$1 } = {}) {
113
+ const android = join(root, "android");
114
+ return ["app", ...list(android).filter((name) => name !== "app" && existsSync(join(android, name, "build", "generated", "res"))).toSorted()];
115
+ }
116
+ function findGeneratedResDir(root, variant, { list = safeList$1 } = {}) {
117
+ for (const module of androidModuleDirs(root, { list })) {
118
+ const base = join(root, "android", module, "build", "generated", "res");
119
+ const names = list(base);
120
+ if (names.length === 0) continue;
121
+ const picked = pickGeneratedResDir(names, variant, names.includes("react") ? list(join(base, "react")) : []);
122
+ if (picked) return join(base, ...picked.split("/"));
123
+ }
124
+ return null;
125
+ }
126
+ function captureAssetManifest(root, { variant = null, findDir = findGeneratedResDir, read = readAssetManifest } = {}) {
127
+ const dir = findDir(root, variant);
128
+ return dir ? read(dir) : null;
129
+ }
130
+ function compareAssetManifests(fresh, stored) {
131
+ const left = new Map(fresh.assets.map((a) => [a.path, a.sha256]));
132
+ const right = new Map(stored.assets.map((a) => [a.path, a.sha256]));
133
+ const added = [];
134
+ const changed = [];
135
+ const removed = [];
136
+ for (const [path, sha256] of left) if (!right.has(path)) added.push(path);
137
+ else if (right.get(path) !== sha256) changed.push(path);
138
+ for (const path of right.keys()) if (!left.has(path)) removed.push(path);
139
+ added.sort();
140
+ changed.sort();
141
+ removed.sort();
142
+ return {
143
+ same: added.length === 0 && changed.length === 0 && removed.length === 0,
144
+ added,
145
+ removed,
146
+ changed,
147
+ example: added[0] ?? changed[0] ?? removed[0] ?? null
148
+ };
149
+ }
150
+ function assetDiffReason(diff) {
151
+ const which = diff.added.length ? "added" : diff.changed.length ? "changed" : "removed";
152
+ return `this workspace emits a different asset set than the cached build did (${diff.added.length} added, ${diff.changed.length} changed, ${diff.removed.length} removed; e.g. ${which} ${diff.example})`;
153
+ }
154
+ function safeList$1(dir) {
155
+ try {
156
+ return readdirSync(dir);
157
+ } catch {
158
+ return [];
159
+ }
160
+ }
161
+ //#endregion
162
+ //#region src/build-cache.ts
163
+ function entryDir(platform, key, root = sharedBuildCache()) {
164
+ return join$1(root, platform, key);
165
+ }
166
+ function artifactIn(dir) {
167
+ if (!existsSync$1(dir)) return null;
168
+ let found;
169
+ try {
170
+ found = readdirSync$1(dir).find((f) => f.endsWith(".app") || f.endsWith(".apk"));
171
+ } catch {
172
+ return null;
173
+ }
174
+ return found ? join$1(dir, found) : null;
175
+ }
176
+ const FINGERPRINT_PLATFORMS = /* @__PURE__ */ new Set(["ios", "android"]);
177
+ /**
178
+ * Paths that exist in a working checkout and not in a fresh one, which
179
+ * @expo/fingerprint does not ignore on its own. A fingerprint that counts them
180
+ * cannot match across checkouts, which is the parity failure `doctor` reports.
181
+ *
182
+ * The list stays narrow on purpose: a path belongs here only when no native
183
+ * build on any project can read it. Anything a single project can judge --
184
+ * a lockfile with machine paths, a generated report -- belongs in that
185
+ * project's `.fingerprintignore`, which still applies on top of this.
186
+ */
187
+ const DEFAULT_FINGERPRINT_IGNORES = ["**/android/local.properties", "**/android/.idea/**"];
188
+ async function fingerprintProject(projectRoot, { platform, createFingerprint = expoFingerprint.createFingerprintAsync, debug = false } = {}) {
189
+ const result = await createFingerprint(projectRoot, {
190
+ ...platform && FINGERPRINT_PLATFORMS.has(platform) ? { platforms: [platform] } : void 0,
191
+ ...debug ? { debug } : {},
192
+ ignorePaths: DEFAULT_FINGERPRINT_IGNORES,
193
+ silent: true
194
+ });
195
+ const hash = result?.hash ?? null;
196
+ if (!hash) return null;
197
+ return {
198
+ hash,
199
+ sources: Array.isArray(result?.sources) ? result.sources : []
200
+ };
201
+ }
202
+ function registerOnce(root) {
203
+ try {
204
+ register({
205
+ dir: root,
206
+ name: "Build cache",
207
+ prune: "entries",
208
+ entriesDepth: 2,
209
+ note: "built .app/.apk keyed on the native fingerprint"
210
+ });
211
+ } catch {}
212
+ }
213
+ function resolveBuild(platform, key, root = sharedBuildCache()) {
214
+ const hit = artifactIn(entryDir(platform, key, root));
215
+ if (!hit) return null;
216
+ try {
217
+ utimesSync(dirname$1(hit), /* @__PURE__ */ new Date(), /* @__PURE__ */ new Date());
218
+ } catch {}
219
+ return hit;
220
+ }
221
+ function storeBuild(platform, key, buildPath, rootOrOptions = {}) {
222
+ const options = typeof rootOrOptions === "string" ? { root: rootOrOptions } : rootOrOptions || {};
223
+ const root = options.root || sharedBuildCache();
224
+ const overwrite = Boolean(options.overwrite);
225
+ if (!buildPath || !existsSync$1(buildPath)) throw new Error(`No build to store at ${buildPath}`);
226
+ registerOnce(root);
227
+ const dest = entryDir(platform, key, root);
228
+ const existing = artifactIn(dest);
229
+ if (existing && !overwrite) return existing;
230
+ const staging = `${dest}.staging-${process.pid}`;
231
+ rmSync$1(staging, {
232
+ recursive: true,
233
+ force: true
234
+ });
235
+ mkdirSync$1(staging, { recursive: true });
236
+ try {
237
+ getExecutor().runFile("cp", [
238
+ "-c",
239
+ "-R",
240
+ buildPath,
241
+ join$1(staging, basename$1(buildPath))
242
+ ]);
243
+ } catch {
244
+ getExecutor().runFile("cp", [
245
+ "-R",
246
+ buildPath,
247
+ join$1(staging, basename$1(buildPath))
248
+ ]);
249
+ }
250
+ if (Array.isArray(options.sources)) try {
251
+ writeFileSync$1(join$1(staging, SOURCES_FILE), JSON.stringify(options.sources));
252
+ } catch {}
253
+ if (options.assetManifest) try {
254
+ writeFileSync$1(join$1(staging, ASSET_MANIFEST_FILE), JSON.stringify(options.assetManifest));
255
+ } catch {}
256
+ mkdirSync$1(dirname$1(dest), { recursive: true });
257
+ rmSync$1(dest, {
258
+ recursive: true,
259
+ force: true
260
+ });
261
+ renameSync$1(staging, dest);
262
+ return artifactIn(dest);
263
+ }
264
+ const PROVIDER_DOWNLOAD_DIR = "cache-provider";
265
+ function providerDownloadPath(workspacePath) {
266
+ return join$1(workspacePath, PROVIDER_DOWNLOAD_DIR);
267
+ }
268
+ /**
269
+ * Empties and registers the scratch directory a provider downloads into. It is
270
+ * created only when a provider is about to be asked, and registered so `gc`
271
+ * reports what an interrupted run left behind.
272
+ */
273
+ function prepareProviderDownloadDir(dir) {
274
+ rmSync$1(dir, {
275
+ recursive: true,
276
+ force: true
277
+ });
278
+ mkdirSync$1(dir, { recursive: true });
279
+ try {
280
+ register({
281
+ dir,
282
+ name: "Cache provider downloads",
283
+ prune: "entries",
284
+ entriesDepth: 1,
285
+ note: "artifacts fetched from the project cache provider; anything left here is from an interrupted run"
286
+ });
287
+ } catch {}
288
+ }
289
+ function filesystemBuildCapability({ root, sources, assetManifest, resolve = resolveBuild, store = storeBuild } = {}) {
290
+ const stored = {
291
+ ...root === void 0 ? {} : { root },
292
+ ...sources === void 0 ? {} : { sources },
293
+ ...assetManifest === void 0 ? {} : { assetManifest }
294
+ };
295
+ return {
296
+ resolve: ({ platform, key }) => root === void 0 ? resolve(platform, key) : resolve(platform, key, root),
297
+ store: ({ platform, key, sourcePath, overwrite }) => store(platform, key, sourcePath, {
298
+ ...stored,
299
+ overwrite
300
+ })
301
+ };
302
+ }
303
+ function providerUploadOutcome(result, name) {
304
+ if (!result) return null;
305
+ const label = name || "the cache provider";
306
+ if (result.timedOut) return {
307
+ line: `${label} upload was cancelled after ${buildUploadTimeoutMs()}ms`,
308
+ warn: true
309
+ };
310
+ if (result.failed) return {
311
+ line: `${label} upload failed: ${result.failed}`,
312
+ warn: true
313
+ };
314
+ return {
315
+ line: `uploaded (${label})`,
316
+ warn: false
317
+ };
318
+ }
319
+ const SOURCES_FILE = "fingerprint-sources.json";
320
+ function storedSources(platform, key, root = sharedBuildCache()) {
321
+ try {
322
+ const parsed = JSON.parse(readFileSync$1(join$1(entryDir(platform, key, root), SOURCES_FILE), "utf-8"));
323
+ return Array.isArray(parsed) ? parsed : null;
324
+ } catch {
325
+ return null;
326
+ }
327
+ }
328
+ function storedAssetManifest(platform, key, root = sharedBuildCache()) {
329
+ try {
330
+ return parseAssetManifest(readFileSync$1(join$1(entryDir(platform, key, root), ASSET_MANIFEST_FILE), "utf-8"));
331
+ } catch {
332
+ return null;
333
+ }
334
+ }
335
+ function sourceName(source) {
336
+ const s = source;
337
+ if (typeof s?.filePath === "string" && s.filePath !== "") return s.filePath;
338
+ if (typeof s?.id === "string" && s.id !== "") return s.id;
339
+ return null;
340
+ }
341
+ function diffItemName(item) {
342
+ const o = item;
343
+ for (const candidate of [
344
+ o,
345
+ o?.addedSource,
346
+ o?.removedSource,
347
+ o?.afterSource,
348
+ o?.beforeSource
349
+ ]) {
350
+ const name = sourceName(candidate);
351
+ if (name) return name;
352
+ }
353
+ return null;
354
+ }
355
+ function compareSourceLists(previous, current) {
356
+ const previousByName = /* @__PURE__ */ new Map();
357
+ for (const source of previous) {
358
+ const name = sourceName(source);
359
+ if (name !== null) previousByName.set(name, source.hash ?? null);
360
+ }
361
+ const changed = [];
362
+ const seen = /* @__PURE__ */ new Set();
363
+ for (const source of current) {
364
+ const name = sourceName(source);
365
+ if (name === null || seen.has(name)) continue;
366
+ seen.add(name);
367
+ const hash = source.hash;
368
+ if (!previousByName.has(name) || previousByName.get(name) !== (hash ?? null)) changed.push(name);
369
+ }
370
+ for (const [name] of previousByName) if (!seen.has(name)) changed.push(name);
371
+ return changed;
372
+ }
373
+ function diffFingerprintSources({ previous, previousHash = null, current, differ = null }) {
374
+ if (typeof differ === "function") try {
375
+ const items = differ({
376
+ sources: previous,
377
+ hash: previousHash ?? ""
378
+ }, {
379
+ sources: current.sources,
380
+ hash: current.hash
381
+ });
382
+ if (Array.isArray(items)) {
383
+ const names = [];
384
+ const seen = /* @__PURE__ */ new Set();
385
+ for (const item of items) {
386
+ const name = diffItemName(item);
387
+ if (name !== null && !seen.has(name)) {
388
+ seen.add(name);
389
+ names.push(name);
390
+ }
391
+ }
392
+ return names;
393
+ }
394
+ } catch {}
395
+ return compareSourceLists(previous, current.sources);
396
+ }
397
+ function fingerprintDiffSuffix(changed) {
398
+ if (!changed.length) return "";
399
+ const shown = changed.slice(0, 3).join(", ");
400
+ return ` -- ${changed.length} source${changed.length === 1 ? "" : "s"} changed: ${shown}`;
401
+ }
402
+ function describeFingerprintMiss({ platform, current, lastBuild, root = sharedBuildCache(), differ = expoFingerprint.diffFingerprints }) {
403
+ if (!lastBuild || lastBuild.platform !== platform) return null;
404
+ const previousHash = typeof lastBuild.fingerprint === "string" ? lastBuild.fingerprint : null;
405
+ const previousKey = typeof lastBuild.cacheKey === "string" ? lastBuild.cacheKey : null;
406
+ if (!previousHash || !previousKey || previousHash === current.hash) return null;
407
+ const previous = storedSources(platform, previousKey, root);
408
+ if (!previous) return null;
409
+ const changed = diffFingerprintSources({
410
+ previous,
411
+ previousHash,
412
+ current,
413
+ differ
414
+ });
415
+ return changed.length ? {
416
+ changed,
417
+ previousHash
418
+ } : null;
419
+ }
420
+ async function refingerprintAfterMutation({ projectRoot, platform, previousHash, fingerprint = fingerprintProject }) {
421
+ let computed = null;
422
+ try {
423
+ computed = await fingerprint(projectRoot, { platform });
424
+ } catch {
425
+ return null;
426
+ }
427
+ if (!computed?.hash) return null;
428
+ return {
429
+ hash: computed.hash,
430
+ sources: computed.sources,
431
+ moved: computed.hash !== previousHash
432
+ };
433
+ }
434
+ function untrackedNativeFiles({ projectRoot, exec = getExecutor() }) {
435
+ let out;
436
+ try {
437
+ out = exec.runFile("git", [
438
+ "-C",
439
+ projectRoot,
440
+ "ls-files",
441
+ "--others",
442
+ "--exclude-standard",
443
+ "--",
444
+ "ios",
445
+ "android"
446
+ ]);
447
+ } catch {
448
+ return [];
449
+ }
450
+ return String(out || "").split("\n").map((line) => line.trim()).filter((line) => line !== "");
451
+ }
452
+ function untrackedMissLine(files, cap = 3) {
453
+ if (!Array.isArray(files) || files.length === 0) return null;
454
+ const shown = files.slice(0, cap);
455
+ const more = files.length > shown.length ? `, and ${files.length - shown.length} more` : "";
456
+ return `no previous entry to diff against; ${files.length} untracked file${files.length === 1 ? "" : "s"} under ios/ or android/ are hashed like any other source: ${shown.join(", ")}${more} -- list the build-irrelevant ones in .fingerprintignore`;
457
+ }
458
+ function fingerprintDiffRecord({ changed, previousHash, hash }) {
459
+ const shown = changed.slice(0, 20);
460
+ return {
461
+ src: "build",
462
+ level: "info",
463
+ event: "fingerprint_diff",
464
+ msg: `fingerprint ${previousHash} -> ${hash}: ${changed.length} source${changed.length === 1 ? "" : "s"} changed: ` + shown.join(", ") + (changed.length > shown.length ? `, and ${changed.length - shown.length} more` : ""),
465
+ changed: changed.length,
466
+ sources: shown
467
+ };
468
+ }
469
+ //#endregion
470
+ //#region src/engine/build-lock.ts
471
+ const LOCK_SUFFIX = ".lock";
472
+ const WAIT_POLL_MS = 1e3;
473
+ const WAIT_PROGRESS_MS = 3e4;
474
+ const WAIT_CEILING_MS = 54e5;
475
+ function sleepAsync(ms) {
476
+ return new Promise((resolve) => setTimeout(resolve, ms));
477
+ }
478
+ function segment(value) {
479
+ return String(value).replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^\.+/, "") || "unknown";
480
+ }
481
+ function buildLocksDir() {
482
+ return join(getConfigDir(), "build-locks");
483
+ }
484
+ function buildLockPath(platform, key) {
485
+ return join(buildLocksDir(), `${segment(platform)}-${segment(key)}${LOCK_SUFFIX}`);
486
+ }
487
+ function label(platform) {
488
+ return `${platform} build`;
489
+ }
490
+ function toRecord(holder) {
491
+ const details = holder.details;
492
+ return {
493
+ pid: holder.owner.pid,
494
+ projectRoot: typeof details.projectRoot === "string" ? details.projectRoot : null,
495
+ startedAt: holder.startedAt || null,
496
+ logFile: typeof details.logFile === "string" ? details.logFile : null
497
+ };
498
+ }
499
+ function readBuildLock(pathOrSpec) {
500
+ const path = typeof pathOrSpec === "string" ? pathOrSpec : buildLockPath(pathOrSpec.platform, pathOrSpec.key);
501
+ const survey = readClaimSet(path);
502
+ const holder = survey.live[0] ?? survey.dead[0];
503
+ return holder ? toRecord(holder) : null;
504
+ }
505
+ function acquireBuildLock({ platform, key, root = null, logFile = null }) {
506
+ const path = buildLockPath(platform, key);
507
+ const attempt = tryAcquireClaim({
508
+ root: path,
509
+ mode: "exclusive",
510
+ label: label(platform),
511
+ details: {
512
+ projectRoot: root,
513
+ logFile
514
+ }
515
+ });
516
+ const reaped = attempt.reaped[0];
517
+ if (attempt.acquired) {
518
+ const handle = {
519
+ acquired: true,
520
+ path,
521
+ lock: {
522
+ pid: attempt.acquired.owner.pid,
523
+ projectRoot: root,
524
+ startedAt: attempt.acquired.startedAt,
525
+ logFile
526
+ },
527
+ claim: attempt.acquired
528
+ };
529
+ return reaped ? {
530
+ ...handle,
531
+ tookOver: toRecord(reaped)
532
+ } : handle;
533
+ }
534
+ if (attempt.pending) releaseClaim(attempt.pending);
535
+ const holder = attempt.held ?? attempt.waitingFor?.[0];
536
+ return holder ? {
537
+ held: toRecord(holder),
538
+ path
539
+ } : { path };
540
+ }
541
+ function releaseBuildLock(handle) {
542
+ return releaseClaim(handle?.claim);
543
+ }
544
+ function listBuildLocks() {
545
+ const dir = buildLocksDir();
546
+ let names;
547
+ try {
548
+ names = readdirSync(dir);
549
+ } catch {
550
+ return [];
551
+ }
552
+ const locks = [];
553
+ for (const name of names) {
554
+ if (!name.endsWith(LOCK_SUFFIX)) continue;
555
+ const path = join(dir, name);
556
+ try {
557
+ if (!statSync(path).isDirectory()) continue;
558
+ } catch {
559
+ continue;
560
+ }
561
+ const stem = name.slice(0, -5);
562
+ const cut = stem.indexOf("-");
563
+ const survey = readClaimSet(path);
564
+ const holder = survey.live[0] ?? survey.dead[0];
565
+ const info = holder ? toRecord(holder) : null;
566
+ locks.push({
567
+ path,
568
+ name,
569
+ platform: cut > 0 ? stem.slice(0, cut) : stem,
570
+ key: cut > 0 ? stem.slice(cut + 1) : null,
571
+ pid: info?.pid ?? null,
572
+ projectRoot: info?.projectRoot ?? null,
573
+ startedAt: info?.startedAt ?? null,
574
+ logFile: info?.logFile ?? null,
575
+ alive: survey.live.length > 0,
576
+ unresolved: survey.unresolved.length > 0
577
+ });
578
+ }
579
+ return locks;
580
+ }
581
+ function waitingLine({ projectRoot, pid, elapsedMs, logFile }) {
582
+ const where = projectRoot || "another workspace";
583
+ const tail = logFile ? ` -- tail ${logFile}` : "";
584
+ return `${"build".padEnd(11)} waiting on ${where} (pid ${pid ?? "?"}, ${formatElapsed(elapsedMs)} elapsed)${tail} -- stim guide lifecycle concurrency`;
585
+ }
586
+ function takeoverLine({ projectRoot, pid, logFile, startedAt = null, now = Date.now }) {
587
+ const where = projectRoot || "another workspace";
588
+ const started = startedAt ? Date.parse(startedAt) : NaN;
589
+ const ago = Number.isFinite(started) ? ` (it started ${formatElapsed(now() - started)} ago)` : "";
590
+ const tail = logFile ? ` -- read ${logFile} before this one finishes` : "";
591
+ return `RETRY: ${where}'s build of this fingerprint (pid ${pid ?? "?"}) FAILED without an artifact${ago}, and this run rebuilds the SAME inputs, so expect the same failure unless something changed${tail}`;
592
+ }
593
+ async function waitForBuild({ platform, key, resolve = resolveBuild, intervalMs = WAIT_POLL_MS, progressMs = WAIT_PROGRESS_MS, ceilingMs = WAIT_CEILING_MS, out = () => {}, now = Date.now, sleep = sleepAsync }) {
594
+ const path = buildLockPath(platform, key);
595
+ const started = now();
596
+ let lastProgress = started;
597
+ let holder = readBuildLock(path);
598
+ for (;;) {
599
+ const hit = resolve(platform, key);
600
+ if (hit) return {
601
+ hit,
602
+ waitedMs: now() - started
603
+ };
604
+ const survey = readClaimSet(path);
605
+ const unresolved = survey.unresolved[0];
606
+ if (unresolved) throw new ClaimRefusedError({
607
+ claimPath: unresolved.path,
608
+ root: path,
609
+ label: label(platform),
610
+ reason: unresolved.reason
611
+ });
612
+ const live = survey.live[0];
613
+ if (live) holder = toRecord(live);
614
+ else {
615
+ const dead = survey.dead[0];
616
+ if (dead) return {
617
+ builderFailed: `the builder (pid ${dead.owner.pid}) is gone`,
618
+ holder: toRecord(dead),
619
+ waitedMs: now() - started
620
+ };
621
+ return {
622
+ lockReleased: true,
623
+ holder,
624
+ waitedMs: now() - started
625
+ };
626
+ }
627
+ const elapsed = now() - started;
628
+ if (elapsed >= ceilingMs) {
629
+ const err = /* @__PURE__ */ new Error(`Waited ${formatElapsed(elapsed)} for ${holder?.projectRoot || "another workspace"}'s ${platform} build of ${key} without an artifact, and pid ${holder?.pid} is still the live holder. The lock is ${path}; remove it if that process is not really building:\n ${claimRemoveCommand(path)}`);
630
+ err.code = "STIM_BUILD_WAIT_TIMEOUT";
631
+ err.lockPath = path;
632
+ err.holder = holder ?? void 0;
633
+ throw err;
634
+ }
635
+ if (now() - lastProgress >= progressMs) {
636
+ lastProgress = now();
637
+ out(waitingLine({
638
+ projectRoot: holder.projectRoot,
639
+ pid: holder.pid,
640
+ elapsedMs: elapsed,
641
+ logFile: holder.logFile
642
+ }));
643
+ }
644
+ await sleep(intervalMs);
645
+ }
646
+ }
647
+ //#endregion
648
+ //#region src/sim/android.ts
649
+ const ANDROID_ABIS = /* @__PURE__ */ new Set([
650
+ "armeabi-v7a",
651
+ "arm64-v8a",
652
+ "x86",
653
+ "x86_64"
654
+ ]);
655
+ const DEFAULT_AVD_DEVICE = "pixel_6";
656
+ function androidHome() {
657
+ return process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT || join$1(homedir(), "Library", "Android", "sdk");
658
+ }
659
+ const SDK_TOOL_LOCATIONS = {
660
+ emulator: ["emulator", "emulator"],
661
+ adb: ["platform-tools", "adb"],
662
+ avdmanager: [
663
+ "cmdline-tools",
664
+ "latest",
665
+ "bin",
666
+ "avdmanager"
667
+ ]
668
+ };
669
+ function androidToolPath(tool) {
670
+ const abs = join$1(androidHome(), ...SDK_TOOL_LOCATIONS[tool]);
671
+ return existsSync$1(abs) ? abs : tool;
672
+ }
673
+ function newestBuildTools(names) {
674
+ return [...Array.isArray(names) ? names : []].filter((n) => /^\d+(\.\d+)*(-\w+)?$/.test(String(n))).toSorted((a, b) => {
675
+ const pa = String(a).split(/[.-]/).map(Number);
676
+ const pb = String(b).split(/[.-]/).map(Number);
677
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
678
+ const d = (pb[i] || 0) - (pa[i] || 0);
679
+ if (d) return d;
680
+ }
681
+ return 0;
682
+ })[0] ?? null;
683
+ }
684
+ function buildToolsMajor(version) {
685
+ const major = Number(String(version ?? "").split(/[.-]/)[0]);
686
+ return Number.isFinite(major) ? major : 0;
687
+ }
688
+ function findBuildTool(tools, { home = androidHome(), readDir = readdirSync$1, exists = existsSync$1 } = {}) {
689
+ const root = join$1(home, "build-tools");
690
+ let versions = [];
691
+ try {
692
+ versions = readDir(root);
693
+ } catch {
694
+ return null;
695
+ }
696
+ while (versions.length) {
697
+ const version = newestBuildTools(versions);
698
+ if (!version) return null;
699
+ for (const tool of tools) {
700
+ const path = join$1(root, version, tool);
701
+ if (exists(path)) return {
702
+ path,
703
+ tool,
704
+ version,
705
+ major: buildToolsMajor(version)
706
+ };
707
+ }
708
+ versions = versions.filter((v) => v !== version);
709
+ }
710
+ return null;
711
+ }
712
+ function androidTool(tool) {
713
+ const resolved = androidToolPath(tool);
714
+ return resolved === tool ? tool : `"${resolved}"`;
715
+ }
716
+ function listInstalledSystemImages() {
717
+ const root = join$1(androidHome(), "system-images");
718
+ const images = [];
719
+ if (!existsSync$1(root)) return images;
720
+ for (const apiDir of safeList(root)) {
721
+ const m = apiDir.match(/^android-(\d+)$/);
722
+ if (!m) continue;
723
+ const apiPath = join$1(root, apiDir);
724
+ for (const tag of safeList(apiPath)) for (const arch of safeList(join$1(apiPath, tag))) images.push({
725
+ api: Number(m[1]),
726
+ tag,
727
+ arch,
728
+ pkg: `system-images;${apiDir};${tag};${arch}`
729
+ });
730
+ }
731
+ return images;
732
+ }
733
+ function safeList(dir) {
734
+ try {
735
+ return readdirSync$1(dir);
736
+ } catch {
737
+ return [];
738
+ }
739
+ }
740
+ function pageSizeRank(image) {
741
+ return /(^|_)ps16k$/.test(String(image?.tag || "")) ? 1 : 0;
742
+ }
743
+ function hostSystemImageArch(arch = process.arch) {
744
+ return arch === "arm64" ? "arm64-v8a" : "x86_64";
745
+ }
746
+ function pickDefaultSystemImage(images, { systemImage, hostArch } = {}) {
747
+ if (systemImage) return images.find((i) => i.pkg === systemImage) || null;
748
+ const wanted = hostArch ?? hostSystemImageArch();
749
+ const matching = images.filter((i) => i.arch === wanted);
750
+ if (matching.length === 0) return null;
751
+ 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;
752
+ }
753
+ function createOwnedAvd(label, { systemImage } = {}) {
754
+ const pick = pickDefaultSystemImage(listInstalledSystemImages(), { systemImage });
755
+ if (!pick) {
756
+ const arch = hostSystemImageArch();
757
+ throw new Error(`No ${arch} Android system image is installed. Install one, e.g.: sdkmanager "system-images;android-36;google_apis;${arch}"`);
758
+ }
759
+ const avdName = ownedAvdName(label);
760
+ getExecutor().run(`echo no | ${androidTool("avdmanager")} create avd -n "${avdName}" -k "${pick.pkg}" --device "${DEFAULT_AVD_DEVICE}"`);
761
+ return {
762
+ avdName,
763
+ systemImage: pick.pkg
764
+ };
765
+ }
766
+ function parseAvdSystemImage(configIni) {
767
+ for (const line of String(configIni).split(/\r?\n/)) {
768
+ const separator = line.indexOf("=");
769
+ if (separator < 0) continue;
770
+ if (line.slice(0, separator).trim() !== "image.sysdir.1") continue;
771
+ const dir = line.slice(separator + 1).trim().replace(/\/+$/, "");
772
+ if (!dir) return null;
773
+ return dir.split("/").join(";");
774
+ }
775
+ return null;
776
+ }
777
+ function ownedAvdSystemImage(avdName, { avdDirectory = ownedAvdDirectory, readFile = (path) => readFileSync$1(path, "utf8") } = {}) {
778
+ const directory = avdDirectory(avdName);
779
+ if (!directory) return null;
780
+ try {
781
+ return parseAvdSystemImage(readFile(join$1(directory, "config.ini")));
782
+ } catch {
783
+ return null;
784
+ }
785
+ }
786
+ function sanitizeAvdLabel(label) {
787
+ return String(label).replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
788
+ }
789
+ function ownedAvdName(label) {
790
+ const clean = sanitizeAvdLabel(label);
791
+ return `stim-${clean.startsWith("stim-") ? clean.slice(5) : clean}`;
792
+ }
793
+ function deleteAvd(avdName) {
794
+ if (!avdName?.startsWith("stim-")) throw new Error(`Refusing to delete AVD "${avdName}": not a Stim-owned AVD (name must start with "stim-").`);
795
+ getExecutor().run(`${androidTool("avdmanager")} delete avd -n "${avdName}"`);
796
+ }
797
+ function parseAvdList(text) {
798
+ return text.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("INFO") && !l.startsWith("WARNING"));
799
+ }
800
+ function parseAdbDevices(text) {
801
+ const lines = text.split("\n").slice(1);
802
+ const emulators = [];
803
+ const physical = [];
804
+ const unhealthy = [];
805
+ for (const line of lines) {
806
+ const trimmed = line.trim();
807
+ if (!trimmed) continue;
808
+ const match = trimmed.match(/^(\S+)\s+(.+)$/);
809
+ if (!match) continue;
810
+ const serial = match[1];
811
+ const status = match[2].trim();
812
+ const m = serial.match(/^emulator-(\d+)$/);
813
+ if (m) {
814
+ const consolePort = parseInt(m[1], 10);
815
+ if (status === "device") emulators.push({
816
+ serial,
817
+ consolePort
818
+ });
819
+ else unhealthy.push({
820
+ serial,
821
+ kind: "emulator",
822
+ consolePort,
823
+ status
824
+ });
825
+ } else if (status === "device") physical.push({ serial });
826
+ else unhealthy.push({
827
+ serial,
828
+ kind: "physical",
829
+ status
830
+ });
831
+ }
832
+ return {
833
+ emulators,
834
+ physical,
835
+ unhealthy
836
+ };
837
+ }
838
+ function listAvds({ timeoutMs } = {}) {
839
+ return parseAvdList(getExecutor().run(`${androidTool("emulator")} -list-avds`, { timeoutMs }));
840
+ }
841
+ function listAdbDevices({ timeoutMs } = {}) {
842
+ return parseAdbDevices(getExecutor().run(`${androidTool("adb")} devices`, { timeoutMs }));
843
+ }
844
+ const ADB_PROP_TIMEOUT_MS = 5e3;
845
+ const EMULATOR_HARDWARE = /* @__PURE__ */ new Set([
846
+ "ranchu",
847
+ "goldfish",
848
+ "vbox86",
849
+ "vbox86p"
850
+ ]);
851
+ function adbProp(serial, name) {
852
+ try {
853
+ const out = getExecutor().runFile(androidToolPath("adb"), [
854
+ "-s",
855
+ serial,
856
+ "shell",
857
+ "getprop",
858
+ name
859
+ ], { timeoutMs: ADB_PROP_TIMEOUT_MS });
860
+ return String(out ?? "").trim() || null;
861
+ } catch {
862
+ return null;
863
+ }
864
+ }
865
+ function physicalDeviceModel(serial) {
866
+ return adbProp(serial, "ro.product.model");
867
+ }
868
+ function androidDeviceAbi(serial) {
869
+ const abi = adbProp(serial, "ro.product.cpu.abi");
870
+ return abi && ANDROID_ABIS.has(abi) ? abi : null;
871
+ }
872
+ function androidSystemImageAbi(systemImage) {
873
+ const abi = String(systemImage ?? "").split(";").at(-1) ?? "";
874
+ return ANDROID_ABIS.has(abi) ? abi : null;
875
+ }
876
+ function looksLikeEmulator({ kernelQemu, hardware }) {
877
+ if (String(kernelQemu ?? "").trim() === "1") return true;
878
+ return EMULATOR_HARDWARE.has(String(hardware ?? "").trim().toLowerCase());
879
+ }
880
+ function probeEmulatorSerial(serial) {
881
+ return looksLikeEmulator({
882
+ kernelQemu: adbProp(serial, "ro.kernel.qemu"),
883
+ hardware: adbProp(serial, "ro.hardware")
884
+ });
885
+ }
886
+ function unreachableDeviceRefusal(entry) {
887
+ return {
888
+ error: `${entry.serial} is connected but ${entry.status}, so adb cannot talk to it.`,
889
+ remedy: entry.status === "unauthorized" ? "Unlock the device, accept the USB debugging prompt, then retry." : "Reconnect the device or run `adb reconnect`, then retry."
890
+ };
891
+ }
892
+ function emulatorRefusal(serial) {
893
+ return {
894
+ error: `${serial} is an emulator, not a physical device.`,
895
+ remedy: "Run `stim android` without --device to use this workspace's owned emulator."
896
+ };
897
+ }
898
+ function memoizeEmulatorProbe(probe = probeEmulatorSerial) {
899
+ const seen = /* @__PURE__ */ new Map();
900
+ return (serial) => {
901
+ const cached = seen.get(serial);
902
+ if (cached !== void 0) return cached;
903
+ const probed = probe(serial);
904
+ seen.set(serial, probed);
905
+ return probed;
906
+ };
907
+ }
908
+ function androidPoolCandidates(adb, isEmulator = probeEmulatorSerial) {
909
+ return (adb?.physical ?? []).filter((entry) => !isEmulator(entry.serial));
910
+ }
911
+ function androidPoolNoCandidatesRefusal(adb, isEmulator = probeEmulatorSerial) {
912
+ const physical = adb?.physical ?? [];
913
+ if (physical.length > 0 && physical.every((entry) => isEmulator(entry.serial))) {
914
+ const reasons = physical.map((entry) => emulatorRefusal(entry.serial));
915
+ return {
916
+ error: reasons.map((reason) => reason.error).join(" "),
917
+ remedy: [...new Set(reasons.map((reason) => reason.remedy))].join(" ")
918
+ };
919
+ }
920
+ return resolvePhysicalDevice(null, adb, isEmulator);
921
+ }
922
+ function resolvePhysicalDevice(requested, adb, isEmulator = probeEmulatorSerial) {
923
+ const physical = adb?.physical ?? [];
924
+ const unhealthy = adb?.unhealthy ?? [];
925
+ if (requested) {
926
+ if (physical.some((p) => p.serial === requested)) return isEmulator(requested) ? emulatorRefusal(requested) : { serial: requested };
927
+ if (/^emulator-\d+$/.test(requested)) return emulatorRefusal(requested);
928
+ const unreachable = unhealthy.find((u) => u.serial === requested);
929
+ if (unreachable) return unreachableDeviceRefusal(unreachable);
930
+ const connected = physical.map((p) => p.serial);
931
+ return {
932
+ 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.`,
933
+ remedy: "Check the cable and `adb devices`, then retry with a serial adb lists."
934
+ };
935
+ }
936
+ if (physical.length === 1) {
937
+ const only = physical[0].serial;
938
+ return isEmulator(only) ? emulatorRefusal(only) : { serial: only };
939
+ }
940
+ if (physical.length > 1) return {
941
+ error: `Several physical devices are connected: ${physical.map((p) => p.serial).join(", ")}.`,
942
+ remedy: "Name the one to build for with `stim android --device <serial>`."
943
+ };
944
+ const unreachable = unhealthy.find((u) => u.kind === "physical");
945
+ if (unreachable) return unreachableDeviceRefusal(unreachable);
946
+ return {
947
+ error: "No physical Android device is connected.",
948
+ remedy: "Plug the device in, accept the USB debugging prompt, then check `adb devices`."
949
+ };
950
+ }
951
+ function nextConsolePort(claimedPorts) {
952
+ if (claimedPorts.length === 0) return 5554;
953
+ return Math.max(...claimedPorts) + 2;
954
+ }
955
+ function headlessEmulatorArgs(env = process.env, platform = process.platform) {
956
+ const args = [];
957
+ 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");
958
+ return args;
959
+ }
960
+ function parseAvdRootIni(contents) {
961
+ let path = null;
962
+ let relativePath = null;
963
+ for (const rawLine of contents.split(/\r?\n/)) {
964
+ const line = rawLine.trim();
965
+ if (!line || line.startsWith("#") || line.startsWith(";")) continue;
966
+ const separator = line.indexOf("=");
967
+ if (separator < 0) continue;
968
+ const key = line.slice(0, separator).trim();
969
+ const value = line.slice(separator + 1).trim();
970
+ if (!value) continue;
971
+ if (key === "path") path = value;
972
+ if (key === "path.rel") relativePath = value;
973
+ }
974
+ return {
975
+ path,
976
+ relativePath
977
+ };
978
+ }
979
+ function avdIniPaths(root, ini) {
980
+ const parsed = parseAvdRootIni(ini);
981
+ return [parsed.relativePath && !isAbsolute$1(parsed.relativePath) ? resolve$1(dirname$1(root), parsed.relativePath) : null, parsed.path && isAbsolute$1(parsed.path) ? parsed.path : null].filter((candidate) => candidate !== null);
982
+ }
983
+ function avdStorageRoots() {
984
+ const env = process.env;
985
+ return [...new Set([
986
+ env.ANDROID_AVD_HOME,
987
+ env.ANDROID_EMULATOR_HOME ? join$1(env.ANDROID_EMULATOR_HOME, "avd") : null,
988
+ env.ANDROID_USER_HOME ? join$1(env.ANDROID_USER_HOME, "avd") : null,
989
+ env.ANDROID_SDK_HOME ? join$1(env.ANDROID_SDK_HOME, ".android", "avd") : null,
990
+ env.ANDROID_SDK_HOME ? join$1(env.ANDROID_SDK_HOME, "avd") : null,
991
+ join$1(homedir(), ".android", "avd")
992
+ ].filter((value) => Boolean(value)))];
993
+ }
994
+ function avdPathExists(path) {
995
+ try {
996
+ lstatSync$1(path);
997
+ return true;
998
+ } catch (error) {
999
+ if (error.code === "ENOENT") return false;
1000
+ throw error;
1001
+ }
1002
+ }
1003
+ function listOrphanedAvdDirectories(avdName) {
1004
+ const roots = avdStorageRoots();
1005
+ const stores = roots.filter(avdPathExists).map((root) => ({
1006
+ root,
1007
+ canonicalRoot: realpathSync$1(root),
1008
+ names: readdirSync$1(root)
1009
+ }));
1010
+ const registered = /* @__PURE__ */ new Set();
1011
+ for (const { root, canonicalRoot, names } of stores) for (const name of names.filter((entry) => entry.endsWith(".ini"))) {
1012
+ const path = join$1(root, name);
1013
+ const ini = readFileSync$1(path, "utf8");
1014
+ const targets = /* @__PURE__ */ new Set([...avdIniPaths(root, ini), ...avdIniPaths(canonicalRoot, ini)]);
1015
+ if (!targets.size) throw new Error(`Cannot verify AVD registration at ${path}; its data was kept.`);
1016
+ for (const target of targets) try {
1017
+ registered.add(realpathSync$1(target));
1018
+ } catch (error) {
1019
+ const code = error.code;
1020
+ if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
1021
+ }
1022
+ }
1023
+ const orphaned = [];
1024
+ for (const { canonicalRoot, names: entries } of stores) {
1025
+ const names = avdName ? [`${avdName}.avd`] : entries;
1026
+ for (const entry of names) {
1027
+ if (!/^stim-[A-Za-z0-9._-]+\.avd$/.test(entry)) continue;
1028
+ const name = entry.slice(0, -4);
1029
+ if (roots.some((candidate) => avdPathExists(join$1(candidate, `${name}.ini`)))) continue;
1030
+ const directory = join$1(canonicalRoot, entry);
1031
+ if (registered.has(directory) || !avdPathExists(directory)) continue;
1032
+ const stat = lstatSync$1(directory);
1033
+ if (!stat.isDirectory() || stat.isSymbolicLink() || realpathSync$1(directory) !== directory) throw new Error(`Cannot verify AVD data at ${directory}; it was kept.`);
1034
+ if (!orphaned.some((candidate) => candidate.directory === directory)) orphaned.push({
1035
+ name,
1036
+ directory,
1037
+ dev: stat.dev,
1038
+ ino: stat.ino
1039
+ });
1040
+ }
1041
+ }
1042
+ return orphaned;
1043
+ }
1044
+ function ownedAvdDirectory(avdName, { env = process.env, home = homedir(), readFile = (path) => readFileSync$1(path, "utf8"), realpath = realpathSync$1, isDirectory = (path) => statSync$1(path).isDirectory() } = {}) {
1045
+ if (!/^stim-[A-Za-z0-9._-]+$/.test(avdName)) return null;
1046
+ const roots = [
1047
+ env.ANDROID_AVD_HOME,
1048
+ env.ANDROID_SDK_HOME ? join$1(env.ANDROID_SDK_HOME, "avd") : null,
1049
+ join$1(home, ".android", "avd")
1050
+ ];
1051
+ for (const root of new Set(roots.filter((value) => Boolean(value)))) {
1052
+ let ini;
1053
+ try {
1054
+ ini = readFile(join$1(root, `${avdName}.ini`));
1055
+ } catch (error) {
1056
+ const code = error.code;
1057
+ if (code === "ENOENT" || code === "ENOTDIR") continue;
1058
+ return null;
1059
+ }
1060
+ for (const candidate of avdIniPaths(root, ini)) try {
1061
+ const canonical = realpath(candidate);
1062
+ if (isDirectory(canonical)) return canonical;
1063
+ } catch {}
1064
+ return null;
1065
+ }
1066
+ return null;
1067
+ }
1068
+ function withAvdConfigOverrides(contents, overrides) {
1069
+ const newline = contents.includes("\r\n") ? "\r\n" : "\n";
1070
+ const trailingNewline = /\r?\n$/.test(contents);
1071
+ const lines = contents.split(/\r?\n/);
1072
+ if (trailingNewline) lines.pop();
1073
+ const updated = [];
1074
+ const remaining = new Map(Object.entries(overrides));
1075
+ for (const line of lines) {
1076
+ const separator = line.indexOf("=");
1077
+ const key = separator >= 0 ? line.slice(0, separator).trim() : "";
1078
+ if (!remaining.has(key)) {
1079
+ if (!Object.hasOwn(overrides, key)) updated.push(line);
1080
+ continue;
1081
+ }
1082
+ updated.push(`${key}=${remaining.get(key)}`);
1083
+ remaining.delete(key);
1084
+ }
1085
+ for (const [key, value] of remaining) updated.push(`${key}=${value}`);
1086
+ return updated.join(newline) + (trailingNewline ? newline : "");
1087
+ }
1088
+ let avdConfigWriteSequence = 0;
1089
+ function configureNewOwnedAvd(avdName, { dataPartitionSizeGb, avdConfig = {} }, { avdDirectory = ownedAvdDirectory, readFile = (path) => readFileSync$1(path, "utf8"), writeFile = (path, contents) => writeFileSync$1(path, contents, {
1090
+ encoding: "utf8",
1091
+ flag: "wx"
1092
+ }), rename = renameSync$1, remove = (path) => rmSync$1(path, { force: true }) } = {}) {
1093
+ const sizeBytes = androidDataPartitionSizeBytes(dataPartitionSizeGb);
1094
+ const directory = avdDirectory(avdName);
1095
+ if (!directory) throw new Error(`Could not resolve the content directory for newly created AVD ${avdName}.`);
1096
+ const configPath = join$1(directory, "config.ini");
1097
+ const original = readFile(configPath);
1098
+ const expected = {
1099
+ ...avdConfig,
1100
+ "disk.dataPartition.size": String(sizeBytes)
1101
+ };
1102
+ const updated = withAvdConfigOverrides(original, expected);
1103
+ const tempPath = join$1(directory, `.config.ini.stim-${process.pid}-${++avdConfigWriteSequence}.tmp`);
1104
+ try {
1105
+ writeFile(tempPath, updated);
1106
+ rename(tempPath, configPath);
1107
+ } catch (error) {
1108
+ try {
1109
+ remove(tempPath);
1110
+ } catch {}
1111
+ throw error;
1112
+ }
1113
+ const verified = readFile(configPath);
1114
+ 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}.`);
1115
+ return configPath;
1116
+ }
1117
+ function avdPoolConfiguration(dataPartitionSizeGb, avdConfig) {
1118
+ return JSON.stringify(Object.entries({
1119
+ ...avdConfig,
1120
+ "hw.device.name": DEFAULT_AVD_DEVICE,
1121
+ "disk.dataPartition.size": String(androidDataPartitionSizeBytes(dataPartitionSizeGb))
1122
+ }).toSorted(([a], [b]) => a.localeCompare(b)));
1123
+ }
1124
+ function ownedAvdMatchesConfiguration(avdName, configuration) {
1125
+ const directory = ownedAvdDirectory(avdName);
1126
+ if (!directory) return false;
1127
+ const contents = readFileSync$1(join$1(directory, "config.ini"), "utf8");
1128
+ const expected = JSON.parse(configuration);
1129
+ 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]}`));
1130
+ }
1131
+ async function resetAdoptedAvd(avdName, serial, keepPackage) {
1132
+ const exec = getExecutor();
1133
+ let recoveryDeadline;
1134
+ let firstFailure;
1135
+ let unavailableTarget = false;
1136
+ const commandTimeout = (timeoutMs) => recoveryDeadline === void 0 ? timeoutMs : Math.max(1, Math.min(timeoutMs, recoveryDeadline - Date.now()));
1137
+ const assertTarget = () => {
1138
+ const resolved = resolveOwnedAvdSerial(avdName, { timeoutMs: commandTimeout(3e4) });
1139
+ unavailableTarget = Boolean(resolved.notRunning);
1140
+ if (resolved.serial !== serial) throw new Error(`AVD ${avdName} is no longer running on ${serial}.`);
1141
+ };
1142
+ for (;;) {
1143
+ unavailableTarget = false;
1144
+ try {
1145
+ assertTarget();
1146
+ const packages = exec.runFile("adb", [
1147
+ "-s",
1148
+ serial,
1149
+ "shell",
1150
+ "pm",
1151
+ "list",
1152
+ "packages",
1153
+ "-3"
1154
+ ], { timeoutMs: commandTimeout(3e4) }).split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => {
1155
+ const match = /^package:([A-Za-z0-9_]+(?:\.[A-Za-z0-9_]+)+)$/.exec(line);
1156
+ if (!match) throw new Error(`Could not read installed apps on ${avdName}: ${line}`);
1157
+ return match[1];
1158
+ });
1159
+ for (const packageName of packages) {
1160
+ assertTarget();
1161
+ const args = packageName === keepPackage ? [
1162
+ "shell",
1163
+ "pm",
1164
+ "clear",
1165
+ packageName
1166
+ ] : ["uninstall", packageName];
1167
+ const result = exec.runFile("adb", [
1168
+ "-s",
1169
+ serial,
1170
+ ...args
1171
+ ], { timeoutMs: commandTimeout(12e4) });
1172
+ if (result.trim() !== "Success") throw new Error(`Could not clean ${packageName} on ${avdName}: ${result.trim()}`);
1173
+ }
1174
+ return;
1175
+ } catch (error) {
1176
+ const failed = error;
1177
+ const detail = [
1178
+ failed?.message || String(error),
1179
+ failed?.stderr,
1180
+ failed?.stdout
1181
+ ].filter(Boolean).map(String).join("\n");
1182
+ firstFailure ??= detail;
1183
+ const diagnostic = detail === firstFailure ? detail : `${firstFailure}\nRecovery failed: ${detail}`;
1184
+ const transient = /(?:^|\n)(?:adb: )?(?:error: )?(?:device offline|closed)\s*(?:$|\n)/i.test(detail);
1185
+ recoveryDeadline ??= Date.now() + 3e4;
1186
+ if (!unavailableTarget && !transient || Date.now() >= recoveryDeadline) throw new Error(diagnostic, { cause: error });
1187
+ const delayMs = Math.min(1e3, recoveryDeadline - Date.now());
1188
+ await new Promise((done) => setTimeout(done, delayMs));
1189
+ const ready = await waitForBoot(serial, recoveryDeadline - Date.now(), { commandTimeoutMs: 5e3 });
1190
+ if (!ready.ok) throw new Error(`${diagnostic}\nEmulator did not recover: ${JSON.stringify(ready.diagnostic)}`, { cause: error });
1191
+ }
1192
+ }
1193
+ }
1194
+ function bootAndroidEmulator(avdName, consolePort, { logFile } = {}) {
1195
+ const child = getExecutor().spawn(androidToolPath("emulator"), [
1196
+ "-avd",
1197
+ avdName,
1198
+ "-port",
1199
+ String(consolePort),
1200
+ ...headlessEmulatorArgs()
1201
+ ], {
1202
+ detached: true,
1203
+ stdio: emulatorStdio(logFile)
1204
+ });
1205
+ child?.unref?.();
1206
+ return child?.pid ?? null;
1207
+ }
1208
+ function emulatorStdio(logFile) {
1209
+ if (!logFile) return "ignore";
1210
+ try {
1211
+ mkdirSync$1(dirname$1(logFile), { recursive: true });
1212
+ const fd = openSync$1(logFile, "w");
1213
+ return [
1214
+ "ignore",
1215
+ fd,
1216
+ fd
1217
+ ];
1218
+ } catch {
1219
+ return "ignore";
1220
+ }
1221
+ }
1222
+ const EMULATOR_PIPE_SEVERITY = /^(?:\S+:\s+)?(FATAL|ERROR)\s*\|\s*\S/;
1223
+ const EMULATOR_PANIC = /^(?:\S+:\s+)?PANIC:\s*\S/;
1224
+ function extractEmulatorFailure(text) {
1225
+ if (typeof text !== "string" || text.trim() === "") return [];
1226
+ const matched = [];
1227
+ const lines = text.split("\n");
1228
+ for (let i = 0; i < lines.length; i++) {
1229
+ const line = String(lines[i] ?? "").replace(/\r/g, "").trim();
1230
+ if (!line) continue;
1231
+ const pipe = EMULATOR_PIPE_SEVERITY.exec(line);
1232
+ if (pipe) {
1233
+ matched.push({
1234
+ line,
1235
+ fatal: pipe[1] !== "ERROR",
1236
+ index: i
1237
+ });
1238
+ continue;
1239
+ }
1240
+ if (EMULATOR_PANIC.test(line)) matched.push({
1241
+ line,
1242
+ fatal: true,
1243
+ index: i
1244
+ });
1245
+ }
1246
+ const fatal = matched.filter((m) => m.fatal);
1247
+ const pool = fatal.length > 0 ? fatal : matched;
1248
+ const byText = /* @__PURE__ */ new Map();
1249
+ for (const entry of pool) byText.set(entry.line.replace(/\s+/g, " ").toLowerCase(), entry);
1250
+ return [...byText.values()].toSorted((a, b) => a.index - b.index).slice(-3).map((entry) => entry.line);
1251
+ }
1252
+ function emulatorDiskSpaceRemedy(lines) {
1253
+ const text = (Array.isArray(lines) ? lines : []).join("\n");
1254
+ if (!/ENOSPC|not enough space|no space left|disk (?:is )?full/i.test(text)) return null;
1255
+ return "Free disk space (owned AVDs normally live under ~/.android/avd and can use several GB), then run `stim android` again.";
1256
+ }
1257
+ function emulatorFailureRemedy(lines) {
1258
+ return emulatorDiskSpaceRemedy(lines) ?? "Fix what the emulator reported above, then run `stim android` again.";
1259
+ }
1260
+ function getprop(exec, serial, prop, timeoutMs) {
1261
+ const out = exec.runQuiet(`${androidTool("adb")} -s ${serial} shell getprop ${prop}`, { timeoutMs });
1262
+ return typeof out === "string" ? out.trim() : "";
1263
+ }
1264
+ async function waitForBoot(serial, timeoutMs = 6e4, { aborted = () => false, pollMs = 1e3, commandTimeoutMs } = {}) {
1265
+ const exec = getExecutor();
1266
+ const start = Date.now();
1267
+ let exited = false;
1268
+ const probeTimeout = () => commandTimeoutMs === void 0 ? void 0 : Math.max(1, Math.min(commandTimeoutMs, timeoutMs - (Date.now() - start)));
1269
+ while (Date.now() - start < timeoutMs) {
1270
+ if (getprop(exec, serial, "sys.boot_completed", probeTimeout()) === "1") return { ok: true };
1271
+ if (getprop(exec, serial, "dev.bootcomplete", probeTimeout()) === "1") return { ok: true };
1272
+ if (aborted()) {
1273
+ exited = true;
1274
+ break;
1275
+ }
1276
+ await new Promise((r) => setTimeout(r, commandTimeoutMs === void 0 ? pollMs : Math.min(pollMs, Math.max(0, timeoutMs - (Date.now() - start)))));
1277
+ }
1278
+ const devicesOut = exec.runQuiet(`${androidTool("adb")} devices`, { timeoutMs: probeTimeout() });
1279
+ return {
1280
+ ok: false,
1281
+ ...exited ? { exited: true } : {},
1282
+ diagnostic: {
1283
+ devices: typeof devicesOut === "string" ? devicesOut.trim() : "",
1284
+ sysBoot: getprop(exec, serial, "sys.boot_completed", probeTimeout()),
1285
+ devBoot: getprop(exec, serial, "dev.bootcomplete", probeTimeout()),
1286
+ bootAnim: getprop(exec, serial, "init.svc.bootanim", probeTimeout())
1287
+ }
1288
+ };
1289
+ }
1290
+ const ANDROID_EMULATOR_SHUTDOWN_TIMEOUT_MS = 6e4;
1291
+ const ANDROID_EMULATOR_SHUTDOWN_POLL_MS = 100;
1292
+ function shutdownAndroidEmulator(serial, timeoutMs = ANDROID_EMULATOR_SHUTDOWN_TIMEOUT_MS) {
1293
+ const exec = getExecutor();
1294
+ const started = Date.now();
1295
+ 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`);
1296
+ exec.runQuiet(`${androidTool("adb")} -s ${serial} emu kill`, { timeoutMs: Math.max(1, timeoutMs - (Date.now() - started)) });
1297
+ }
1298
+ function sleepSync(ms) {
1299
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
1300
+ }
1301
+ function avdProcessLockPaths(avdDirectory, platform) {
1302
+ return ["hardware-qemu.ini.lock", "userdata-qemu.img.lock"].map((name) => join$1(avdDirectory, name, ...platform === "win32" ? ["pid"] : []));
1303
+ }
1304
+ function readAvdProcessId(path) {
1305
+ try {
1306
+ const pid = Number(readFileSync$1(path, "utf8").split("\0")[0]);
1307
+ if (!Number.isSafeInteger(pid) || pid <= 0) throw new Error(`Could not read the emulator PID from process lock ${path}.`);
1308
+ return pid;
1309
+ } catch (error) {
1310
+ const code = error.code;
1311
+ if (code === "ENOENT" || code === "ENOTDIR") return null;
1312
+ throw error;
1313
+ }
1314
+ }
1315
+ function resolveAvdProcess(avdName, { platform = process.platform, resolveDirectory = ownedAvdDirectory, readProcessId = readAvdProcessId } = {}) {
1316
+ const directory = resolveDirectory(avdName);
1317
+ if (!directory) throw new Error(`Could not resolve the content directory for owned AVD ${avdName}.`);
1318
+ for (const lockPath of avdProcessLockPaths(directory, platform)) {
1319
+ const processId = readProcessId(lockPath);
1320
+ if (processId !== null) return {
1321
+ directory,
1322
+ processId
1323
+ };
1324
+ }
1325
+ return {
1326
+ directory,
1327
+ processId: null
1328
+ };
1329
+ }
1330
+ function assertOwnedAvdStopped(avdName, { processAlive = pidExists, ...resolveOptions } = {}) {
1331
+ const { processId } = resolveAvdProcess(avdName, resolveOptions);
1332
+ if (processId !== null && processAlive(processId)) throw new Error(`Owned AVD ${avdName} still has a live emulator process (${processId}).`);
1333
+ }
1334
+ 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$1(path).isDirectory(), now = Date.now, sleep = sleepSync } = {}) {
1335
+ const { directory, processId } = resolveAvdProcess(avdName, {
1336
+ platform,
1337
+ resolveDirectory,
1338
+ readProcessId
1339
+ });
1340
+ if (processId === null) throw new Error(`Could not find the emulator process lock for owned AVD ${avdName}.`);
1341
+ const deadline = now() + timeoutMs;
1342
+ const shutdownTimeoutMs = deadline - now();
1343
+ if (shutdownTimeoutMs <= 0) throw new Error(`Owned AVD ${avdName} did not finish shutting down within ${Math.ceil(timeoutMs / 1e3)}s.`);
1344
+ shutdown(shutdownTimeoutMs);
1345
+ while (processAlive(processId)) {
1346
+ const remaining = deadline - now();
1347
+ if (remaining <= 0) throw new Error(`Owned AVD ${avdName} did not finish shutting down within ${Math.ceil(timeoutMs / 1e3)}s.`);
1348
+ sleep(Math.min(pollMs, remaining));
1349
+ }
1350
+ if (!directoryExists(directory)) throw new Error(`Could not verify the content directory for owned AVD ${avdName} after shutdown.`);
1351
+ }
1352
+ function getAvdNameForSerial(serial, { timeoutMs } = {}) {
1353
+ const out = getExecutor().runQuiet(`${androidTool("adb")} -s ${serial} emu avd name`, { timeoutMs });
1354
+ if (!out) return null;
1355
+ return out.split("\n")[0]?.trim() || null;
1356
+ }
1357
+ function resolveOwnedAvdSerial(avdName, { timeoutMs } = {}) {
1358
+ const started = Date.now();
1359
+ const remaining = () => ({ timeoutMs: timeoutMs === void 0 ? void 0 : Math.max(1, timeoutMs - (Date.now() - started)) });
1360
+ if (!listAvds(remaining()).includes(avdName)) return { missing: true };
1361
+ if (!avdName?.startsWith("stim-")) return { notOwned: true };
1362
+ const adb = listAdbDevices(remaining());
1363
+ const match = [...adb.emulators, ...adb.unhealthy.filter((entry) => entry.kind === "emulator" && entry.consolePort !== void 0)].find((e) => getAvdNameForSerial(e.serial, remaining()) === avdName);
1364
+ if (match) return { serial: match.serial };
1365
+ return { notRunning: true };
1366
+ }
1367
+ //#endregion
1368
+ export { providerDownloadPath as $, pickDefaultSystemImage as A, listBuildLocks as B, memoizeEmulatorProbe as C, ownedAvdName as D, ownedAvdMatchesConfiguration as E, shutdownAndroidEmulator as F, buildCacheKey as G, segment as H, waitForAndroidEmulatorShutdown as I, filesystemBuildCapability as J, describeFingerprintMiss as K, waitForBoot as L, resetAdoptedAvd as M, resolveOwnedAvdSerial as N, ownedAvdSystemImage as O, resolvePhysicalDevice as P, prepareProviderDownloadDir as Q, WAIT_CEILING_MS as R, listOrphanedAvdDirectories as S, ownedAvdDirectory as T, takeoverLine as U, releaseBuildLock as V, waitForBuild as W, fingerprintDiffSuffix as X, fingerprintDiffRecord as Y, fingerprintProject as Z, findBuildTool as _, androidSystemImageAbi as a, untrackedMissLine as at, listAvds as b, avdPoolConfiguration as c, captureAssetManifest as ct, configureNewOwnedAvd as d, providerUploadOutcome as et, createOwnedAvd as f, extractEmulatorFailure as g, emulatorFailureRemedy as h, androidPoolNoCandidatesRefusal as i, storedAssetManifest as it, probeEmulatorSerial as j, physicalDeviceModel as k, avdStorageRoots as l, compareAssetManifests as lt, emulatorDiskSpaceRemedy as m, androidHome as n, resolveBuild as nt, assertOwnedAvdStopped as o, untrackedNativeFiles as ot, deleteAvd as p, diffFingerprintSources as q, androidPoolCandidates as r, storeBuild as rt, avdPathExists as s, assetDiffReason as st, androidDeviceAbi as t, refingerprintAfterMutation as tt, bootAndroidEmulator as u, readAssetManifest as ut, getAvdNameForSerial as v, nextConsolePort as w, listInstalledSystemImages as x, listAdbDevices as y, acquireBuildLock as z };