stim 1.12.0 → 1.13.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 +3 -1
- package/dist/{activity-CCZJs__P.mjs → activity-DfVKZTEB.mjs} +42 -25
- package/dist/{android-BQiKJYK0.mjs → android-BHfAu2mf.mjs} +1 -1
- package/dist/{android-B7X-8H7h.mjs → android-CD8pr8Q2.mjs} +62 -47
- package/dist/{android-cas-DzLPXiEn.mjs → android-cas-B9Kv9aIy.mjs} +9 -7
- package/dist/{android-75Sk2rIR.mjs → android-qx_31wJo.mjs} +395 -116
- package/dist/{app-install-CsZi0SAT.mjs → app-install-CDFY2nUL.mjs} +1 -1
- package/dist/{budget-DbzW5VTD.mjs → budget-E5o91NR1.mjs} +154 -37
- package/dist/{build-plan-CqntbR7b.mjs → build-plan-BTa93o_r.mjs} +21 -88
- package/dist/{build-progress-h60GPV27.mjs → build-progress-l7d-X-ZP.mjs} +75 -7
- package/dist/cdp-Dq_ypAlJ.mjs +115 -0
- package/dist/chrome-B5hWgQJS.mjs +61 -0
- package/dist/cli.mjs +16 -15
- package/dist/collector-run.mjs +14 -8
- package/dist/{command-output-BWCj5Vh-.mjs → command-output-BIL7rlX7.mjs} +1 -1
- package/dist/created-devices-Cd558h31.mjs +37 -0
- package/dist/{deps-CO-_7UOm.mjs → deps-2Wo81np9.mjs} +1 -1
- package/dist/{device-9r9xRYDa.mjs → device-Cf7vl-so.mjs} +6 -6
- package/dist/{device-lease-B9LAXIUH.mjs → device-lease-CL9dZf73.mjs} +52 -3
- package/dist/{device-pool-BoezJd-c.mjs → device-pool-MuoiyRcu.mjs} +3 -3
- package/dist/{device-remote-C0_o-tJH.mjs → device-remote-DT9ngZiD.mjs} +10 -10
- package/dist/{doctor-LfDlbR-N.mjs → doctor-D2iysNNH.mjs} +47 -18
- package/dist/{error-diagnostics-CCxjn31V.mjs → error-diagnostics-BoiOhck8.mjs} +6 -6
- package/dist/{gc-C4nQFcTX.mjs → gc-eA-PRBX8.mjs} +853 -126
- package/dist/{guide-BPBlh9ML.mjs → guide-BfZ0OmiP.mjs} +736 -110
- package/dist/{guide-status-i9ogHt4v.mjs → guide-status-BaMYu55M.mjs} +1 -1
- package/dist/{idle-S3tFZ53_.mjs → idle-DxLi5OkN.mjs} +42 -9
- package/dist/{in-use-D9XYEgFD.mjs → in-use-wYEaKQM9.mjs} +9 -5
- package/dist/{ios-CDzWL1mj.mjs → ios-CcfCeZ19.mjs} +98 -54
- package/dist/{ios-CZWOHTTc.mjs → ios-CmEzZiOj.mjs} +10 -5
- package/dist/{ios-device-D9cLxVr5.mjs → ios-device-BW-t_Lkv.mjs} +1 -1
- package/dist/{launch-verify-B2oR_kFN.mjs → launch-verify-CuJaBpEa.mjs} +33 -20
- package/dist/{logs-SzuYudOj.mjs → logs-DcSRbC2t.mjs} +11 -10
- package/dist/{metro-Dzo30hvJ.mjs → metro-BPeY1YMh.mjs} +4 -4
- package/dist/{named-ports-Dn1o61D9.mjs → named-ports-n7tnJsI8.mjs} +78 -6
- package/dist/native-runtime-CiiFHt0e.mjs +74 -0
- package/dist/{ownership-Dqnrjrio.mjs → ownership-Bz2kETAS.mjs} +1 -1
- package/dist/{ownership-B8d4kXBx.mjs → ownership-PUP4Dwai.mjs} +134 -209
- package/dist/page-BTu7I60K.mjs +29 -0
- package/dist/{ports-Dp_QTwTC.mjs → ports-C5UutqpJ.mjs} +4 -4
- package/dist/{project-B8rbutVC.mjs → project-DGeclhvO.mjs} +53 -17
- package/dist/{reload-nocmx8KB.mjs → reload-C-cV5yy_.mjs} +79 -20
- package/dist/{remote-cache-BRsRZru5.mjs → remote-cache--aehr4Pr.mjs} +3 -3
- package/dist/{server-bare-Gn3ssSdK.mjs → server-bare-Bq8BpSIo.mjs} +1 -1
- package/dist/server-expo-BeYwP_Xy.mjs +2 -0
- package/dist/{server-expo-CeT1nIIk.mjs → server-expo-DUCg52-K.mjs} +2 -2
- package/dist/{settings-bE-Gu3Ti.mjs → settings-ssVBToDR.mjs} +10 -10
- package/dist/{settings-BlyDn2Oz.mjs → settings-xB0Frkg6.mjs} +11 -2
- package/dist/settings.schema.json +53 -1
- package/dist/{simslim-COtEEP7x.mjs → simslim-Bbg9UWrt.mjs} +12 -6
- package/dist/{slot-launch-DIACGEfH.mjs → slot-launch-BIWEW-wI.mjs} +3 -3
- package/dist/{start-BboE1HIS.mjs → start-BZhItkiL.mjs} +11 -10
- package/dist/{start-BFstVBlx.mjs → start-Rw2GD3nV.mjs} +1 -1
- package/dist/{state-DcDgzfu3.mjs → state-DwAU2_-D.mjs} +1 -1
- package/dist/state-l1pYwOre.mjs +148 -0
- package/dist/{stats-DiCtmluY.mjs → stats-CaSwmsSZ.mjs} +2 -2
- package/dist/{status-DI9Tjo7T.mjs → status-TJlL0HWo.mjs} +624 -103
- package/dist/{status-BUASYyy_.mjs → status-sszP4XKU.mjs} +59 -2
- package/dist/{stim-desktop-D0xhpZt8.mjs → stim-desktop-CXt8-Z3J.mjs} +13 -2
- package/dist/{stop-Bn6v-GvS.mjs → stop-BZ2qMiBP.mjs} +37 -12
- package/dist/{stop-BI7QY76M.mjs → stop-DskDKNTU.mjs} +2 -2
- package/dist/supervisor-run.mjs +10 -10
- package/dist/web-run.d.mts +22 -0
- package/dist/web-run.mjs +481 -0
- package/dist/web-sWXd5iek.mjs +394 -0
- package/dist/{worktree-B5sTRskL.mjs → worktree-3cBa-bQt.mjs} +1 -1
- package/dist/{worktree-ChDL5fO1.mjs → worktree-D4Do_XiQ.mjs} +226 -19
- package/dist/{xcode-BzplpQzc.mjs → xcode-LM6A8cNZ.mjs} +5 -5
- package/package.json +5 -5
- package/dist/server-expo-eWi9Coof.mjs +0 -2
- package/dist/workspace-process-lock-BvvJTfo_.mjs +0 -57
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { _ as loadConfig$1, d as getConfigDir } from "./config-0jCcMM0E.mjs";
|
|
1
|
+
import { A as ownership_claim_exports, B as deviceSlotPlatforms, E as withConfigLock$1, L as assignSlotDevice, O as process_identity_exports, S as saveConfig, U as projectDeviceSlots, W as removeSlotDevice, _ as loadConfig$1, c as ensureConfig, d as getConfigDir, f as getConfigPath$1 } from "./config-0jCcMM0E.mjs";
|
|
2
2
|
import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { n as stimDesktopInstalled, t as
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { existsSync
|
|
3
|
+
import { a as pidExists, l as signalProcessTree } from "./metro-BPeY1YMh.mjs";
|
|
4
|
+
import { n as settingDefault, r as stimDesktopInstalled, t as STIM_DESKTOP_OPEN_OPTIONS } from "./stim-desktop-CXt8-Z3J.mjs";
|
|
5
|
+
import { n as readCreatedDevices$1, r as recordCreatedDevice, t as forgetCreatedDevice } from "./created-devices-Cd558h31.mjs";
|
|
6
|
+
import { c as androidDataPartitionSizeBytes } from "./settings-xB0Frkg6.mjs";
|
|
7
|
+
import { existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "fs";
|
|
8
8
|
import { dirname, isAbsolute, join, resolve } from "path";
|
|
9
|
-
import { ANDROID_EMULATOR_APPS,
|
|
9
|
+
import { ANDROID_EMULATOR_APPS, loadConfig, settingDefinition } from "@stim-cli/core/state";
|
|
10
|
+
import { join as join$1 } from "node:path";
|
|
10
11
|
import { homedir, tmpdir } from "os";
|
|
12
|
+
import { isDeepStrictEqual } from "node:util";
|
|
11
13
|
//#region src/devices/android-emulator-viewer.ts
|
|
12
14
|
function configuredAndroidEmulatorApp(platform = process.platform) {
|
|
13
15
|
const value = loadConfig$1()?.androidEmulatorApp ?? settingDefault(settingDefinition("androidEmulatorApp"), () => stimDesktopInstalled(platform)).value;
|
|
@@ -22,66 +24,232 @@ function openEmulatorInStimDesktop(serial) {
|
|
|
22
24
|
"-a",
|
|
23
25
|
"Stim",
|
|
24
26
|
`stim-desktop://open?serial=${serial}`
|
|
25
|
-
],
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
], STIM_DESKTOP_OPEN_OPTIONS);
|
|
28
|
+
}
|
|
29
|
+
const POOL_SETTING_REMEDY = "Run `stim guide settings` for the device pool bounds and where it can be set.";
|
|
30
|
+
const MAX_SETTING = {
|
|
31
|
+
ios: {
|
|
32
|
+
key: "iosParkedMax",
|
|
33
|
+
env: "STIM_POOL_IOS_PARKED_MAX"
|
|
34
|
+
},
|
|
35
|
+
android: {
|
|
36
|
+
key: "androidParkedMax",
|
|
37
|
+
env: "STIM_POOL_ANDROID_PARKED_MAX"
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
function parseMax(raw, strings) {
|
|
41
|
+
const value = strings && typeof raw === "string" ? /^\d+$/.test(raw.trim()) ? Number(raw.trim()) : NaN : raw;
|
|
42
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) return null;
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
function parkedMaxSetting(platform, { config, env = process.env } = {}) {
|
|
46
|
+
const { key, env: envKey } = MAX_SETTING[platform];
|
|
47
|
+
const fromEnv = env[envKey];
|
|
48
|
+
const explicit = fromEnv !== void 0 && fromEnv !== "";
|
|
49
|
+
const pool = (config === void 0 ? loadConfig$1() : config)?.pool;
|
|
50
|
+
if (!explicit && pool !== void 0 && (pool === null || typeof pool !== "object" || Array.isArray(pool))) return {
|
|
51
|
+
max: 0,
|
|
52
|
+
error: "Invalid pool value. Expected an object with simulator and emulator bounds."
|
|
53
|
+
};
|
|
54
|
+
const fromConfig = pool !== null && typeof pool === "object" && !Array.isArray(pool) ? pool[key] : void 0;
|
|
55
|
+
const raw = explicit ? fromEnv : fromConfig;
|
|
56
|
+
if (raw === void 0) return {
|
|
57
|
+
max: env.STIM_HOME ? 0 : 3,
|
|
58
|
+
error: null
|
|
59
|
+
};
|
|
60
|
+
const parsed = parseMax(raw, explicit);
|
|
61
|
+
if (parsed === null) return {
|
|
62
|
+
max: 0,
|
|
63
|
+
error: `Invalid ${explicit ? envKey : `pool.${key}`} value ${JSON.stringify(raw)}. Expected a whole number of parked ${platform === "ios" ? "simulators" : "emulators"}, 0 or more.`
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
max: explicit ? parsed : env.STIM_HOME ? 0 : parsed,
|
|
67
|
+
error: null
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function isParkedRecord(value, platform) {
|
|
71
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
72
|
+
const record = value;
|
|
73
|
+
return typeof record.udid === "string" && typeof record.name === "string" && record.name.startsWith("stim-") && typeof record.parkedAt === "string" && (platform === "android" ? record.udid === record.name && /^stim-[A-Za-z0-9._-]+$/.test(record.name) && typeof record.systemImage === "string" && typeof record.configuration === "string" : typeof record.deviceTypeIdentifier === "string" && typeof record.runtimeIdentifier === "string" && typeof record.simslimManaged === "boolean" && (record.bundleId === void 0 || typeof record.bundleId === "string") && (record.cacheKey === void 0 || typeof record.cacheKey === "string"));
|
|
74
|
+
}
|
|
75
|
+
function poolBlock(config) {
|
|
76
|
+
const raw = config?.parked;
|
|
77
|
+
const block = raw !== null && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
78
|
+
const read = (platform) => {
|
|
79
|
+
const list = block[platform];
|
|
80
|
+
return Array.isArray(list) ? list.filter((record) => isParkedRecord(record, platform)) : [];
|
|
81
|
+
};
|
|
82
|
+
return {
|
|
83
|
+
ios: read("ios"),
|
|
84
|
+
android: read("android")
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function readParked(platform, { config } = {}) {
|
|
88
|
+
return poolBlock(config === void 0 ? loadConfig$1() : config)[platform];
|
|
89
|
+
}
|
|
90
|
+
function writeParked(config, platform, records) {
|
|
91
|
+
config.parked = {
|
|
92
|
+
...poolBlock(config),
|
|
93
|
+
[platform]: records
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function oldestFirst(records) {
|
|
97
|
+
return records.toSorted((a, b) => String(a.parkedAt).localeCompare(String(b.parkedAt)));
|
|
98
|
+
}
|
|
99
|
+
function isLegacyDeletionClaim(value) {
|
|
100
|
+
if (value === void 0) return false;
|
|
101
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return true;
|
|
102
|
+
const marker = value;
|
|
103
|
+
return marker.kind !== "ownership-claim" || typeof marker.claimId !== "string" || marker.claimId.length === 0;
|
|
104
|
+
}
|
|
105
|
+
function selectParked(records, { deviceTypeIdentifier, runtimeIdentifier }) {
|
|
106
|
+
return oldestFirst(records.filter((r) => !isLegacyDeletionClaim(r.deletionClaim) && r.deviceTypeIdentifier === deviceTypeIdentifier && r.runtimeIdentifier === runtimeIdentifier));
|
|
107
|
+
}
|
|
108
|
+
function evictOverflow(records, max) {
|
|
109
|
+
if (records.length <= max) return {
|
|
110
|
+
keep: [...records],
|
|
111
|
+
evicted: []
|
|
112
|
+
};
|
|
113
|
+
const ordered = oldestFirst(records);
|
|
114
|
+
const evicted = ordered.slice(0, ordered.length - max);
|
|
115
|
+
const dropped = new Set(evicted.map((r) => r.udid));
|
|
116
|
+
return {
|
|
117
|
+
keep: records.filter((r) => !dropped.has(r.udid)),
|
|
118
|
+
evicted
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function parkSim({ platform, projectPath, slot = "default", record, max }) {
|
|
122
|
+
return withConfigLock$1(() => {
|
|
123
|
+
const cfg = ensureConfig();
|
|
124
|
+
const current = deviceSlotPlatforms(cfg.projects[projectPath], slot)?.[platform];
|
|
125
|
+
const currentId = platform === "ios" ? current?.deviceUdid : current?.avdName;
|
|
126
|
+
if (!current?.owned || currentId !== record.udid) throw new Error(`The ${platform === "ios" ? "simulator" : "emulator"} assignment changed before parking.`);
|
|
127
|
+
const { keep, evicted } = evictOverflow([...readParked(platform, { config: cfg }).filter((r) => r.udid !== record.udid), record], max);
|
|
128
|
+
writeParked(cfg, platform, [...keep, ...evicted]);
|
|
129
|
+
const project = cfg.projects[projectPath];
|
|
130
|
+
if (project) removeSlotDevice(project, platform, slot);
|
|
131
|
+
saveConfig(cfg);
|
|
132
|
+
return evicted;
|
|
28
133
|
});
|
|
29
134
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
135
|
+
function adoptParked({ platform, projectPath, slot = "default", udid, device }) {
|
|
136
|
+
const claim = claimParkedOperation(platform, udid);
|
|
137
|
+
if (!claim) return null;
|
|
138
|
+
try {
|
|
139
|
+
return withConfigLock$1(() => {
|
|
140
|
+
const cfg = ensureConfig();
|
|
141
|
+
const records = readParked(platform, { config: cfg });
|
|
142
|
+
const taken = records.find((r) => r.udid === udid);
|
|
143
|
+
if (!taken || isLegacyDeletionClaim(taken.deletionClaim)) return null;
|
|
144
|
+
if (deviceSlotPlatforms(cfg.projects[projectPath], slot)?.[platform]) return null;
|
|
145
|
+
writeParked(cfg, platform, records.filter((r) => r.udid !== udid));
|
|
146
|
+
const project = cfg.projects[projectPath];
|
|
147
|
+
if (!project) throw new Error(`Project not registered: ${projectPath}`);
|
|
148
|
+
assignSlotDevice(project, platform, device, slot);
|
|
149
|
+
saveConfig(cfg);
|
|
150
|
+
const adopted = { ...taken };
|
|
151
|
+
delete adopted.deletionClaim;
|
|
152
|
+
return adopted;
|
|
153
|
+
});
|
|
154
|
+
} finally {
|
|
155
|
+
(0, ownership_claim_exports.releaseClaim)(claim);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function claimParkedOperation(platform, udid) {
|
|
159
|
+
const attempt = (0, ownership_claim_exports.tryAcquireClaim)({
|
|
160
|
+
root: join$1(getConfigDir(), "pool-locks", platform, `${encodeURIComponent(udid.toLowerCase())}.lock`),
|
|
161
|
+
mode: "exclusive",
|
|
162
|
+
label: `parked ${platform} device ${udid}`
|
|
163
|
+
});
|
|
164
|
+
if (attempt.pending) (0, ownership_claim_exports.releaseClaim)(attempt.pending);
|
|
165
|
+
return attempt.acquired ?? null;
|
|
166
|
+
}
|
|
167
|
+
function removeParkedAfter(platform, udid, beforeRemove) {
|
|
168
|
+
const claim = claimParkedOperation(platform, udid);
|
|
169
|
+
if (!claim) return null;
|
|
170
|
+
try {
|
|
171
|
+
const claimed = withConfigLock$1(() => {
|
|
172
|
+
const cfg = loadConfig$1();
|
|
173
|
+
if (!cfg) return null;
|
|
174
|
+
const records = readParked(platform, { config: cfg });
|
|
175
|
+
const current = records.find((candidate) => candidate.udid === udid);
|
|
176
|
+
if (!current) return null;
|
|
177
|
+
if (isLegacyDeletionClaim(current.deletionClaim)) throw new Error(`Parked ${platform} device ${udid} has a legacy deletionClaim in ${getConfigPath$1()}. Its PID does not establish whether the original operation finished. Keep the device and pool record; after verifying that neither the old Stim process nor its native child is using it, remove only that record's deletionClaim field and retry.`);
|
|
178
|
+
const record = { ...current };
|
|
179
|
+
delete record.deletionClaim;
|
|
180
|
+
const marked = {
|
|
181
|
+
...record,
|
|
182
|
+
deletionClaim: {
|
|
183
|
+
kind: "ownership-claim",
|
|
184
|
+
claimId: claim.claimId
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
writeParked(cfg, platform, records.map((candidate) => candidate.udid === udid ? marked : candidate));
|
|
188
|
+
saveConfig(cfg);
|
|
189
|
+
return {
|
|
190
|
+
record,
|
|
191
|
+
marked
|
|
192
|
+
};
|
|
193
|
+
});
|
|
194
|
+
if (!claimed) return null;
|
|
195
|
+
let completed = false;
|
|
196
|
+
let removed = null;
|
|
47
197
|
try {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
198
|
+
(0, ownership_claim_exports.markClaimChildPending)(claim);
|
|
199
|
+
try {
|
|
200
|
+
beforeRemove(claimed.record);
|
|
201
|
+
} finally {
|
|
202
|
+
(0, ownership_claim_exports.clearClaimChild)(claim);
|
|
203
|
+
}
|
|
204
|
+
completed = true;
|
|
205
|
+
} finally {
|
|
206
|
+
withConfigLock$1(() => {
|
|
207
|
+
const cfg = loadConfig$1();
|
|
208
|
+
if (!cfg) return;
|
|
209
|
+
const records = readParked(platform, { config: cfg });
|
|
210
|
+
const current = records.find((candidate) => candidate.udid === udid);
|
|
211
|
+
if (!isDeepStrictEqual(current, claimed.marked)) return;
|
|
212
|
+
writeParked(cfg, platform, completed ? records.filter((candidate) => candidate.udid !== udid) : records.map((candidate) => candidate.udid === udid ? claimed.record : candidate));
|
|
213
|
+
saveConfig(cfg);
|
|
214
|
+
if (completed) removed = claimed.record;
|
|
215
|
+
});
|
|
52
216
|
}
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
217
|
+
return removed;
|
|
218
|
+
} finally {
|
|
219
|
+
(0, ownership_claim_exports.releaseClaim)(claim);
|
|
220
|
+
}
|
|
57
221
|
}
|
|
58
|
-
function
|
|
59
|
-
|
|
222
|
+
function dropParked(platform, udid) {
|
|
223
|
+
return removeParkedAfter(platform, udid, () => {}) !== null;
|
|
60
224
|
}
|
|
61
225
|
//#endregion
|
|
62
226
|
//#region src/devices/device-ownership.ts
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
227
|
+
function recordedAsOwned(platform, id) {
|
|
228
|
+
const config = loadConfig();
|
|
229
|
+
if (!config) return false;
|
|
230
|
+
if (platform === "ios" ? readParked("ios", { config }).some((sim) => sim.udid === id) : readParked("android", { config }).some((avd) => avd.name === id)) return true;
|
|
231
|
+
for (const project of Object.values(config.projects ?? {})) {
|
|
232
|
+
let slots;
|
|
233
|
+
try {
|
|
234
|
+
slots = projectDeviceSlots(project);
|
|
235
|
+
} catch {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
for (const { platforms } of slots) {
|
|
239
|
+
const record = platforms[platform];
|
|
240
|
+
if (record?.owned && (platform === "ios" ? record.deviceUdid : record.avdName) === id) return true;
|
|
241
|
+
}
|
|
76
242
|
}
|
|
77
243
|
return false;
|
|
78
244
|
}
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
|
|
245
|
+
function isStimOwnedDevice(platform, id) {
|
|
246
|
+
return readCreatedDevices$1()[platform].has(id) || recordedAsOwned(platform, id);
|
|
247
|
+
}
|
|
248
|
+
function isStimOwnedSim(sim) {
|
|
249
|
+
return (sim.name ?? "").startsWith("stim-") && isStimOwnedDevice("ios", sim.udid);
|
|
82
250
|
}
|
|
83
|
-
function isStimOwnedAvd(name
|
|
84
|
-
return
|
|
251
|
+
function isStimOwnedAvd(name) {
|
|
252
|
+
return name.startsWith("stim-") && isStimOwnedDevice("android", name);
|
|
85
253
|
}
|
|
86
254
|
//#endregion
|
|
87
255
|
//#region src/devices/android.ts
|
|
@@ -92,10 +260,10 @@ const ANDROID_ABIS = /* @__PURE__ */ new Set([
|
|
|
92
260
|
"x86_64"
|
|
93
261
|
]);
|
|
94
262
|
const DEFAULT_AVD_DEVICE_PROFILE = String(settingDefinition("android.deviceProfile")?.default);
|
|
95
|
-
function androidHome() {
|
|
96
|
-
const configured =
|
|
263
|
+
function androidHome(env = process.env) {
|
|
264
|
+
const configured = env.ANDROID_HOME || env.ANDROID_SDK_ROOT;
|
|
97
265
|
if (configured) return configured;
|
|
98
|
-
if (process.platform === "win32") return join(
|
|
266
|
+
if (process.platform === "win32") return join(env.LOCALAPPDATA || join(homedir(), "AppData", "Local"), "Android", "Sdk");
|
|
99
267
|
return join(homedir(), "Library", "Android", "sdk");
|
|
100
268
|
}
|
|
101
269
|
const SDK_TOOL_LOCATIONS = {
|
|
@@ -106,19 +274,26 @@ const SDK_TOOL_LOCATIONS = {
|
|
|
106
274
|
"latest",
|
|
107
275
|
"bin",
|
|
108
276
|
"avdmanager"
|
|
277
|
+
],
|
|
278
|
+
sdkmanager: [
|
|
279
|
+
"cmdline-tools",
|
|
280
|
+
"latest",
|
|
281
|
+
"bin",
|
|
282
|
+
"sdkmanager"
|
|
109
283
|
]
|
|
110
284
|
};
|
|
111
285
|
const WINDOWS_SDK_TOOL_EXTENSIONS = {
|
|
112
286
|
emulator: ".exe",
|
|
113
287
|
adb: ".exe",
|
|
114
|
-
avdmanager: ".bat"
|
|
288
|
+
avdmanager: ".bat",
|
|
289
|
+
sdkmanager: ".bat"
|
|
115
290
|
};
|
|
116
291
|
function androidToolPath(tool, platform = process.platform) {
|
|
117
292
|
const location = SDK_TOOL_LOCATIONS[tool];
|
|
118
293
|
const bare = location.at(-1);
|
|
119
294
|
const name = platform === "win32" ? `${bare}${WINDOWS_SDK_TOOL_EXTENSIONS[tool]}` : bare;
|
|
120
295
|
const abs = join(androidHome(), ...location.slice(0, -1), name);
|
|
121
|
-
return existsSync
|
|
296
|
+
return existsSync(abs) ? abs : bare;
|
|
122
297
|
}
|
|
123
298
|
const WINDOWS_BUILD_TOOL_EXTENSIONS = { apksigner: ".bat" };
|
|
124
299
|
function androidBuildToolName(tool, platform = process.platform) {
|
|
@@ -140,7 +315,7 @@ function buildToolsMajor(version) {
|
|
|
140
315
|
const major = Number(String(version ?? "").split(/[.-]/)[0]);
|
|
141
316
|
return Number.isFinite(major) ? major : 0;
|
|
142
317
|
}
|
|
143
|
-
function findBuildTool(tools, { home = androidHome(), readDir = readdirSync
|
|
318
|
+
function findBuildTool(tools, { home = androidHome(), readDir = readdirSync, exists = existsSync, platform = process.platform } = {}) {
|
|
144
319
|
const root = join(home, "build-tools");
|
|
145
320
|
let versions = [];
|
|
146
321
|
try {
|
|
@@ -168,15 +343,23 @@ function androidTool(tool) {
|
|
|
168
343
|
const resolved = androidToolPath(tool);
|
|
169
344
|
return resolved === tool ? tool : `"${resolved}"`;
|
|
170
345
|
}
|
|
171
|
-
function listInstalledSystemImages() {
|
|
346
|
+
function listInstalledSystemImages(onUnreadable) {
|
|
172
347
|
const root = join(androidHome(), "system-images");
|
|
173
348
|
const images = [];
|
|
174
|
-
|
|
175
|
-
|
|
349
|
+
const list = (dir) => {
|
|
350
|
+
try {
|
|
351
|
+
return readdirSync(dir);
|
|
352
|
+
} catch (error) {
|
|
353
|
+
const code = error.code;
|
|
354
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") onUnreadable?.(error);
|
|
355
|
+
return [];
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
for (const apiDir of list(root)) {
|
|
176
359
|
const m = apiDir.match(/^android-(\d+)$/);
|
|
177
360
|
if (!m) continue;
|
|
178
361
|
const apiPath = join(root, apiDir);
|
|
179
|
-
for (const tag of
|
|
362
|
+
for (const tag of list(apiPath)) for (const arch of list(join(apiPath, tag))) images.push({
|
|
180
363
|
api: Number(m[1]),
|
|
181
364
|
tag,
|
|
182
365
|
arch,
|
|
@@ -185,13 +368,6 @@ function listInstalledSystemImages() {
|
|
|
185
368
|
}
|
|
186
369
|
return images;
|
|
187
370
|
}
|
|
188
|
-
function safeList(dir) {
|
|
189
|
-
try {
|
|
190
|
-
return readdirSync$1(dir);
|
|
191
|
-
} catch {
|
|
192
|
-
return [];
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
371
|
function pageSizeRank(image) {
|
|
196
372
|
return /(^|_)ps16k$/.test(String(image?.tag || "")) ? 1 : 0;
|
|
197
373
|
}
|
|
@@ -211,7 +387,7 @@ function profileNeedsFoldFeature(profile) {
|
|
|
211
387
|
}
|
|
212
388
|
function systemImageSupportsFold(pkg, home = androidHome()) {
|
|
213
389
|
try {
|
|
214
|
-
const ini = readFileSync
|
|
390
|
+
const ini = readFileSync(join(home, ...pkg.split(";"), "advancedFeatures.ini"), "utf8");
|
|
215
391
|
return /^\s*SupportPixelFold\s*=\s*on\s*$/im.test(ini);
|
|
216
392
|
} catch {
|
|
217
393
|
return false;
|
|
@@ -283,7 +459,7 @@ function avdIniValue(configIni, key) {
|
|
|
283
459
|
}
|
|
284
460
|
return null;
|
|
285
461
|
}
|
|
286
|
-
function ownedAvdSystemImage(avdName, { avdDirectory = ownedAvdDirectory, readFile = (path) => readFileSync
|
|
462
|
+
function ownedAvdSystemImage(avdName, { avdDirectory = ownedAvdDirectory, readFile = (path) => readFileSync(path, "utf8") } = {}) {
|
|
287
463
|
const directory = avdDirectory(avdName);
|
|
288
464
|
if (!directory) return null;
|
|
289
465
|
try {
|
|
@@ -310,24 +486,18 @@ function sanitizeAvdLabel(label) {
|
|
|
310
486
|
function avdConfigIni(avdName, directory = ownedAvdDirectory(avdName)) {
|
|
311
487
|
if (!directory) return null;
|
|
312
488
|
try {
|
|
313
|
-
return readFileSync
|
|
489
|
+
return readFileSync(join(directory, "config.ini"), "utf8");
|
|
314
490
|
} catch {
|
|
315
491
|
return null;
|
|
316
492
|
}
|
|
317
493
|
}
|
|
318
|
-
function registeredOrOrphanedAvdDirectory(avdName) {
|
|
319
|
-
return ownedAvdDirectory(avdName) ?? listOrphanedAvdDirectories(avdName)[0]?.directory ?? null;
|
|
320
|
-
}
|
|
321
|
-
function isStimOwnedAvdName(avdName, directory) {
|
|
322
|
-
return isStimOwnedAvd(avdName, () => avdConfigIni(avdName, directory === void 0 ? registeredOrOrphanedAvdDirectory(avdName) : directory));
|
|
323
|
-
}
|
|
324
494
|
function ownedAvdName(label) {
|
|
325
495
|
const clean = sanitizeAvdLabel(label);
|
|
326
496
|
return `stim-${clean.startsWith("stim-") ? clean.slice(5) : clean}`;
|
|
327
497
|
}
|
|
328
498
|
const AVDMANAGER_DELETE_TIMEOUT_MS = 12e4;
|
|
329
499
|
function deleteAvd(avdName) {
|
|
330
|
-
if (!
|
|
500
|
+
if (!isStimOwnedAvd(avdName)) throw new Error(`Refusing to delete AVD "${avdName}": not a Stim-owned AVD; Stim has no record of creating it.`);
|
|
331
501
|
getExecutor().run(`${androidTool("avdmanager")} delete avd -n "${avdName}"`, {
|
|
332
502
|
timeoutMs: AVDMANAGER_DELETE_TIMEOUT_MS,
|
|
333
503
|
killSignal: "SIGKILL"
|
|
@@ -548,28 +718,45 @@ function avdStorageRoots() {
|
|
|
548
718
|
}
|
|
549
719
|
function avdPathExists(path) {
|
|
550
720
|
try {
|
|
551
|
-
lstatSync
|
|
721
|
+
lstatSync(path);
|
|
552
722
|
return true;
|
|
553
723
|
} catch (error) {
|
|
554
724
|
if (error.code === "ENOENT") return false;
|
|
555
725
|
throw error;
|
|
556
726
|
}
|
|
557
727
|
}
|
|
728
|
+
/**
|
|
729
|
+
* True only when the AVD home the emulator uses first exists and no AVD root holds a registration or data
|
|
730
|
+
* directory for `avdName`. A missing first root may be an unmounted volume, so it proves nothing; an
|
|
731
|
+
* unreadable path throws.
|
|
732
|
+
*/
|
|
733
|
+
function avdNameAbsent(avdName, roots = avdStorageRoots()) {
|
|
734
|
+
if (!roots[0] || !isDirectoryPath(roots[0])) return false;
|
|
735
|
+
return roots.every((root) => !avdPathExists(join(root, `${avdName}.ini`)) && !avdPathExists(join(root, `${avdName}.avd`)));
|
|
736
|
+
}
|
|
737
|
+
function isDirectoryPath(path) {
|
|
738
|
+
try {
|
|
739
|
+
return statSync(path).isDirectory();
|
|
740
|
+
} catch (error) {
|
|
741
|
+
if (error.code === "ENOENT") return false;
|
|
742
|
+
throw error;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
558
745
|
function listOrphanedAvdDirectories(avdName) {
|
|
559
746
|
const roots = avdStorageRoots();
|
|
560
747
|
const stores = roots.filter(avdPathExists).map((root) => ({
|
|
561
748
|
root,
|
|
562
|
-
canonicalRoot: realpathSync
|
|
563
|
-
names: readdirSync
|
|
749
|
+
canonicalRoot: realpathSync(root),
|
|
750
|
+
names: readdirSync(root)
|
|
564
751
|
}));
|
|
565
752
|
const registered = /* @__PURE__ */ new Set();
|
|
566
753
|
for (const { root, canonicalRoot, names } of stores) for (const name of names.filter((entry) => entry.endsWith(".ini"))) {
|
|
567
754
|
const path = join(root, name);
|
|
568
|
-
const ini = readFileSync
|
|
755
|
+
const ini = readFileSync(path, "utf8");
|
|
569
756
|
const targets = /* @__PURE__ */ new Set([...avdIniPaths(root, ini), ...avdIniPaths(canonicalRoot, ini)]);
|
|
570
757
|
if (!targets.size) throw new Error(`Cannot verify AVD registration at ${path}; its data was kept.`);
|
|
571
758
|
for (const target of targets) try {
|
|
572
|
-
registered.add(realpathSync
|
|
759
|
+
registered.add(realpathSync(target));
|
|
573
760
|
} catch (error) {
|
|
574
761
|
const code = error.code;
|
|
575
762
|
if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
|
|
@@ -584,8 +771,8 @@ function listOrphanedAvdDirectories(avdName) {
|
|
|
584
771
|
if (roots.some((candidate) => avdPathExists(join(candidate, `${name}.ini`)))) continue;
|
|
585
772
|
const directory = join(canonicalRoot, entry);
|
|
586
773
|
if (registered.has(directory) || !avdPathExists(directory)) continue;
|
|
587
|
-
const stat = lstatSync
|
|
588
|
-
if (!stat.isDirectory() || stat.isSymbolicLink() || realpathSync
|
|
774
|
+
const stat = lstatSync(directory);
|
|
775
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || realpathSync(directory) !== directory) throw new Error(`Cannot verify AVD data at ${directory}; it was kept.`);
|
|
589
776
|
if (!orphaned.some((candidate) => candidate.directory === directory)) orphaned.push({
|
|
590
777
|
name,
|
|
591
778
|
directory,
|
|
@@ -596,7 +783,7 @@ function listOrphanedAvdDirectories(avdName) {
|
|
|
596
783
|
}
|
|
597
784
|
return orphaned;
|
|
598
785
|
}
|
|
599
|
-
function ownedAvdDirectory(avdName, { env = process.env, home = homedir(), readFile = (path) => readFileSync
|
|
786
|
+
function ownedAvdDirectory(avdName, { env = process.env, home = homedir(), readFile = (path) => readFileSync(path, "utf8"), realpath = realpathSync, isDirectory = (path) => statSync(path).isDirectory() } = {}) {
|
|
600
787
|
if (!/^stim-[A-Za-z0-9._-]+$/.test(avdName)) return null;
|
|
601
788
|
const roots = [
|
|
602
789
|
env.ANDROID_AVD_HOME,
|
|
@@ -630,7 +817,7 @@ const AVD_USER_DATA = [
|
|
|
630
817
|
"snapshots"
|
|
631
818
|
];
|
|
632
819
|
function wipeAvdUserData(directory) {
|
|
633
|
-
for (const name of AVD_USER_DATA) rmSync
|
|
820
|
+
for (const name of AVD_USER_DATA) rmSync(join(directory, name), {
|
|
634
821
|
recursive: true,
|
|
635
822
|
force: true
|
|
636
823
|
});
|
|
@@ -656,10 +843,10 @@ function withAvdConfigOverrides(contents, overrides) {
|
|
|
656
843
|
return updated.join(newline) + (trailingNewline ? newline : "");
|
|
657
844
|
}
|
|
658
845
|
let avdConfigWriteSequence = 0;
|
|
659
|
-
function configureNewOwnedAvd(avdName, { dataPartitionSizeGb, avdConfig = {} }, { avdDirectory = ownedAvdDirectory, readFile = (path) => readFileSync
|
|
846
|
+
function configureNewOwnedAvd(avdName, { dataPartitionSizeGb, avdConfig = {} }, { avdDirectory = ownedAvdDirectory, readFile = (path) => readFileSync(path, "utf8"), writeFile = (path, contents) => writeFileSync(path, contents, {
|
|
660
847
|
encoding: "utf8",
|
|
661
848
|
flag: "wx"
|
|
662
|
-
}), rename = renameSync
|
|
849
|
+
}), rename = renameSync, remove = (path) => rmSync(path, { force: true }) } = {}) {
|
|
663
850
|
const sizeBytes = androidDataPartitionSizeBytes(dataPartitionSizeGb);
|
|
664
851
|
const directory = avdDirectory(avdName);
|
|
665
852
|
if (!directory) throw new Error(`Could not resolve the content directory for newly created AVD ${avdName}.`);
|
|
@@ -699,7 +886,7 @@ function avdPoolConfiguration(dataPartitionSizeGb, avdConfig, deviceProfile = DE
|
|
|
699
886
|
function ownedAvdMatchesConfiguration(avdName, configuration) {
|
|
700
887
|
const directory = ownedAvdDirectory(avdName);
|
|
701
888
|
if (!directory) return false;
|
|
702
|
-
const contents = readFileSync
|
|
889
|
+
const contents = readFileSync(join(directory, "config.ini"), "utf8");
|
|
703
890
|
const expected = JSON.parse(configuration);
|
|
704
891
|
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]}`));
|
|
705
892
|
}
|
|
@@ -782,7 +969,7 @@ const CRASH_REPORT_MODE_EMULATOR_VERSION = 37;
|
|
|
782
969
|
function emulatorCrashDatabases(env = process.env) {
|
|
783
970
|
const root = join(env.TMP || env.TEMP || tmpdir(), "AndroidEmulator");
|
|
784
971
|
try {
|
|
785
|
-
return readdirSync
|
|
972
|
+
return readdirSync(root).filter((name) => /^emu-crash-.*\.db$/.test(name)).map((name) => join(root, name));
|
|
786
973
|
} catch {
|
|
787
974
|
return [];
|
|
788
975
|
}
|
|
@@ -796,7 +983,7 @@ function emulatorCrashDatabases(env = process.env) {
|
|
|
796
983
|
* removed instead, which has the same effect unless a crash handler is still writing into it.
|
|
797
984
|
* https://github.com/appandflow/stim/issues/918
|
|
798
985
|
*/
|
|
799
|
-
function suppressEmulatorCrashConsent({ platform = process.platform, version = emulatorMajorVersion, databases = emulatorCrashDatabases, remove = (path) => rmSync
|
|
986
|
+
function suppressEmulatorCrashConsent({ platform = process.platform, version = emulatorMajorVersion, databases = emulatorCrashDatabases, remove = (path) => rmSync(path, {
|
|
800
987
|
recursive: true,
|
|
801
988
|
force: true
|
|
802
989
|
}) } = {}) {
|
|
@@ -834,8 +1021,8 @@ function bootAndroidEmulator(avdName, consolePort, { logFile, platform = process
|
|
|
834
1021
|
function emulatorStdio(logFile) {
|
|
835
1022
|
if (!logFile) return "ignore";
|
|
836
1023
|
try {
|
|
837
|
-
mkdirSync
|
|
838
|
-
const fd = openSync
|
|
1024
|
+
mkdirSync(dirname(logFile), { recursive: true });
|
|
1025
|
+
const fd = openSync(logFile, "w");
|
|
839
1026
|
return [
|
|
840
1027
|
"ignore",
|
|
841
1028
|
fd,
|
|
@@ -940,7 +1127,7 @@ function avdProcessLockPaths(avdDirectory, platform) {
|
|
|
940
1127
|
}
|
|
941
1128
|
function readAvdProcessId(path) {
|
|
942
1129
|
try {
|
|
943
|
-
const pid = Number(readFileSync
|
|
1130
|
+
const pid = Number(readFileSync(path, "utf8").split("\0")[0]);
|
|
944
1131
|
if (!Number.isSafeInteger(pid) || pid <= 0) throw new Error(`Could not read the emulator PID from process lock ${path}.`);
|
|
945
1132
|
return pid;
|
|
946
1133
|
} catch (error) {
|
|
@@ -964,9 +1151,37 @@ function resolveAvdProcess(avdName, { platform = process.platform, resolveDirect
|
|
|
964
1151
|
processId: null
|
|
965
1152
|
};
|
|
966
1153
|
}
|
|
967
|
-
function
|
|
1154
|
+
function escapeRegExp(value) {
|
|
1155
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1156
|
+
}
|
|
1157
|
+
function parseAvdEmulatorProcesses(psOutput, avdName) {
|
|
1158
|
+
const command = new RegExp(`(?:^|/)(?:qemu-system-[^\\s/]+|emulator)(?:\\s.*)?\\s(?:-avd\\s+|@)${escapeRegExp(avdName)}(?:\\s|$)`);
|
|
1159
|
+
const pids = [];
|
|
1160
|
+
for (const line of psOutput.split("\n")) {
|
|
1161
|
+
const match = /^\s*(\d+)\s+(.*)$/.exec(line);
|
|
1162
|
+
if (match && command.test(match[2])) pids.push(Number(match[1]));
|
|
1163
|
+
}
|
|
1164
|
+
return pids;
|
|
1165
|
+
}
|
|
1166
|
+
function listAvdEmulatorProcesses(avdName, platform) {
|
|
1167
|
+
if (platform === "win32") return [];
|
|
1168
|
+
const out = getExecutor().runFileQuiet("ps", [
|
|
1169
|
+
"-axww",
|
|
1170
|
+
"-o",
|
|
1171
|
+
"pid=,command="
|
|
1172
|
+
], { timeoutMs: 5e3 });
|
|
1173
|
+
return out === null ? null : parseAvdEmulatorProcesses(out, avdName);
|
|
1174
|
+
}
|
|
1175
|
+
function scanAvdEmulatorProcesses(avdName, platform, listProcesses) {
|
|
1176
|
+
const pids = listProcesses(avdName, platform);
|
|
1177
|
+
if (pids === null) throw new Error(`Could not read the process table to verify that owned AVD ${avdName} stopped.`);
|
|
1178
|
+
return pids;
|
|
1179
|
+
}
|
|
1180
|
+
function assertOwnedAvdStopped(avdName, { processAlive = pidExists, listProcesses = listAvdEmulatorProcesses, ...resolveOptions } = {}) {
|
|
968
1181
|
const { processId } = resolveAvdProcess(avdName, resolveOptions);
|
|
969
|
-
|
|
1182
|
+
const live = new Set(scanAvdEmulatorProcesses(avdName, resolveOptions.platform ?? process.platform, listProcesses));
|
|
1183
|
+
if (processId !== null && processAlive(processId)) live.add(processId);
|
|
1184
|
+
if (live.size) throw new Error(`Owned AVD ${avdName} still has a live emulator process (${[...live].join(", ")}).`);
|
|
970
1185
|
}
|
|
971
1186
|
const CRASH_HANDLER_EXIT_TIMEOUT_MS = 15e3;
|
|
972
1187
|
/**
|
|
@@ -983,23 +1198,85 @@ function emulatorCrashHandlerPids(qemuPid) {
|
|
|
983
1198
|
], { timeoutMs: 1e4 });
|
|
984
1199
|
return String(out ?? "").split(/\r?\n/).map((line) => Number(line.trim())).filter((pid) => Number.isSafeInteger(pid) && pid > 0);
|
|
985
1200
|
}
|
|
986
|
-
|
|
1201
|
+
const EMULATOR_SIGNAL_GRACE_MS = 5e3;
|
|
1202
|
+
/**
|
|
1203
|
+
* Shuts down the emulator of an owned AVD and returns only once every emulator process launched for it
|
|
1204
|
+
* has exited. `shutdown` asks the emulator to quit through its console; it is null when the emulator is
|
|
1205
|
+
* not reachable over adb. Processes still running after it get SIGTERM, and SIGKILL if they outlive their
|
|
1206
|
+
* grace, but only a process whose command line names this AVD and whose identity, captured before
|
|
1207
|
+
* shutdown, still matches. Windows has no process-table scan, so there nothing is signalled.
|
|
1208
|
+
*/
|
|
1209
|
+
function waitForAndroidEmulatorShutdown(avdName, shutdown, { timeoutMs = ANDROID_EMULATOR_SHUTDOWN_TIMEOUT_MS, pollMs = ANDROID_EMULATOR_SHUTDOWN_POLL_MS, signalGraceMs = EMULATOR_SIGNAL_GRACE_MS, platform = process.platform, resolveDirectory = ownedAvdDirectory, readProcessId = readAvdProcessId, processAlive = pidExists, listProcesses = listAvdEmulatorProcesses, captureIdentity = process_identity_exports.captureProcessIdentity, inspectIdentity = process_identity_exports.inspectProcessIdentity, signal = (pid, name) => signalProcessTree(pid, name, { platform }), directoryExists = (path) => statSync(path).isDirectory(), crashHandlerPids = emulatorCrashHandlerPids, killCrashHandler = (pid) => signalProcessTree(pid, "SIGKILL", { platform }), now = Date.now, sleep = sleepSync } = {}) {
|
|
987
1210
|
const { directory, processId } = resolveAvdProcess(avdName, {
|
|
988
1211
|
platform,
|
|
989
1212
|
resolveDirectory,
|
|
990
1213
|
readProcessId
|
|
991
1214
|
});
|
|
992
|
-
|
|
993
|
-
const
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1215
|
+
const identities = /* @__PURE__ */ new Map();
|
|
1216
|
+
for (const pid of scanAvdEmulatorProcesses(avdName, platform, listProcesses)) {
|
|
1217
|
+
const captured = captureIdentity(pid);
|
|
1218
|
+
identities.set(pid, captured.ok ? {
|
|
1219
|
+
pid,
|
|
1220
|
+
processToken: captured.token
|
|
1221
|
+
} : null);
|
|
1222
|
+
}
|
|
1223
|
+
if (processId === null && identities.size === 0) {
|
|
1224
|
+
if (shutdown) throw new Error(`Could not find the emulator process lock for owned AVD ${avdName}.`);
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
const exited = (pid) => {
|
|
1228
|
+
const record = identities.get(pid);
|
|
1229
|
+
if (!record) return !processAlive(pid);
|
|
1230
|
+
const status = inspectIdentity(record);
|
|
1231
|
+
return status === "gone" || status === "different";
|
|
1232
|
+
};
|
|
1233
|
+
const running = () => {
|
|
1234
|
+
const pids = [...identities.keys()].filter((pid) => !exited(pid));
|
|
1235
|
+
if (processId !== null && !identities.has(processId) && processAlive(processId)) pids.push(processId);
|
|
1236
|
+
return pids;
|
|
1237
|
+
};
|
|
1238
|
+
const waitUntil = (until) => {
|
|
1239
|
+
for (;;) {
|
|
1240
|
+
if (running().length === 0) return true;
|
|
1241
|
+
const remaining = until - now();
|
|
1242
|
+
if (remaining <= 0) return false;
|
|
1243
|
+
sleep(Math.min(pollMs, remaining));
|
|
1244
|
+
}
|
|
1245
|
+
};
|
|
1246
|
+
const started = now();
|
|
1247
|
+
if (shutdown) {
|
|
1248
|
+
const deadline = started + timeoutMs;
|
|
1249
|
+
const shutdownTimeoutMs = deadline - now();
|
|
1250
|
+
if (shutdownTimeoutMs > 0) shutdown(shutdownTimeoutMs);
|
|
1251
|
+
waitUntil(deadline);
|
|
1252
|
+
}
|
|
1253
|
+
for (const name of ["SIGTERM", "SIGKILL"]) {
|
|
1254
|
+
const verified = running().filter((pid) => {
|
|
1255
|
+
const record = identities.get(pid);
|
|
1256
|
+
return record && inspectIdentity(record) === "same";
|
|
1257
|
+
});
|
|
1258
|
+
if (verified.length === 0) break;
|
|
1259
|
+
for (const pid of verified) try {
|
|
1260
|
+
signal(pid, name);
|
|
1261
|
+
} catch {}
|
|
1262
|
+
const graceMs = name === "SIGTERM" && !shutdown ? timeoutMs : signalGraceMs;
|
|
1263
|
+
if (waitUntil(now() + graceMs)) break;
|
|
1264
|
+
}
|
|
1265
|
+
const left = new Set(running());
|
|
1266
|
+
for (const pid of scanAvdEmulatorProcesses(avdName, platform, listProcesses)) {
|
|
1267
|
+
const record = identities.get(pid);
|
|
1268
|
+
if (record === void 0 || (record === null ? processAlive(pid) : inspectIdentity(record) !== "gone")) left.add(pid);
|
|
1269
|
+
}
|
|
1270
|
+
if (left.size) {
|
|
1271
|
+
const scanned = platform !== "win32";
|
|
1272
|
+
const unverified = [...left].filter((pid) => {
|
|
1273
|
+
const record = identities.get(pid);
|
|
1274
|
+
return scanned && (record === null || record !== void 0 && inspectIdentity(record) === "different");
|
|
1275
|
+
});
|
|
1276
|
+
const unlisted = [...left].filter((pid) => scanned && pid === processId && !identities.has(pid));
|
|
1277
|
+
throw new Error(`Owned AVD ${avdName} did not finish shutting down after ${Math.ceil((now() - started) / 1e3)}s: emulator process ${[...left].join(", ")} is still running` + (unverified.length ? ` (Stim could not verify the identity of ${unverified.join(", ")}, so it sent no signal)` : "") + (unlisted.length ? ` (${unlisted.join(", ")} holds the AVD process lock, but its command line does not name the AVD, so Stim sent no signal)` : "") + ".");
|
|
1001
1278
|
}
|
|
1002
|
-
if (platform === "win32") {
|
|
1279
|
+
if (platform === "win32" && processId !== null) {
|
|
1003
1280
|
const handlerDeadline = now() + CRASH_HANDLER_EXIT_TIMEOUT_MS;
|
|
1004
1281
|
for (const handler of crashHandlerPids(processId)) {
|
|
1005
1282
|
while (processAlive(handler) && now() < handlerDeadline) sleep(pollMs);
|
|
@@ -1046,12 +1323,14 @@ function ownedAvdSerialResolver({ timeoutMs } = {}) {
|
|
|
1046
1323
|
const adb = once(() => listAdbDevices(remaining()));
|
|
1047
1324
|
const names = /* @__PURE__ */ new Map();
|
|
1048
1325
|
const avdNameOf = (serial) => {
|
|
1049
|
-
if (
|
|
1050
|
-
|
|
1326
|
+
if (names.has(serial)) return names.get(serial);
|
|
1327
|
+
const name = getAvdNameForSerial(serial, remaining());
|
|
1328
|
+
if (name !== null) names.set(serial, name);
|
|
1329
|
+
return name;
|
|
1051
1330
|
};
|
|
1052
1331
|
return (avdName) => {
|
|
1053
1332
|
if (!avds().includes(avdName)) return { missing: true };
|
|
1054
|
-
if (!
|
|
1333
|
+
if (!isStimOwnedAvd(avdName)) return { notOwned: true };
|
|
1055
1334
|
const devices = adb();
|
|
1056
1335
|
const match = [...devices.emulators, ...devices.unhealthy.filter((entry) => entry.kind === "emulator" && entry.consolePort !== void 0)].find((e) => avdNameOf(e.serial) === avdName);
|
|
1057
1336
|
if (match) return { serial: match.serial };
|
|
@@ -1059,4 +1338,4 @@ function ownedAvdSerialResolver({ timeoutMs } = {}) {
|
|
|
1059
1338
|
};
|
|
1060
1339
|
}
|
|
1061
1340
|
//#endregion
|
|
1062
|
-
export {
|
|
1341
|
+
export { waitForBoot as $, listInstalledSystemImages as A, parseAvdDeviceProfile as B, extractEmulatorFailure as C, listAdbDevices as D, hostSystemImageArch as E, ownedAvdDirectory as F, profileNeedsFoldFeature as G, physicalDeviceModel as H, ownedAvdMatchesConfiguration as I, resolvePhysicalDevice as J, resetAdoptedAvd as K, ownedAvdName as L, memoizeEmulatorProbe as M, nextConsolePort as N, listAvdDeviceProfiles as O, ownedAvdDeviceProfile as P, waitForAndroidEmulatorShutdown as Q, ownedAvdSerialResolver as R, emulatorFailureRemedy as S, getAvdNameForSerial as T, pickDefaultSystemImage as U, parseAvdSystemImage as V, probeEmulatorSerial as W, sleepSync as X, shutdownAndroidEmulator as Y, systemImageSupportsFold as Z, bootAndroidEmulator as _, androidHome as a, dropParked as at, deleteAvd as b, androidSystemImageAbi as c, parkedMaxSetting as ct, assertOwnedAvdStopped as d, selectParked as dt, wipeAvdUserData as et, avdIniPaths as f, configuredAndroidEmulatorApp as ft, avdStorageRoots as g, avdPoolConfiguration as h, androidDeviceAbi as i, adoptParked as it, listOrphanedAvdDirectories as j, listAvds as k, androidToolCwd as l, readParked as lt, avdPathExists as m, OWNED_AVD_CONFIG_DEFAULTS as n, isStimOwnedSim as nt, androidPoolCandidates as o, isLegacyDeletionClaim as ot, avdNameAbsent as p, resolveOwnedAvdSerial as q, androidBuildToolName as r, POOL_SETTING_REMEDY as rt, androidPoolNoCandidatesRefusal as s, parkSim as st, DEFAULT_AVD_DEVICE_PROFILE as t, isStimOwnedAvd as tt, androidToolPath as u, removeParkedAfter as ut, configureNewOwnedAvd as v, findBuildTool as w, emulatorDiskSpaceRemedy as x, createOwnedAvd as y, ownedAvdSystemImage as z };
|