stim 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{android-DJWiTBkz.mjs → android-DWssqHQ8.mjs} +165 -661
- package/dist/{android-sobvO9ia.mjs → android-Da-G_o3n.mjs} +646 -548
- package/dist/{android-BW8i_YxU.mjs → android-DeYdjjBP.mjs} +6 -2
- package/dist/{android-cas-CSS8gjR7.mjs → android-cas-SWIjzeuL.mjs} +54 -18
- package/dist/android-cas-compiler.mjs +1 -1
- package/dist/{app-install--PAocWvq.mjs → app-install-DJg7bVv-.mjs} +36 -10
- package/dist/build-lock-CgtsB6TV.mjs +604 -0
- package/dist/{build-slots-CuPszJDu.mjs → build-slots-C-oAHRT1.mjs} +2 -2
- package/dist/{cache-manifest-uoOKhgUD.mjs → cache-manifest-CYJi8-Hx.mjs} +2 -2
- package/dist/cache-manifest.d.mts +1 -1
- package/dist/cache-manifest.mjs +1 -1
- package/dist/cli.mjs +14 -14
- package/dist/collector-run.d.mts +2 -0
- package/dist/collector-run.mjs +7 -7
- package/dist/{command-output-BuIb_ZOI.mjs → command-output-B3ycpsYv.mjs} +2 -2
- package/dist/{config-DMP7L7H6.mjs → config-B7iyKWkL.mjs} +56 -16
- package/dist/{dependency-state-CTL8A9p3.mjs → dependency-state-D0M7oKA7.mjs} +2 -2
- package/dist/{deps-BAGOnrc7.mjs → deps-oCXVNEqQ.mjs} +6 -6
- package/dist/detached-entry-Bpo6ZY_I.mjs +65 -0
- package/dist/{dev-client-BoifdqkU.mjs → dev-client-DDUkWgJL.mjs} +12 -12
- package/dist/{device-BUw4JBuH.mjs → device-CBSaC6BU.mjs} +7 -7
- package/dist/{device-DvKJ454A.mjs → device-CLeDp-KH.mjs} +17 -15
- package/dist/{device-lease-r7yEQ1R8.mjs → device-lease-Ffz_M5t0.mjs} +3 -3
- package/dist/{device-pool-DU4FQKp5.mjs → device-pool-DNSHL5z8.mjs} +4 -4
- package/dist/{device-remote-BTUqbl0i.mjs → device-remote-BprxZp-m.mjs} +11 -11
- package/dist/{doctor-Di_B26uh.mjs → doctor-CTAx9gMu.mjs} +90 -46
- package/dist/{error-diagnostics-CTePWAzW.mjs → error-diagnostics-D1Kaf-eT.mjs} +23 -17
- package/dist/{exec-CyylIdq9.mjs → exec-B29XNuhP.mjs} +14 -2
- package/dist/{gc-C20dWw_8.mjs → gc-CX0KRfLG.mjs} +17 -16
- package/dist/{guide-sAnoty2y.mjs → guide-CPmAG9me.mjs} +61 -22
- package/dist/{guide-status-BF6RsJJp.mjs → guide-status-Craq9z0t.mjs} +3 -3
- package/dist/{ios-BdpkncL6.mjs → ios-BRXYSxSc.mjs} +34 -37
- package/dist/{ios-device-DJOi9FR5.mjs → ios-device-CDbvn5H2.mjs} +2 -2
- package/dist/{ios-device-D3pzTKQE.mjs → ios-device-CFXA46ez.mjs} +1 -1
- package/dist/{ios-Deh5js5V.mjs → ios-lEJ5pKsq.mjs} +3 -3
- package/dist/{logs-BfQdIhiF.mjs → logs-CakdBTtm.mjs} +6 -6
- package/dist/{logs-query-Cdp5aaCH.mjs → logs-query-BGL-Pl12.mjs} +1 -1
- package/dist/metro-C7bcJ0uo.mjs +233 -0
- package/dist/{named-ports-DH9SXd0f.mjs → named-ports-DzMOlmaX.mjs} +57 -42
- package/dist/{ownership-CD_XnpDu.mjs → ownership-Cp52QAK1.mjs} +3 -3
- package/dist/{ownership-CVY9cqCr.mjs → ownership-D5vkdm8H.mjs} +13 -13
- package/dist/{ports-A6uTJSPj.mjs → ports-BIbJaKNW.mjs} +3 -3
- package/dist/{process-output-C7IlIzd7.mjs → process-output-Dl5uKE03.mjs} +1 -1
- package/dist/{project-B_SDt4ip.mjs → project-DDZWf_16.mjs} +3 -3
- package/dist/{reclaim-C_rB25f4.mjs → reclaim-D614jI6_.mjs} +11 -11
- package/dist/{reload-CYBJbJfI.mjs → reload-ChW46VDb.mjs} +9 -9
- package/dist/{server-bare-H3pAhbMs.mjs → server-bare-BQkyxa_b.mjs} +1 -1
- package/dist/{server-expo-Ds8_mfng.mjs → server-expo-BVAA1OqJ.mjs} +23 -11
- package/dist/server-expo-YDDG3eP7.mjs +2 -0
- package/dist/{settings-kO2ZqIKy.mjs → settings-BXXorVM2.mjs} +8 -5
- package/dist/{slot-launch-BpHiHH6A.mjs → slot-launch-DbLvyrup.mjs} +4 -4
- package/dist/{start-DiEfqEWk.mjs → start-TTVILlUi.mjs} +111 -38
- package/dist/{state-CnWzvCWP.mjs → state-DZobzxLW.mjs} +2 -2
- package/dist/{stats-Fo9ACIv7.mjs → stats-BYcZ6n1h.mjs} +1 -1
- package/dist/{stats-DHrO0frH.mjs → stats-CLg5RuDS.mjs} +4 -4
- package/dist/{status-J5LAXpag.mjs → status-DUNy_HZs.mjs} +14 -15
- package/dist/{stop-CM1fejKW.mjs → stop-CnUpnFT5.mjs} +2 -2
- package/dist/{stop-0jJNahwN.mjs → stop-ZrfODZ51.mjs} +13 -13
- package/dist/supervisor-run.mjs +7 -5
- package/dist/{teardown-CAVcWeRD.mjs → teardown-5J5UXz2P.mjs} +14 -17
- package/dist/{workspace-process-lock-Bz19_R9R.mjs → workspace-process-lock-BYfZz_S-.mjs} +1 -1
- package/dist/{workspace-state-WVwWw_hB.mjs → workspace-state-BT5E3SA2.mjs} +3 -3
- package/dist/{worktree-CeUKDAQw.mjs → worktree-BfE4lzHK.mjs} +21 -16
- package/dist/{worktree-DJnBbQZ9.mjs → worktree-FG_ZKKdb.mjs} +13 -9
- package/dist/{xcode-BqDeRkpT.mjs → xcode-B6YaWeg0.mjs} +92 -17
- package/package.json +12 -8
- package/shim/android-optimizations.gradle +6 -0
- package/dist/metro-ato6ef3J.mjs +0 -99
- package/dist/ports-BZprTJRa.mjs +0 -90
- package/dist/server-expo-CSwUVYOa.mjs +0 -2
|
@@ -1,610 +1,10 @@
|
|
|
1
|
-
import { t as getExecutor } from "./exec-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
9
|
-
import { createHash } from "node:crypto";
|
|
10
|
-
import { join } from "node:path";
|
|
11
|
-
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, writeFileSync as writeFileSync$1 } from "fs";
|
|
12
|
-
import { homedir } from "os";
|
|
13
|
-
import { dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1, resolve as resolve$1 } from "path";
|
|
14
|
-
import { buildCacheKey, resolveArtifact, storeArtifact } from "@stim-cli/core";
|
|
15
|
-
import * as expoFingerprint from "@expo/fingerprint";
|
|
16
|
-
import { buildUploadTimeoutMs } from "@stim-cli/cache";
|
|
17
|
-
const ASSET_MANIFEST_FILE = "assets-manifest.json";
|
|
18
|
-
const MAX_DEPTH = 4;
|
|
19
|
-
function hashFile(file) {
|
|
20
|
-
try {
|
|
21
|
-
return createHash("sha256").update(readFileSync(file)).digest("hex");
|
|
22
|
-
} catch {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
function readAssetManifest(root) {
|
|
27
|
-
if (!existsSync(root)) return null;
|
|
28
|
-
const assets = [];
|
|
29
|
-
let broken = false;
|
|
30
|
-
const walk = (abs, rel, depth) => {
|
|
31
|
-
if (depth > MAX_DEPTH || broken) return;
|
|
32
|
-
let names;
|
|
33
|
-
try {
|
|
34
|
-
names = readdirSync(abs);
|
|
35
|
-
} catch {
|
|
36
|
-
broken = true;
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
for (const name of names) {
|
|
40
|
-
const child = join(abs, name);
|
|
41
|
-
let stat;
|
|
42
|
-
try {
|
|
43
|
-
stat = statSync(child);
|
|
44
|
-
} catch {
|
|
45
|
-
broken = true;
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
const path = rel === "" ? name : `${rel}/${name}`;
|
|
49
|
-
if (stat.isDirectory()) {
|
|
50
|
-
walk(child, path, depth + 1);
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
if (!stat.isFile()) continue;
|
|
54
|
-
const sha256 = hashFile(child);
|
|
55
|
-
if (sha256 === null) {
|
|
56
|
-
broken = true;
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
assets.push({
|
|
60
|
-
path,
|
|
61
|
-
sha256
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
walk(root, "", 0);
|
|
66
|
-
if (broken) return null;
|
|
67
|
-
assets.sort((a, b) => a.path.localeCompare(b.path));
|
|
68
|
-
return {
|
|
69
|
-
version: 1,
|
|
70
|
-
assets
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
function parseAssetManifest(text) {
|
|
74
|
-
let parsed;
|
|
75
|
-
try {
|
|
76
|
-
parsed = JSON.parse(String(text));
|
|
77
|
-
} catch {
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
const obj = parsed;
|
|
81
|
-
if (!obj || obj.version !== 1 || !Array.isArray(obj.assets)) return null;
|
|
82
|
-
const assets = [];
|
|
83
|
-
for (const raw of obj.assets) {
|
|
84
|
-
const entry = raw;
|
|
85
|
-
if (typeof entry?.path !== "string" || typeof entry?.sha256 !== "string") return null;
|
|
86
|
-
assets.push({
|
|
87
|
-
path: entry.path,
|
|
88
|
-
sha256: entry.sha256
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
return {
|
|
92
|
-
version: 1,
|
|
93
|
-
assets
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
function pickGeneratedResDir(names, variant, reactChildren = []) {
|
|
97
|
-
const wanted = (variant ?? "").trim().toLowerCase();
|
|
98
|
-
const modern = names.filter((n) => /^createBundle.+JsAndAssets$/.test(n)).toSorted();
|
|
99
|
-
if (wanted) {
|
|
100
|
-
const match = modern.find((n) => n.toLowerCase() === `createbundle${wanted}jsandassets`);
|
|
101
|
-
if (match) return match;
|
|
102
|
-
} else if (modern.length === 1) return modern[0];
|
|
103
|
-
if (!names.includes("react")) return null;
|
|
104
|
-
const children = reactChildren.toSorted();
|
|
105
|
-
if (wanted) {
|
|
106
|
-
const match = children.find((n) => n.toLowerCase() === wanted);
|
|
107
|
-
if (match) return `react/${match}`;
|
|
108
|
-
} else if (children.length === 1) return `react/${children[0]}`;
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
function androidModuleDirs(root, { list = safeList$1 } = {}) {
|
|
112
|
-
const android = join(root, "android");
|
|
113
|
-
return ["app", ...list(android).filter((name) => name !== "app" && existsSync(join(android, name, "build", "generated", "res"))).toSorted()];
|
|
114
|
-
}
|
|
115
|
-
function findGeneratedResDir(root, variant, { list = safeList$1 } = {}) {
|
|
116
|
-
for (const module of androidModuleDirs(root, { list })) {
|
|
117
|
-
const base = join(root, "android", module, "build", "generated", "res");
|
|
118
|
-
const names = list(base);
|
|
119
|
-
if (names.length === 0) continue;
|
|
120
|
-
const picked = pickGeneratedResDir(names, variant, names.includes("react") ? list(join(base, "react")) : []);
|
|
121
|
-
if (picked) return join(base, ...picked.split("/"));
|
|
122
|
-
}
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
function captureAssetManifest(root, { variant = null, findDir = findGeneratedResDir, read = readAssetManifest } = {}) {
|
|
126
|
-
const dir = findDir(root, variant);
|
|
127
|
-
return dir ? read(dir) : null;
|
|
128
|
-
}
|
|
129
|
-
function compareAssetManifests(fresh, stored) {
|
|
130
|
-
const left = new Map(fresh.assets.map((a) => [a.path, a.sha256]));
|
|
131
|
-
const right = new Map(stored.assets.map((a) => [a.path, a.sha256]));
|
|
132
|
-
const added = [];
|
|
133
|
-
const changed = [];
|
|
134
|
-
const removed = [];
|
|
135
|
-
for (const [path, sha256] of left) if (!right.has(path)) added.push(path);
|
|
136
|
-
else if (right.get(path) !== sha256) changed.push(path);
|
|
137
|
-
for (const path of right.keys()) if (!left.has(path)) removed.push(path);
|
|
138
|
-
added.sort();
|
|
139
|
-
changed.sort();
|
|
140
|
-
removed.sort();
|
|
141
|
-
return {
|
|
142
|
-
same: added.length === 0 && changed.length === 0 && removed.length === 0,
|
|
143
|
-
added,
|
|
144
|
-
removed,
|
|
145
|
-
changed,
|
|
146
|
-
example: added[0] ?? changed[0] ?? removed[0] ?? null
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
function assetDiffReason(diff) {
|
|
150
|
-
const which = diff.added.length ? "added" : diff.changed.length ? "changed" : "removed";
|
|
151
|
-
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})`;
|
|
152
|
-
}
|
|
153
|
-
function safeList$1(dir) {
|
|
154
|
-
try {
|
|
155
|
-
return readdirSync(dir);
|
|
156
|
-
} catch {
|
|
157
|
-
return [];
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
//#endregion
|
|
161
|
-
//#region src/build-cache.ts
|
|
162
|
-
function entryDir(platform, key, root = sharedBuildCache()) {
|
|
163
|
-
return join$1(root, platform, key);
|
|
164
|
-
}
|
|
165
|
-
const FINGERPRINT_PLATFORMS = /* @__PURE__ */ new Set(["ios", "android"]);
|
|
166
|
-
/**
|
|
167
|
-
* Paths that exist in a working checkout and not in a fresh one, which
|
|
168
|
-
* @expo/fingerprint does not ignore on its own. A fingerprint that counts them
|
|
169
|
-
* cannot match across checkouts, which is the parity failure `doctor` reports.
|
|
170
|
-
*
|
|
171
|
-
* The list stays narrow on purpose: a path belongs here only when no native
|
|
172
|
-
* build on any project can read it. Anything a single project can judge --
|
|
173
|
-
* a lockfile with machine paths, a generated report -- belongs in that
|
|
174
|
-
* project's `.fingerprintignore`, which still applies on top of this.
|
|
175
|
-
*/
|
|
176
|
-
const DEFAULT_FINGERPRINT_IGNORES = ["**/android/local.properties", "**/android/.idea/**"];
|
|
177
|
-
async function fingerprintProject(projectRoot, { platform, createFingerprint = expoFingerprint.createFingerprintAsync, debug = false } = {}) {
|
|
178
|
-
const result = await createFingerprint(projectRoot, {
|
|
179
|
-
...platform && FINGERPRINT_PLATFORMS.has(platform) ? { platforms: [platform] } : void 0,
|
|
180
|
-
...debug ? { debug } : {},
|
|
181
|
-
ignorePaths: DEFAULT_FINGERPRINT_IGNORES,
|
|
182
|
-
silent: true
|
|
183
|
-
});
|
|
184
|
-
const hash = result?.hash ?? null;
|
|
185
|
-
if (!hash) return null;
|
|
186
|
-
return {
|
|
187
|
-
hash,
|
|
188
|
-
sources: Array.isArray(result?.sources) ? result.sources : []
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
function registerOnce(root) {
|
|
192
|
-
try {
|
|
193
|
-
register({
|
|
194
|
-
dir: root,
|
|
195
|
-
name: "Build cache",
|
|
196
|
-
prune: "entries",
|
|
197
|
-
entriesDepth: 2,
|
|
198
|
-
note: "built .app/.apk keyed on the native fingerprint"
|
|
199
|
-
});
|
|
200
|
-
} catch {}
|
|
201
|
-
}
|
|
202
|
-
function resolveBuild(platform, key, root = sharedBuildCache()) {
|
|
203
|
-
return resolveArtifact(entryDir(platform, key, root));
|
|
204
|
-
}
|
|
205
|
-
function storeBuild(platform, key, buildPath, rootOrOptions = {}) {
|
|
206
|
-
const options = typeof rootOrOptions === "string" ? { root: rootOrOptions } : rootOrOptions || {};
|
|
207
|
-
const root = options.root || sharedBuildCache();
|
|
208
|
-
if (!buildPath || !existsSync$1(buildPath)) throw new Error(`No build to store at ${buildPath}`);
|
|
209
|
-
registerOnce(root);
|
|
210
|
-
return storeArtifact(entryDir(platform, key, root), buildPath, {
|
|
211
|
-
runFile: getExecutor().runFile,
|
|
212
|
-
overwrite: Boolean(options.overwrite),
|
|
213
|
-
writeMetadata: (staging) => {
|
|
214
|
-
if (Array.isArray(options.sources)) try {
|
|
215
|
-
writeFileSync$1(join$1(staging, SOURCES_FILE), JSON.stringify(options.sources));
|
|
216
|
-
} catch {}
|
|
217
|
-
if (options.assetManifest) try {
|
|
218
|
-
writeFileSync$1(join$1(staging, ASSET_MANIFEST_FILE), JSON.stringify(options.assetManifest));
|
|
219
|
-
} catch {}
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
const PROVIDER_DOWNLOAD_DIR = "cache-provider";
|
|
224
|
-
function providerDownloadPath(workspacePath) {
|
|
225
|
-
return join$1(workspacePath, PROVIDER_DOWNLOAD_DIR);
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* Empties and registers the scratch directory a provider downloads into. It is
|
|
229
|
-
* created only when a provider is about to be asked, and registered so `gc`
|
|
230
|
-
* reports what an interrupted run left behind.
|
|
231
|
-
*/
|
|
232
|
-
function prepareProviderDownloadDir(dir) {
|
|
233
|
-
rmSync$1(dir, {
|
|
234
|
-
recursive: true,
|
|
235
|
-
force: true
|
|
236
|
-
});
|
|
237
|
-
mkdirSync$1(dir, { recursive: true });
|
|
238
|
-
try {
|
|
239
|
-
register({
|
|
240
|
-
dir,
|
|
241
|
-
name: "Cache provider downloads",
|
|
242
|
-
prune: "entries",
|
|
243
|
-
entriesDepth: 1,
|
|
244
|
-
note: "artifacts fetched from the project cache provider; anything left here is from an interrupted run"
|
|
245
|
-
});
|
|
246
|
-
} catch {}
|
|
247
|
-
}
|
|
248
|
-
function filesystemBuildCapability({ root, sources, assetManifest, resolve = resolveBuild, store = storeBuild } = {}) {
|
|
249
|
-
const stored = {
|
|
250
|
-
...root === void 0 ? {} : { root },
|
|
251
|
-
...sources === void 0 ? {} : { sources },
|
|
252
|
-
...assetManifest === void 0 ? {} : { assetManifest }
|
|
253
|
-
};
|
|
254
|
-
return {
|
|
255
|
-
resolve: ({ platform, key }) => root === void 0 ? resolve(platform, key) : resolve(platform, key, root),
|
|
256
|
-
store: ({ platform, key, sourcePath, overwrite }) => store(platform, key, sourcePath, {
|
|
257
|
-
...stored,
|
|
258
|
-
overwrite
|
|
259
|
-
})
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
function providerUploadOutcome(result, name) {
|
|
263
|
-
if (!result) return null;
|
|
264
|
-
const label = name || "the cache provider";
|
|
265
|
-
if (result.timedOut) return {
|
|
266
|
-
line: `${label} upload was cancelled after ${buildUploadTimeoutMs()}ms`,
|
|
267
|
-
warn: true
|
|
268
|
-
};
|
|
269
|
-
if (result.failed) return {
|
|
270
|
-
line: `${label} upload failed: ${result.failed}`,
|
|
271
|
-
warn: true
|
|
272
|
-
};
|
|
273
|
-
return {
|
|
274
|
-
line: `uploaded (${label})`,
|
|
275
|
-
warn: false
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
const SOURCES_FILE = "fingerprint-sources.json";
|
|
279
|
-
function storedSources(platform, key, root = sharedBuildCache()) {
|
|
280
|
-
try {
|
|
281
|
-
const parsed = JSON.parse(readFileSync$1(join$1(entryDir(platform, key, root), SOURCES_FILE), "utf-8"));
|
|
282
|
-
return Array.isArray(parsed) ? parsed : null;
|
|
283
|
-
} catch {
|
|
284
|
-
return null;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
function storedAssetManifest(platform, key, root = sharedBuildCache()) {
|
|
288
|
-
try {
|
|
289
|
-
return parseAssetManifest(readFileSync$1(join$1(entryDir(platform, key, root), ASSET_MANIFEST_FILE), "utf-8"));
|
|
290
|
-
} catch {
|
|
291
|
-
return null;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
function sourceName(source) {
|
|
295
|
-
const s = source;
|
|
296
|
-
if (typeof s?.filePath === "string" && s.filePath !== "") return s.filePath;
|
|
297
|
-
if (typeof s?.id === "string" && s.id !== "") return s.id;
|
|
298
|
-
return null;
|
|
299
|
-
}
|
|
300
|
-
function diffItemName(item) {
|
|
301
|
-
const o = item;
|
|
302
|
-
for (const candidate of [
|
|
303
|
-
o,
|
|
304
|
-
o?.addedSource,
|
|
305
|
-
o?.removedSource,
|
|
306
|
-
o?.afterSource,
|
|
307
|
-
o?.beforeSource
|
|
308
|
-
]) {
|
|
309
|
-
const name = sourceName(candidate);
|
|
310
|
-
if (name) return name;
|
|
311
|
-
}
|
|
312
|
-
return null;
|
|
313
|
-
}
|
|
314
|
-
function compareSourceLists(previous, current) {
|
|
315
|
-
const previousByName = /* @__PURE__ */ new Map();
|
|
316
|
-
for (const source of previous) {
|
|
317
|
-
const name = sourceName(source);
|
|
318
|
-
if (name !== null) previousByName.set(name, source.hash ?? null);
|
|
319
|
-
}
|
|
320
|
-
const changed = [];
|
|
321
|
-
const seen = /* @__PURE__ */ new Set();
|
|
322
|
-
for (const source of current) {
|
|
323
|
-
const name = sourceName(source);
|
|
324
|
-
if (name === null || seen.has(name)) continue;
|
|
325
|
-
seen.add(name);
|
|
326
|
-
const hash = source.hash;
|
|
327
|
-
if (!previousByName.has(name) || previousByName.get(name) !== (hash ?? null)) changed.push(name);
|
|
328
|
-
}
|
|
329
|
-
for (const [name] of previousByName) if (!seen.has(name)) changed.push(name);
|
|
330
|
-
return changed;
|
|
331
|
-
}
|
|
332
|
-
function diffFingerprintSources({ previous, previousHash = null, current, differ = null }) {
|
|
333
|
-
if (typeof differ === "function") try {
|
|
334
|
-
const items = differ({
|
|
335
|
-
sources: previous,
|
|
336
|
-
hash: previousHash ?? ""
|
|
337
|
-
}, {
|
|
338
|
-
sources: current.sources,
|
|
339
|
-
hash: current.hash
|
|
340
|
-
});
|
|
341
|
-
if (Array.isArray(items)) {
|
|
342
|
-
const names = [];
|
|
343
|
-
const seen = /* @__PURE__ */ new Set();
|
|
344
|
-
for (const item of items) {
|
|
345
|
-
const name = diffItemName(item);
|
|
346
|
-
if (name !== null && !seen.has(name)) {
|
|
347
|
-
seen.add(name);
|
|
348
|
-
names.push(name);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
return names;
|
|
352
|
-
}
|
|
353
|
-
} catch {}
|
|
354
|
-
return compareSourceLists(previous, current.sources);
|
|
355
|
-
}
|
|
356
|
-
function fingerprintDiffSuffix(changed) {
|
|
357
|
-
if (!changed.length) return "";
|
|
358
|
-
const shown = changed.slice(0, 3).join(", ");
|
|
359
|
-
return ` -- ${changed.length} source${changed.length === 1 ? "" : "s"} changed: ${shown}`;
|
|
360
|
-
}
|
|
361
|
-
function describeFingerprintMiss({ platform, current, lastBuild, root = sharedBuildCache(), differ = expoFingerprint.diffFingerprints }) {
|
|
362
|
-
if (!lastBuild || lastBuild.platform !== platform) return null;
|
|
363
|
-
const previousHash = typeof lastBuild.fingerprint === "string" ? lastBuild.fingerprint : null;
|
|
364
|
-
const previousKey = typeof lastBuild.cacheKey === "string" ? lastBuild.cacheKey : null;
|
|
365
|
-
if (!previousHash || !previousKey || previousHash === current.hash) return null;
|
|
366
|
-
const previous = storedSources(platform, previousKey, root);
|
|
367
|
-
if (!previous) return null;
|
|
368
|
-
const changed = diffFingerprintSources({
|
|
369
|
-
previous,
|
|
370
|
-
previousHash,
|
|
371
|
-
current,
|
|
372
|
-
differ
|
|
373
|
-
});
|
|
374
|
-
return changed.length ? {
|
|
375
|
-
changed,
|
|
376
|
-
previousHash
|
|
377
|
-
} : null;
|
|
378
|
-
}
|
|
379
|
-
async function refingerprintAfterMutation({ projectRoot, platform, previousHash, fingerprint = fingerprintProject }) {
|
|
380
|
-
let computed = null;
|
|
381
|
-
try {
|
|
382
|
-
computed = await fingerprint(projectRoot, { platform });
|
|
383
|
-
} catch {
|
|
384
|
-
return null;
|
|
385
|
-
}
|
|
386
|
-
if (!computed?.hash) return null;
|
|
387
|
-
return {
|
|
388
|
-
hash: computed.hash,
|
|
389
|
-
sources: computed.sources,
|
|
390
|
-
moved: computed.hash !== previousHash
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
function untrackedNativeFiles({ projectRoot, exec = getExecutor() }) {
|
|
394
|
-
let out;
|
|
395
|
-
try {
|
|
396
|
-
out = exec.runFile("git", [
|
|
397
|
-
"-C",
|
|
398
|
-
projectRoot,
|
|
399
|
-
"ls-files",
|
|
400
|
-
"--others",
|
|
401
|
-
"--exclude-standard",
|
|
402
|
-
"--",
|
|
403
|
-
"ios",
|
|
404
|
-
"android"
|
|
405
|
-
]);
|
|
406
|
-
} catch {
|
|
407
|
-
return [];
|
|
408
|
-
}
|
|
409
|
-
return String(out || "").split("\n").map((line) => line.trim()).filter((line) => line !== "");
|
|
410
|
-
}
|
|
411
|
-
function untrackedMissLine(files, cap = 3) {
|
|
412
|
-
if (!Array.isArray(files) || files.length === 0) return null;
|
|
413
|
-
const shown = files.slice(0, cap);
|
|
414
|
-
const more = files.length > shown.length ? `, and ${files.length - shown.length} more` : "";
|
|
415
|
-
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`;
|
|
416
|
-
}
|
|
417
|
-
function fingerprintDiffRecord({ changed, previousHash, hash }) {
|
|
418
|
-
const shown = changed.slice(0, 20);
|
|
419
|
-
return {
|
|
420
|
-
src: "build",
|
|
421
|
-
level: "info",
|
|
422
|
-
event: "fingerprint_diff",
|
|
423
|
-
msg: `fingerprint ${previousHash} -> ${hash}: ${changed.length} source${changed.length === 1 ? "" : "s"} changed: ` + shown.join(", ") + (changed.length > shown.length ? `, and ${changed.length - shown.length} more` : ""),
|
|
424
|
-
changed: changed.length,
|
|
425
|
-
sources: shown
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
//#endregion
|
|
429
|
-
//#region src/engine/build-lock.ts
|
|
430
|
-
const LOCK_SUFFIX = ".lock";
|
|
431
|
-
const WAIT_POLL_MS = 1e3;
|
|
432
|
-
const WAIT_PROGRESS_MS = 3e4;
|
|
433
|
-
const WAIT_CEILING_MS = 54e5;
|
|
434
|
-
function sleepAsync(ms) {
|
|
435
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
436
|
-
}
|
|
437
|
-
function segment(value) {
|
|
438
|
-
return String(value).replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^\.+/, "") || "unknown";
|
|
439
|
-
}
|
|
440
|
-
function buildLocksDir() {
|
|
441
|
-
return join(getConfigDir(), "build-locks");
|
|
442
|
-
}
|
|
443
|
-
function buildLockPath(platform, key) {
|
|
444
|
-
return join(buildLocksDir(), `${segment(platform)}-${segment(key)}${LOCK_SUFFIX}`);
|
|
445
|
-
}
|
|
446
|
-
function label(platform) {
|
|
447
|
-
return `${platform} build`;
|
|
448
|
-
}
|
|
449
|
-
function toRecord(holder) {
|
|
450
|
-
const details = holder.details;
|
|
451
|
-
return {
|
|
452
|
-
pid: holder.owner.pid,
|
|
453
|
-
projectRoot: typeof details.projectRoot === "string" ? details.projectRoot : null,
|
|
454
|
-
startedAt: holder.startedAt || null,
|
|
455
|
-
logFile: typeof details.logFile === "string" ? details.logFile : null
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
function readBuildLock(pathOrSpec) {
|
|
459
|
-
const path = typeof pathOrSpec === "string" ? pathOrSpec : buildLockPath(pathOrSpec.platform, pathOrSpec.key);
|
|
460
|
-
const survey = (0, ownership_claim_exports.readClaimSet)(path);
|
|
461
|
-
const holder = survey.live[0] ?? survey.dead[0];
|
|
462
|
-
return holder ? toRecord(holder) : null;
|
|
463
|
-
}
|
|
464
|
-
function acquireBuildLock({ platform, key, root = null, logFile = null }) {
|
|
465
|
-
const path = buildLockPath(platform, key);
|
|
466
|
-
const attempt = (0, ownership_claim_exports.tryAcquireClaim)({
|
|
467
|
-
root: path,
|
|
468
|
-
mode: "exclusive",
|
|
469
|
-
label: label(platform),
|
|
470
|
-
details: {
|
|
471
|
-
projectRoot: root,
|
|
472
|
-
logFile
|
|
473
|
-
}
|
|
474
|
-
});
|
|
475
|
-
const reaped = attempt.reaped[0];
|
|
476
|
-
if (attempt.acquired) {
|
|
477
|
-
const handle = {
|
|
478
|
-
acquired: true,
|
|
479
|
-
path,
|
|
480
|
-
lock: {
|
|
481
|
-
pid: attempt.acquired.owner.pid,
|
|
482
|
-
projectRoot: root,
|
|
483
|
-
startedAt: attempt.acquired.startedAt,
|
|
484
|
-
logFile
|
|
485
|
-
},
|
|
486
|
-
claim: attempt.acquired
|
|
487
|
-
};
|
|
488
|
-
return reaped ? {
|
|
489
|
-
...handle,
|
|
490
|
-
tookOver: toRecord(reaped)
|
|
491
|
-
} : handle;
|
|
492
|
-
}
|
|
493
|
-
if (attempt.pending) (0, ownership_claim_exports.releaseClaim)(attempt.pending);
|
|
494
|
-
const holder = attempt.held ?? attempt.waitingFor?.[0];
|
|
495
|
-
return holder ? {
|
|
496
|
-
held: toRecord(holder),
|
|
497
|
-
path
|
|
498
|
-
} : { path };
|
|
499
|
-
}
|
|
500
|
-
function releaseBuildLock(handle) {
|
|
501
|
-
return (0, ownership_claim_exports.releaseClaim)(handle?.claim);
|
|
502
|
-
}
|
|
503
|
-
function listBuildLocks() {
|
|
504
|
-
const dir = buildLocksDir();
|
|
505
|
-
let names;
|
|
506
|
-
try {
|
|
507
|
-
names = readdirSync(dir);
|
|
508
|
-
} catch {
|
|
509
|
-
return [];
|
|
510
|
-
}
|
|
511
|
-
const locks = [];
|
|
512
|
-
for (const name of names) {
|
|
513
|
-
if (!name.endsWith(LOCK_SUFFIX)) continue;
|
|
514
|
-
const path = join(dir, name);
|
|
515
|
-
try {
|
|
516
|
-
if (!statSync(path).isDirectory()) continue;
|
|
517
|
-
} catch {
|
|
518
|
-
continue;
|
|
519
|
-
}
|
|
520
|
-
const stem = name.slice(0, -5);
|
|
521
|
-
const cut = stem.indexOf("-");
|
|
522
|
-
const survey = (0, ownership_claim_exports.readClaimSet)(path);
|
|
523
|
-
const holder = survey.live[0] ?? survey.dead[0];
|
|
524
|
-
const info = holder ? toRecord(holder) : null;
|
|
525
|
-
locks.push({
|
|
526
|
-
path,
|
|
527
|
-
name,
|
|
528
|
-
platform: cut > 0 ? stem.slice(0, cut) : stem,
|
|
529
|
-
key: cut > 0 ? stem.slice(cut + 1) : null,
|
|
530
|
-
pid: info?.pid ?? null,
|
|
531
|
-
projectRoot: info?.projectRoot ?? null,
|
|
532
|
-
startedAt: info?.startedAt ?? null,
|
|
533
|
-
logFile: info?.logFile ?? null,
|
|
534
|
-
alive: survey.live.length > 0,
|
|
535
|
-
unresolved: survey.unresolved.length > 0
|
|
536
|
-
});
|
|
537
|
-
}
|
|
538
|
-
return locks;
|
|
539
|
-
}
|
|
540
|
-
function waitingLine({ projectRoot, pid, elapsedMs, logFile }) {
|
|
541
|
-
const where = projectRoot || "another workspace";
|
|
542
|
-
const tail = logFile ? ` -- tail ${logFile}` : "";
|
|
543
|
-
return `${"build".padEnd(11)} waiting on ${where} (pid ${pid ?? "?"}, ${formatElapsed(elapsedMs)} elapsed)${tail} -- stim guide lifecycle concurrency`;
|
|
544
|
-
}
|
|
545
|
-
function takeoverLine({ projectRoot, pid, logFile, startedAt = null, now = Date.now }) {
|
|
546
|
-
const where = projectRoot || "another workspace";
|
|
547
|
-
const started = startedAt ? Date.parse(startedAt) : NaN;
|
|
548
|
-
const ago = Number.isFinite(started) ? ` (it started ${formatElapsed(now() - started)} ago)` : "";
|
|
549
|
-
const tail = logFile ? ` -- read ${logFile} before this one finishes` : "";
|
|
550
|
-
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}`;
|
|
551
|
-
}
|
|
552
|
-
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 }) {
|
|
553
|
-
const path = buildLockPath(platform, key);
|
|
554
|
-
const started = now();
|
|
555
|
-
let lastProgress = started;
|
|
556
|
-
let holder = readBuildLock(path);
|
|
557
|
-
for (;;) {
|
|
558
|
-
const hit = resolve(platform, key);
|
|
559
|
-
if (hit) return {
|
|
560
|
-
hit,
|
|
561
|
-
waitedMs: now() - started
|
|
562
|
-
};
|
|
563
|
-
const survey = (0, ownership_claim_exports.readClaimSet)(path);
|
|
564
|
-
const unresolved = survey.unresolved[0];
|
|
565
|
-
if (unresolved) throw new ownership_claim_exports.ClaimRefusedError({
|
|
566
|
-
claimPath: unresolved.path,
|
|
567
|
-
root: path,
|
|
568
|
-
label: label(platform),
|
|
569
|
-
reason: unresolved.reason
|
|
570
|
-
});
|
|
571
|
-
const live = survey.live[0];
|
|
572
|
-
if (live) holder = toRecord(live);
|
|
573
|
-
else {
|
|
574
|
-
const dead = survey.dead[0];
|
|
575
|
-
if (dead) return {
|
|
576
|
-
builderFailed: `the builder (pid ${dead.owner.pid}) is gone`,
|
|
577
|
-
holder: toRecord(dead),
|
|
578
|
-
waitedMs: now() - started
|
|
579
|
-
};
|
|
580
|
-
return {
|
|
581
|
-
lockReleased: true,
|
|
582
|
-
holder,
|
|
583
|
-
waitedMs: now() - started
|
|
584
|
-
};
|
|
585
|
-
}
|
|
586
|
-
const elapsed = now() - started;
|
|
587
|
-
if (elapsed >= ceilingMs) {
|
|
588
|
-
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 ${(0, ownership_claim_exports.claimRemoveCommand)(path)}`);
|
|
589
|
-
err.code = "STIM_BUILD_WAIT_TIMEOUT";
|
|
590
|
-
err.lockPath = path;
|
|
591
|
-
err.holder = holder ?? void 0;
|
|
592
|
-
throw err;
|
|
593
|
-
}
|
|
594
|
-
if (now() - lastProgress >= progressMs) {
|
|
595
|
-
lastProgress = now();
|
|
596
|
-
out(waitingLine({
|
|
597
|
-
projectRoot: holder.projectRoot,
|
|
598
|
-
pid: holder.pid,
|
|
599
|
-
elapsedMs: elapsed,
|
|
600
|
-
logFile: holder.logFile
|
|
601
|
-
}));
|
|
602
|
-
}
|
|
603
|
-
await sleep(intervalMs);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
//#endregion
|
|
607
|
-
//#region src/sim/android.ts
|
|
1
|
+
import { t as getExecutor } from "./exec-B29XNuhP.mjs";
|
|
2
|
+
import { i as pidExists, o as signalProcessTree } from "./metro-C7bcJ0uo.mjs";
|
|
3
|
+
import { c as androidDataPartitionSizeBytes } from "./settings-BXXorVM2.mjs";
|
|
4
|
+
import { existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "fs";
|
|
5
|
+
import { homedir, tmpdir } from "os";
|
|
6
|
+
import { dirname, isAbsolute, join, resolve } from "path";
|
|
7
|
+
//#region src/devices/android.ts
|
|
608
8
|
const ANDROID_ABIS = /* @__PURE__ */ new Set([
|
|
609
9
|
"armeabi-v7a",
|
|
610
10
|
"arm64-v8a",
|
|
@@ -613,7 +13,10 @@ const ANDROID_ABIS = /* @__PURE__ */ new Set([
|
|
|
613
13
|
]);
|
|
614
14
|
const DEFAULT_AVD_DEVICE = "pixel_6";
|
|
615
15
|
function androidHome() {
|
|
616
|
-
|
|
16
|
+
const configured = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
|
|
17
|
+
if (configured) return configured;
|
|
18
|
+
if (process.platform === "win32") return join(process.env.LOCALAPPDATA || join(homedir(), "AppData", "Local"), "Android", "Sdk");
|
|
19
|
+
return join(homedir(), "Library", "Android", "sdk");
|
|
617
20
|
}
|
|
618
21
|
const SDK_TOOL_LOCATIONS = {
|
|
619
22
|
emulator: ["emulator", "emulator"],
|
|
@@ -625,9 +28,22 @@ const SDK_TOOL_LOCATIONS = {
|
|
|
625
28
|
"avdmanager"
|
|
626
29
|
]
|
|
627
30
|
};
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
31
|
+
const WINDOWS_SDK_TOOL_EXTENSIONS = {
|
|
32
|
+
emulator: ".exe",
|
|
33
|
+
adb: ".exe",
|
|
34
|
+
avdmanager: ".bat"
|
|
35
|
+
};
|
|
36
|
+
function androidToolPath(tool, platform = process.platform) {
|
|
37
|
+
const location = SDK_TOOL_LOCATIONS[tool];
|
|
38
|
+
const bare = location.at(-1);
|
|
39
|
+
const name = platform === "win32" ? `${bare}${WINDOWS_SDK_TOOL_EXTENSIONS[tool]}` : bare;
|
|
40
|
+
const abs = join(androidHome(), ...location.slice(0, -1), name);
|
|
41
|
+
return existsSync(abs) ? abs : bare;
|
|
42
|
+
}
|
|
43
|
+
const WINDOWS_BUILD_TOOL_EXTENSIONS = { apksigner: ".bat" };
|
|
44
|
+
function androidBuildToolName(tool, platform = process.platform) {
|
|
45
|
+
if (platform !== "win32") return tool;
|
|
46
|
+
return `${tool}${WINDOWS_BUILD_TOOL_EXTENSIONS[tool] ?? ".exe"}`;
|
|
631
47
|
}
|
|
632
48
|
function newestBuildTools(names) {
|
|
633
49
|
return [...Array.isArray(names) ? names : []].filter((n) => /^\d+(\.\d+)*(-\w+)?$/.test(String(n))).toSorted((a, b) => {
|
|
@@ -644,8 +60,8 @@ function buildToolsMajor(version) {
|
|
|
644
60
|
const major = Number(String(version ?? "").split(/[.-]/)[0]);
|
|
645
61
|
return Number.isFinite(major) ? major : 0;
|
|
646
62
|
}
|
|
647
|
-
function findBuildTool(tools, { home = androidHome(), readDir = readdirSync
|
|
648
|
-
const root = join
|
|
63
|
+
function findBuildTool(tools, { home = androidHome(), readDir = readdirSync, exists = existsSync, platform = process.platform } = {}) {
|
|
64
|
+
const root = join(home, "build-tools");
|
|
649
65
|
let versions = [];
|
|
650
66
|
try {
|
|
651
67
|
versions = readDir(root);
|
|
@@ -656,7 +72,7 @@ function findBuildTool(tools, { home = androidHome(), readDir = readdirSync$1, e
|
|
|
656
72
|
const version = newestBuildTools(versions);
|
|
657
73
|
if (!version) return null;
|
|
658
74
|
for (const tool of tools) {
|
|
659
|
-
const path = join
|
|
75
|
+
const path = join(root, version, androidBuildToolName(tool, platform));
|
|
660
76
|
if (exists(path)) return {
|
|
661
77
|
path,
|
|
662
78
|
tool,
|
|
@@ -673,14 +89,14 @@ function androidTool(tool) {
|
|
|
673
89
|
return resolved === tool ? tool : `"${resolved}"`;
|
|
674
90
|
}
|
|
675
91
|
function listInstalledSystemImages() {
|
|
676
|
-
const root = join
|
|
92
|
+
const root = join(androidHome(), "system-images");
|
|
677
93
|
const images = [];
|
|
678
|
-
if (!existsSync
|
|
94
|
+
if (!existsSync(root)) return images;
|
|
679
95
|
for (const apiDir of safeList(root)) {
|
|
680
96
|
const m = apiDir.match(/^android-(\d+)$/);
|
|
681
97
|
if (!m) continue;
|
|
682
|
-
const apiPath = join
|
|
683
|
-
for (const tag of safeList(apiPath)) for (const arch of safeList(join
|
|
98
|
+
const apiPath = join(root, apiDir);
|
|
99
|
+
for (const tag of safeList(apiPath)) for (const arch of safeList(join(apiPath, tag))) images.push({
|
|
684
100
|
api: Number(m[1]),
|
|
685
101
|
tag,
|
|
686
102
|
arch,
|
|
@@ -691,7 +107,7 @@ function listInstalledSystemImages() {
|
|
|
691
107
|
}
|
|
692
108
|
function safeList(dir) {
|
|
693
109
|
try {
|
|
694
|
-
return readdirSync
|
|
110
|
+
return readdirSync(dir);
|
|
695
111
|
} catch {
|
|
696
112
|
return [];
|
|
697
113
|
}
|
|
@@ -761,17 +177,17 @@ function parseAvdSystemImage(configIni) {
|
|
|
761
177
|
const separator = line.indexOf("=");
|
|
762
178
|
if (separator < 0) continue;
|
|
763
179
|
if (line.slice(0, separator).trim() !== "image.sysdir.1") continue;
|
|
764
|
-
const dir = line.slice(separator + 1).trim().replace(
|
|
180
|
+
const dir = line.slice(separator + 1).trim().replace(/[\\/]+$/, "");
|
|
765
181
|
if (!dir) return null;
|
|
766
|
-
return dir.split(
|
|
182
|
+
return dir.split(/[\\/]/).join(";");
|
|
767
183
|
}
|
|
768
184
|
return null;
|
|
769
185
|
}
|
|
770
|
-
function ownedAvdSystemImage(avdName, { avdDirectory = ownedAvdDirectory, readFile = (path) => readFileSync
|
|
186
|
+
function ownedAvdSystemImage(avdName, { avdDirectory = ownedAvdDirectory, readFile = (path) => readFileSync(path, "utf8") } = {}) {
|
|
771
187
|
const directory = avdDirectory(avdName);
|
|
772
188
|
if (!directory) return null;
|
|
773
189
|
try {
|
|
774
|
-
return parseAvdSystemImage(readFile(join
|
|
190
|
+
return parseAvdSystemImage(readFile(join(directory, "config.ini")));
|
|
775
191
|
} catch {
|
|
776
192
|
return null;
|
|
777
193
|
}
|
|
@@ -831,8 +247,22 @@ function parseAdbDevices(text) {
|
|
|
831
247
|
function listAvds({ timeoutMs } = {}) {
|
|
832
248
|
return parseAvdList(getExecutor().run(`${androidTool("emulator")} -list-avds`, { timeoutMs }));
|
|
833
249
|
}
|
|
834
|
-
|
|
835
|
-
|
|
250
|
+
/**
|
|
251
|
+
* Working directory for the adb client and the emulator Stim starts. Both leave processes behind
|
|
252
|
+
* that inherit it: the adb server is spawned by whichever client finds none running, and the
|
|
253
|
+
* emulator launcher forks qemu and a crashpad handler that outlives a crash on exit. On Windows a
|
|
254
|
+
* process holds its working directory open, and Windows refuses to delete a directory with an open
|
|
255
|
+
* handle, so one started from a worktree breaks `worktree remove`.
|
|
256
|
+
* https://github.com/appandflow/stim/issues/914
|
|
257
|
+
*/
|
|
258
|
+
function androidToolCwd(platform = process.platform) {
|
|
259
|
+
return platform === "win32" ? homedir() : void 0;
|
|
260
|
+
}
|
|
261
|
+
function listAdbDevices({ timeoutMs, platform = process.platform } = {}) {
|
|
262
|
+
return parseAdbDevices(getExecutor().run(`${androidTool("adb")} devices`, {
|
|
263
|
+
timeoutMs,
|
|
264
|
+
cwd: androidToolCwd(platform)
|
|
265
|
+
}));
|
|
836
266
|
}
|
|
837
267
|
const ADB_PROP_TIMEOUT_MS = 5e3;
|
|
838
268
|
const EMULATOR_HARDWARE = /* @__PURE__ */ new Set([
|
|
@@ -971,22 +401,22 @@ function parseAvdRootIni(contents) {
|
|
|
971
401
|
}
|
|
972
402
|
function avdIniPaths(root, ini) {
|
|
973
403
|
const parsed = parseAvdRootIni(ini);
|
|
974
|
-
return [parsed.relativePath && !isAbsolute
|
|
404
|
+
return [parsed.relativePath && !isAbsolute(parsed.relativePath) ? resolve(dirname(root), parsed.relativePath) : null, parsed.path && isAbsolute(parsed.path) ? parsed.path : null].filter((candidate) => candidate !== null);
|
|
975
405
|
}
|
|
976
406
|
function avdStorageRoots() {
|
|
977
407
|
const env = process.env;
|
|
978
408
|
return [...new Set([
|
|
979
409
|
env.ANDROID_AVD_HOME,
|
|
980
|
-
env.ANDROID_EMULATOR_HOME ? join
|
|
981
|
-
env.ANDROID_USER_HOME ? join
|
|
982
|
-
env.ANDROID_SDK_HOME ? join
|
|
983
|
-
env.ANDROID_SDK_HOME ? join
|
|
984
|
-
join
|
|
410
|
+
env.ANDROID_EMULATOR_HOME ? join(env.ANDROID_EMULATOR_HOME, "avd") : null,
|
|
411
|
+
env.ANDROID_USER_HOME ? join(env.ANDROID_USER_HOME, "avd") : null,
|
|
412
|
+
env.ANDROID_SDK_HOME ? join(env.ANDROID_SDK_HOME, ".android", "avd") : null,
|
|
413
|
+
env.ANDROID_SDK_HOME ? join(env.ANDROID_SDK_HOME, "avd") : null,
|
|
414
|
+
join(homedir(), ".android", "avd")
|
|
985
415
|
].filter((value) => Boolean(value)))];
|
|
986
416
|
}
|
|
987
417
|
function avdPathExists(path) {
|
|
988
418
|
try {
|
|
989
|
-
lstatSync
|
|
419
|
+
lstatSync(path);
|
|
990
420
|
return true;
|
|
991
421
|
} catch (error) {
|
|
992
422
|
if (error.code === "ENOENT") return false;
|
|
@@ -997,17 +427,17 @@ function listOrphanedAvdDirectories(avdName) {
|
|
|
997
427
|
const roots = avdStorageRoots();
|
|
998
428
|
const stores = roots.filter(avdPathExists).map((root) => ({
|
|
999
429
|
root,
|
|
1000
|
-
canonicalRoot: realpathSync
|
|
1001
|
-
names: readdirSync
|
|
430
|
+
canonicalRoot: realpathSync(root),
|
|
431
|
+
names: readdirSync(root)
|
|
1002
432
|
}));
|
|
1003
433
|
const registered = /* @__PURE__ */ new Set();
|
|
1004
434
|
for (const { root, canonicalRoot, names } of stores) for (const name of names.filter((entry) => entry.endsWith(".ini"))) {
|
|
1005
|
-
const path = join
|
|
1006
|
-
const ini = readFileSync
|
|
435
|
+
const path = join(root, name);
|
|
436
|
+
const ini = readFileSync(path, "utf8");
|
|
1007
437
|
const targets = /* @__PURE__ */ new Set([...avdIniPaths(root, ini), ...avdIniPaths(canonicalRoot, ini)]);
|
|
1008
438
|
if (!targets.size) throw new Error(`Cannot verify AVD registration at ${path}; its data was kept.`);
|
|
1009
439
|
for (const target of targets) try {
|
|
1010
|
-
registered.add(realpathSync
|
|
440
|
+
registered.add(realpathSync(target));
|
|
1011
441
|
} catch (error) {
|
|
1012
442
|
const code = error.code;
|
|
1013
443
|
if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
|
|
@@ -1019,11 +449,11 @@ function listOrphanedAvdDirectories(avdName) {
|
|
|
1019
449
|
for (const entry of names) {
|
|
1020
450
|
if (!/^stim-[A-Za-z0-9._-]+\.avd$/.test(entry)) continue;
|
|
1021
451
|
const name = entry.slice(0, -4);
|
|
1022
|
-
if (roots.some((candidate) => avdPathExists(join
|
|
1023
|
-
const directory = join
|
|
452
|
+
if (roots.some((candidate) => avdPathExists(join(candidate, `${name}.ini`)))) continue;
|
|
453
|
+
const directory = join(canonicalRoot, entry);
|
|
1024
454
|
if (registered.has(directory) || !avdPathExists(directory)) continue;
|
|
1025
|
-
const stat = lstatSync
|
|
1026
|
-
if (!stat.isDirectory() || stat.isSymbolicLink() || realpathSync
|
|
455
|
+
const stat = lstatSync(directory);
|
|
456
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || realpathSync(directory) !== directory) throw new Error(`Cannot verify AVD data at ${directory}; it was kept.`);
|
|
1027
457
|
if (!orphaned.some((candidate) => candidate.directory === directory)) orphaned.push({
|
|
1028
458
|
name,
|
|
1029
459
|
directory,
|
|
@@ -1034,17 +464,17 @@ function listOrphanedAvdDirectories(avdName) {
|
|
|
1034
464
|
}
|
|
1035
465
|
return orphaned;
|
|
1036
466
|
}
|
|
1037
|
-
function ownedAvdDirectory(avdName, { env = process.env, home = homedir(), readFile = (path) => readFileSync
|
|
467
|
+
function ownedAvdDirectory(avdName, { env = process.env, home = homedir(), readFile = (path) => readFileSync(path, "utf8"), realpath = realpathSync, isDirectory = (path) => statSync(path).isDirectory() } = {}) {
|
|
1038
468
|
if (!/^stim-[A-Za-z0-9._-]+$/.test(avdName)) return null;
|
|
1039
469
|
const roots = [
|
|
1040
470
|
env.ANDROID_AVD_HOME,
|
|
1041
|
-
env.ANDROID_SDK_HOME ? join
|
|
1042
|
-
join
|
|
471
|
+
env.ANDROID_SDK_HOME ? join(env.ANDROID_SDK_HOME, "avd") : null,
|
|
472
|
+
join(home, ".android", "avd")
|
|
1043
473
|
];
|
|
1044
474
|
for (const root of new Set(roots.filter((value) => Boolean(value)))) {
|
|
1045
475
|
let ini;
|
|
1046
476
|
try {
|
|
1047
|
-
ini = readFile(join
|
|
477
|
+
ini = readFile(join(root, `${avdName}.ini`));
|
|
1048
478
|
} catch (error) {
|
|
1049
479
|
const code = error.code;
|
|
1050
480
|
if (code === "ENOENT" || code === "ENOTDIR") continue;
|
|
@@ -1079,21 +509,21 @@ function withAvdConfigOverrides(contents, overrides) {
|
|
|
1079
509
|
return updated.join(newline) + (trailingNewline ? newline : "");
|
|
1080
510
|
}
|
|
1081
511
|
let avdConfigWriteSequence = 0;
|
|
1082
|
-
function configureNewOwnedAvd(avdName, { dataPartitionSizeGb, avdConfig = {} }, { avdDirectory = ownedAvdDirectory, readFile = (path) => readFileSync
|
|
512
|
+
function configureNewOwnedAvd(avdName, { dataPartitionSizeGb, avdConfig = {} }, { avdDirectory = ownedAvdDirectory, readFile = (path) => readFileSync(path, "utf8"), writeFile = (path, contents) => writeFileSync(path, contents, {
|
|
1083
513
|
encoding: "utf8",
|
|
1084
514
|
flag: "wx"
|
|
1085
|
-
}), rename = renameSync
|
|
515
|
+
}), rename = renameSync, remove = (path) => rmSync(path, { force: true }) } = {}) {
|
|
1086
516
|
const sizeBytes = androidDataPartitionSizeBytes(dataPartitionSizeGb);
|
|
1087
517
|
const directory = avdDirectory(avdName);
|
|
1088
518
|
if (!directory) throw new Error(`Could not resolve the content directory for newly created AVD ${avdName}.`);
|
|
1089
|
-
const configPath = join
|
|
519
|
+
const configPath = join(directory, "config.ini");
|
|
1090
520
|
const original = readFile(configPath);
|
|
1091
521
|
const expected = {
|
|
1092
522
|
...avdConfig,
|
|
1093
523
|
"disk.dataPartition.size": String(sizeBytes)
|
|
1094
524
|
};
|
|
1095
525
|
const updated = withAvdConfigOverrides(original, expected);
|
|
1096
|
-
const tempPath = join
|
|
526
|
+
const tempPath = join(directory, `.config.ini.stim-${process.pid}-${++avdConfigWriteSequence}.tmp`);
|
|
1097
527
|
try {
|
|
1098
528
|
writeFile(tempPath, updated);
|
|
1099
529
|
rename(tempPath, configPath);
|
|
@@ -1117,7 +547,7 @@ function avdPoolConfiguration(dataPartitionSizeGb, avdConfig) {
|
|
|
1117
547
|
function ownedAvdMatchesConfiguration(avdName, configuration) {
|
|
1118
548
|
const directory = ownedAvdDirectory(avdName);
|
|
1119
549
|
if (!directory) return false;
|
|
1120
|
-
const contents = readFileSync
|
|
550
|
+
const contents = readFileSync(join(directory, "config.ini"), "utf8");
|
|
1121
551
|
const expected = JSON.parse(configuration);
|
|
1122
552
|
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]}`));
|
|
1123
553
|
}
|
|
@@ -1184,14 +614,58 @@ async function resetAdoptedAvd(avdName, serial, keepPackage) {
|
|
|
1184
614
|
}
|
|
1185
615
|
}
|
|
1186
616
|
}
|
|
1187
|
-
function
|
|
617
|
+
function parseEmulatorVersion(text) {
|
|
618
|
+
const match = /Android emulator version (\d+)\./.exec(String(text ?? ""));
|
|
619
|
+
return match ? Number(match[1]) : null;
|
|
620
|
+
}
|
|
621
|
+
function emulatorMajorVersion() {
|
|
622
|
+
return parseEmulatorVersion(getExecutor().runQuiet(`${androidTool("emulator")} -version`, { timeoutMs: 1e4 }));
|
|
623
|
+
}
|
|
624
|
+
/** The first emulator whose launcher accepts `-crash-report-mode`; an older one refuses to start with it. */
|
|
625
|
+
const CRASH_REPORT_MODE_EMULATOR_VERSION = 37;
|
|
626
|
+
/**
|
|
627
|
+
* Where the emulator keeps crashpad's database on Windows: `AndroidEmulator\emu-crash-<version>.db`
|
|
628
|
+
* under GetTempPath, which reads TMP before TEMP.
|
|
629
|
+
*/
|
|
630
|
+
function emulatorCrashDatabases(env = process.env) {
|
|
631
|
+
const root = join(env.TMP || env.TEMP || tmpdir(), "AndroidEmulator");
|
|
632
|
+
try {
|
|
633
|
+
return readdirSync(root).filter((name) => /^emu-crash-.*\.db$/.test(name)).map((name) => join(root, name));
|
|
634
|
+
} catch {
|
|
635
|
+
return [];
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* An emulator that crashed (this host's crashes on exit) leaves a report in crashpad's database,
|
|
640
|
+
* and the next launch blocks on a consent dialog before qemu boots: emulator.log says "Showing
|
|
641
|
+
* crashdialog to get consent." and adb never sees the device. `-crash-report-mode never` (emulator
|
|
642
|
+
* 37 and later) answers it: the pending report is deleted at startup and the emulator boots.
|
|
643
|
+
* `-no-metrics` does not; the dialog shows all the same. An older emulator gets the database
|
|
644
|
+
* removed instead, which has the same effect unless a crash handler is still writing into it.
|
|
645
|
+
* https://github.com/appandflow/stim/issues/918
|
|
646
|
+
*/
|
|
647
|
+
function suppressEmulatorCrashConsent({ platform = process.platform, version = emulatorMajorVersion, databases = emulatorCrashDatabases, remove = (path) => rmSync(path, {
|
|
648
|
+
recursive: true,
|
|
649
|
+
force: true
|
|
650
|
+
}) } = {}) {
|
|
651
|
+
if (platform !== "win32") return [];
|
|
652
|
+
const major = version();
|
|
653
|
+
if (major !== null && major >= CRASH_REPORT_MODE_EMULATOR_VERSION) return ["-crash-report-mode", "never"];
|
|
654
|
+
for (const database of databases()) try {
|
|
655
|
+
remove(database);
|
|
656
|
+
} catch {}
|
|
657
|
+
return [];
|
|
658
|
+
}
|
|
659
|
+
function bootAndroidEmulator(avdName, consolePort, { logFile, platform = process.platform } = {}) {
|
|
1188
660
|
const child = getExecutor().spawn(androidToolPath("emulator"), [
|
|
1189
661
|
"-avd",
|
|
1190
662
|
avdName,
|
|
1191
663
|
"-port",
|
|
1192
664
|
String(consolePort),
|
|
1193
|
-
...headlessEmulatorArgs()
|
|
665
|
+
...headlessEmulatorArgs(),
|
|
666
|
+
...suppressEmulatorCrashConsent({ platform })
|
|
1194
667
|
], {
|
|
668
|
+
cwd: androidToolCwd(platform),
|
|
1195
669
|
detached: true,
|
|
1196
670
|
stdio: emulatorStdio(logFile)
|
|
1197
671
|
});
|
|
@@ -1201,8 +675,8 @@ function bootAndroidEmulator(avdName, consolePort, { logFile } = {}) {
|
|
|
1201
675
|
function emulatorStdio(logFile) {
|
|
1202
676
|
if (!logFile) return "ignore";
|
|
1203
677
|
try {
|
|
1204
|
-
mkdirSync
|
|
1205
|
-
const fd = openSync
|
|
678
|
+
mkdirSync(dirname(logFile), { recursive: true });
|
|
679
|
+
const fd = openSync(logFile, "w");
|
|
1206
680
|
return [
|
|
1207
681
|
"ignore",
|
|
1208
682
|
fd,
|
|
@@ -1254,14 +728,18 @@ function getprop(exec, serial, prop, timeoutMs) {
|
|
|
1254
728
|
const out = exec.runQuiet(`${androidTool("adb")} -s ${serial} shell getprop ${prop}`, { timeoutMs });
|
|
1255
729
|
return typeof out === "string" ? out.trim() : "";
|
|
1256
730
|
}
|
|
731
|
+
const PACKAGE_MANAGER_PROBE = "shell pm path android";
|
|
732
|
+
function packageManagerReady(exec, serial, timeoutMs) {
|
|
733
|
+
const out = exec.runQuiet(`${androidTool("adb")} -s ${serial} ${PACKAGE_MANAGER_PROBE}`, { timeoutMs });
|
|
734
|
+
return typeof out === "string" && /^package:/m.test(out);
|
|
735
|
+
}
|
|
1257
736
|
async function waitForBoot(serial, timeoutMs = 6e4, { aborted = () => false, pollMs = 1e3, commandTimeoutMs } = {}) {
|
|
1258
737
|
const exec = getExecutor();
|
|
1259
738
|
const start = Date.now();
|
|
1260
739
|
let exited = false;
|
|
1261
740
|
const probeTimeout = () => commandTimeoutMs === void 0 ? void 0 : Math.max(1, Math.min(commandTimeoutMs, timeoutMs - (Date.now() - start)));
|
|
1262
741
|
while (Date.now() - start < timeoutMs) {
|
|
1263
|
-
if (getprop(exec, serial, "sys.boot_completed", probeTimeout()) === "1") return { ok: true };
|
|
1264
|
-
if (getprop(exec, serial, "dev.bootcomplete", probeTimeout()) === "1") return { ok: true };
|
|
742
|
+
if ((getprop(exec, serial, "sys.boot_completed", probeTimeout()) === "1" || getprop(exec, serial, "dev.bootcomplete", probeTimeout()) === "1") && packageManagerReady(exec, serial, probeTimeout())) return { ok: true };
|
|
1265
743
|
if (aborted()) {
|
|
1266
744
|
exited = true;
|
|
1267
745
|
break;
|
|
@@ -1282,7 +760,8 @@ async function waitForBoot(serial, timeoutMs = 6e4, { aborted = () => false, pol
|
|
|
1282
760
|
devices: diagnosticQuery("devices"),
|
|
1283
761
|
sysBoot: diagnosticQuery(`-s ${serial} shell getprop sys.boot_completed`),
|
|
1284
762
|
devBoot: diagnosticQuery(`-s ${serial} shell getprop dev.bootcomplete`),
|
|
1285
|
-
bootAnim: diagnosticQuery(`-s ${serial} shell getprop init.svc.bootanim`)
|
|
763
|
+
bootAnim: diagnosticQuery(`-s ${serial} shell getprop init.svc.bootanim`),
|
|
764
|
+
packageManager: diagnosticQuery(`-s ${serial} ${PACKAGE_MANAGER_PROBE}`)
|
|
1286
765
|
}
|
|
1287
766
|
};
|
|
1288
767
|
}
|
|
@@ -1298,11 +777,11 @@ function sleepSync(ms) {
|
|
|
1298
777
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
1299
778
|
}
|
|
1300
779
|
function avdProcessLockPaths(avdDirectory, platform) {
|
|
1301
|
-
return ["hardware-qemu.ini.lock", "userdata-qemu.img.lock"].map((name) => join
|
|
780
|
+
return ["hardware-qemu.ini.lock", "userdata-qemu.img.lock"].map((name) => join(avdDirectory, name, ...platform === "win32" ? ["pid"] : []));
|
|
1302
781
|
}
|
|
1303
782
|
function readAvdProcessId(path) {
|
|
1304
783
|
try {
|
|
1305
|
-
const pid = Number(readFileSync
|
|
784
|
+
const pid = Number(readFileSync(path, "utf8").split("\0")[0]);
|
|
1306
785
|
if (!Number.isSafeInteger(pid) || pid <= 0) throw new Error(`Could not read the emulator PID from process lock ${path}.`);
|
|
1307
786
|
return pid;
|
|
1308
787
|
} catch (error) {
|
|
@@ -1330,7 +809,22 @@ function assertOwnedAvdStopped(avdName, { processAlive = pidExists, ...resolveOp
|
|
|
1330
809
|
const { processId } = resolveAvdProcess(avdName, resolveOptions);
|
|
1331
810
|
if (processId !== null && processAlive(processId)) throw new Error(`Owned AVD ${avdName} still has a live emulator process (${processId}).`);
|
|
1332
811
|
}
|
|
1333
|
-
|
|
812
|
+
const CRASH_HANDLER_EXIT_TIMEOUT_MS = 15e3;
|
|
813
|
+
/**
|
|
814
|
+
* The crashpad handler qemu forks on Windows. It outlives an emulator that crashes on exit while
|
|
815
|
+
* it writes the dump, so a `stop` that returns on qemu's exit leaves it running.
|
|
816
|
+
* https://github.com/appandflow/stim/issues/918
|
|
817
|
+
*/
|
|
818
|
+
function emulatorCrashHandlerPids(qemuPid) {
|
|
819
|
+
const out = getExecutor().runFileQuiet("powershell.exe", [
|
|
820
|
+
"-NoProfile",
|
|
821
|
+
"-NonInteractive",
|
|
822
|
+
"-Command",
|
|
823
|
+
`Get-CimInstance Win32_Process | Where-Object { $_.Name -eq 'crashpad_handler.exe' -and $_.ParentProcessId -eq ${qemuPid} } | ForEach-Object ProcessId`
|
|
824
|
+
], { timeoutMs: 1e4 });
|
|
825
|
+
return String(out ?? "").split(/\r?\n/).map((line) => Number(line.trim())).filter((pid) => Number.isSafeInteger(pid) && pid > 0);
|
|
826
|
+
}
|
|
827
|
+
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(), crashHandlerPids = emulatorCrashHandlerPids, killCrashHandler = (pid) => signalProcessTree(pid, "SIGKILL", { platform }), now = Date.now, sleep = sleepSync } = {}) {
|
|
1334
828
|
const { directory, processId } = resolveAvdProcess(avdName, {
|
|
1335
829
|
platform,
|
|
1336
830
|
resolveDirectory,
|
|
@@ -1346,6 +840,16 @@ function waitForAndroidEmulatorShutdown(avdName, shutdown, { timeoutMs = ANDROID
|
|
|
1346
840
|
if (remaining <= 0) throw new Error(`Owned AVD ${avdName} did not finish shutting down within ${Math.ceil(timeoutMs / 1e3)}s.`);
|
|
1347
841
|
sleep(Math.min(pollMs, remaining));
|
|
1348
842
|
}
|
|
843
|
+
if (platform === "win32") {
|
|
844
|
+
const handlerDeadline = now() + CRASH_HANDLER_EXIT_TIMEOUT_MS;
|
|
845
|
+
for (const handler of crashHandlerPids(processId)) {
|
|
846
|
+
while (processAlive(handler) && now() < handlerDeadline) sleep(pollMs);
|
|
847
|
+
if (processAlive(handler)) {
|
|
848
|
+
killCrashHandler(handler);
|
|
849
|
+
if (processAlive(handler)) throw new Error(`Crashpad handler ${handler} for owned AVD ${avdName} stayed alive.`);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
1349
853
|
if (!directoryExists(directory)) throw new Error(`Could not verify the content directory for owned AVD ${avdName} after shutdown.`);
|
|
1350
854
|
}
|
|
1351
855
|
function getAvdNameForSerial(serial, { timeoutMs } = {}) {
|
|
@@ -1364,4 +868,4 @@ function resolveOwnedAvdSerial(avdName, { timeoutMs } = {}) {
|
|
|
1364
868
|
return { notRunning: true };
|
|
1365
869
|
}
|
|
1366
870
|
//#endregion
|
|
1367
|
-
export {
|
|
871
|
+
export { ownedAvdName as A, waitForBoot as B, listAvds as C, nextConsolePort as D, memoizeEmulatorProbe as E, resetAdoptedAvd as F, resolveOwnedAvdSerial as I, resolvePhysicalDevice as L, physicalDeviceModel as M, pickDefaultSystemImage as N, ownedAvdDirectory as O, probeEmulatorSerial as P, shutdownAndroidEmulator as R, listAdbDevices as S, listOrphanedAvdDirectories as T, emulatorFailureRemedy as _, androidPoolNoCandidatesRefusal as a, getAvdNameForSerial as b, assertOwnedAvdStopped as c, avdStorageRoots as d, bootAndroidEmulator as f, emulatorDiskSpaceRemedy as g, deleteAvd as h, androidPoolCandidates as i, ownedAvdSystemImage as j, ownedAvdMatchesConfiguration as k, avdPathExists as l, createOwnedAvd as m, androidDeviceAbi as n, androidSystemImageAbi as o, configureNewOwnedAvd as p, androidHome as r, androidToolCwd as s, androidBuildToolName as t, avdPoolConfiguration as u, extractEmulatorFailure as v, listInstalledSystemImages as w, hostSystemImageArch as x, findBuildTool as y, waitForAndroidEmulatorShutdown as z };
|