stim 1.10.1 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/{build-progress-BXx-ZBe4.mjs → activity-CCZJs__P.mjs} +9 -182
  2. package/dist/{android-787HhP5w.mjs → android-75Sk2rIR.mjs} +81 -19
  3. package/dist/{android-Bu4mbDzc.mjs → android-B7X-8H7h.mjs} +426 -106
  4. package/dist/{android-DPov-fv2.mjs → android-BQiKJYK0.mjs} +1 -1
  5. package/dist/{android-cas-CITmIeWY.mjs → android-cas-DzLPXiEn.mjs} +7 -7
  6. package/dist/{app-install-Cfmqr3TQ.mjs → app-install-CsZi0SAT.mjs} +1 -1
  7. package/dist/{workspaces-wrH6dXfu.mjs → budget-DbzW5VTD.mjs} +651 -32
  8. package/dist/{dev-client-DExzPiOa.mjs → build-plan-CqntbR7b.mjs} +514 -38
  9. package/dist/build-progress-h60GPV27.mjs +179 -0
  10. package/dist/{cache-manifest-Pubfp8wV.mjs → cache-manifest-DWPF4RKJ.mjs} +1 -1
  11. package/dist/cache-manifest.mjs +1 -1
  12. package/dist/cli.mjs +16 -16
  13. package/dist/collector-run.mjs +4 -4
  14. package/dist/{command-output-D5z2_bcF.mjs → command-output-BWCj5Vh-.mjs} +1 -1
  15. package/dist/{config-BEq0Qw_a.mjs → config-0jCcMM0E.mjs} +10 -1
  16. package/dist/{dependency-state-r9UesA9a.mjs → dependency-state-DNN0yfx6.mjs} +1 -1
  17. package/dist/{deps-DP4eWqea.mjs → deps-CO-_7UOm.mjs} +2 -2
  18. package/dist/{device-Czh5kO8A.mjs → device-9r9xRYDa.mjs} +49 -11
  19. package/dist/{device-lease-CbEaIk7b.mjs → device-lease-B9LAXIUH.mjs} +55 -55
  20. package/dist/{device-pool-BBWLAr55.mjs → device-pool-BoezJd-c.mjs} +4 -4
  21. package/dist/{device-remote-powUHdbK.mjs → device-remote-C0_o-tJH.mjs} +254 -579
  22. package/dist/{doctor-CmqK1LcQ.mjs → doctor-LfDlbR-N.mjs} +20 -19
  23. package/dist/{error-diagnostics-xxWKNO3-.mjs → error-diagnostics-CCxjn31V.mjs} +7 -7
  24. package/dist/{gc-zCDpmdHU.mjs → gc-C4nQFcTX.mjs} +220 -282
  25. package/dist/{guide-DwmaD_Wa.mjs → guide-BPBlh9ML.mjs} +512 -73
  26. package/dist/{guide-status-CWvti2Lg.mjs → guide-status-i9ogHt4v.mjs} +2 -2
  27. package/dist/{idle--jn7pBNe.mjs → idle-S3tFZ53_.mjs} +7 -6
  28. package/dist/{in-use-CNIWU-r-.mjs → in-use-D9XYEgFD.mjs} +7 -7
  29. package/dist/{ios-CbdqTqOc.mjs → ios-CDzWL1mj.mjs} +302 -88
  30. package/dist/{ios-COGcGEcN.mjs → ios-CZWOHTTc.mjs} +24 -19
  31. package/dist/{ios-device-DNjPXBnb.mjs → ios-device-D9cLxVr5.mjs} +1 -1
  32. package/dist/{launch-verify-BhgkYAws.mjs → launch-verify-B2oR_kFN.mjs} +2 -2
  33. package/dist/logs-SzuYudOj.mjs +485 -0
  34. package/dist/merge-state-ytFQCeh4.mjs +262 -0
  35. package/dist/{metro-BWLOJ_Wr.mjs → metro-Dzo30hvJ.mjs} +1 -1
  36. package/dist/{named-ports-DiTfSz6p.mjs → named-ports-Dn1o61D9.mjs} +3 -3
  37. package/dist/{ownership-BfPNRf3-.mjs → ownership-B8d4kXBx.mjs} +49 -16
  38. package/dist/{ownership-D2xbLVoz.mjs → ownership-Dqnrjrio.mjs} +2 -2
  39. package/dist/{ports-CLSSFOuz.mjs → ports-Dp_QTwTC.mjs} +3 -3
  40. package/dist/{project-BXZzOABt.mjs → project-B8rbutVC.mjs} +4 -72
  41. package/dist/{reload-CymyF6Kk.mjs → reload-nocmx8KB.mjs} +9 -9
  42. package/dist/remote-cache-BRsRZru5.mjs +526 -0
  43. package/dist/{server-bare-DVbb7kXH.mjs → server-bare-Gn3ssSdK.mjs} +1 -1
  44. package/dist/{server-expo-BvOCeakp.mjs → server-expo-CeT1nIIk.mjs} +2 -2
  45. package/dist/server-expo-eWi9Coof.mjs +2 -0
  46. package/dist/{settings-DOugPlPz.mjs → settings-BlyDn2Oz.mjs} +14 -5
  47. package/dist/{settings-EWYNwe72.mjs → settings-bE-Gu3Ti.mjs} +23 -11
  48. package/dist/settings.schema.json +73 -1
  49. package/dist/{simslim-DZUxzsjq.mjs → simslim-COtEEP7x.mjs} +152 -96
  50. package/dist/{slot-launch-Dk4oTREM.mjs → slot-launch-DIACGEfH.mjs} +4 -4
  51. package/dist/{spawn-claims-CUleC2cM.mjs → spawn-claims-PT8aQpCa.mjs} +25 -2
  52. package/dist/{start-qWapeB4W.mjs → start-BFstVBlx.mjs} +1 -1
  53. package/dist/{start-RbmFPeZ7.mjs → start-BboE1HIS.mjs} +108 -17
  54. package/dist/{state-WGdIbi_d.mjs → state-DcDgzfu3.mjs} +15 -3
  55. package/dist/{stats-BAgJaLA4.mjs → stats-D8rUTPHd.mjs} +1 -1
  56. package/dist/{stats-DBYJ5bxL.mjs → stats-DiCtmluY.mjs} +4 -4
  57. package/dist/{status-Dr4fRkym.mjs → status-BUASYyy_.mjs} +92 -33
  58. package/dist/{status-Zp8kvexe.mjs → status-DI9Tjo7T.mjs} +255 -38
  59. package/dist/stim-desktop-D0xhpZt8.mjs +29 -0
  60. package/dist/stop-BI7QY76M.mjs +3 -0
  61. package/dist/{stop-DcEv5sk-.mjs → stop-Bn6v-GvS.mjs} +205 -37
  62. package/dist/supervisor-run.mjs +11 -11
  63. package/dist/{workspace-process-lock-BXA96myD.mjs → workspace-process-lock-BvvJTfo_.mjs} +10 -5
  64. package/dist/{worktree-DEG2o9F8.mjs → worktree-B5sTRskL.mjs} +1 -1
  65. package/dist/{worktree-Bd4dRvx7.mjs → worktree-ChDL5fO1.mjs} +17 -16
  66. package/dist/{worktree-BlizKDpZ.mjs → worktree-DeBQSVtR.mjs} +3 -1
  67. package/dist/{xcode-B2tBkf0m.mjs → xcode-BzplpQzc.mjs} +7 -7
  68. package/package.json +5 -5
  69. package/dist/budget-nDCEwtJv.mjs +0 -620
  70. package/dist/logs-MARIy6m9.mjs +0 -209
  71. package/dist/server-expo-DBVTdult.mjs +0 -2
  72. package/dist/stop-DI4BG4Os.mjs +0 -3
@@ -1,10 +1,8 @@
1
- import { D as process_identity_exports, k as ownership_claim_exports } from "./config-BEq0Qw_a.mjs";
1
+ import { O as process_identity_exports } from "./config-0jCcMM0E.mjs";
2
2
  import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
- import { f as clearWorkspaceStateKey, g as updateWorkspaceState, j as workspaceLogsDir, m as readWorkspaceState$1 } from "./metro-BWLOJ_Wr.mjs";
4
- import { a as formatElapsed, u as plural } from "./command-output-D5z2_bcF.mjs";
5
- import { i as listLeaseFiles, r as leaseIsExpired } from "./device-lease-CbEaIk7b.mjs";
3
+ import { j as workspaceLogsDir, m as readWorkspaceState } from "./metro-Dzo30hvJ.mjs";
4
+ import { i as listLeaseFiles, r as leaseIsExpired } from "./device-lease-B9LAXIUH.mjs";
6
5
  import { closeSync, fstatSync, openSync, readFileSync, readSync, readdirSync } from "node:fs";
7
- import { BUILD_PHASES } from "@stim-cli/core/state";
8
6
  import { basename, join, resolve } from "node:path";
9
7
  import { homedir } from "node:os";
10
8
  //#region src/devices/activity.ts
@@ -68,6 +66,9 @@ function parseAgentDeviceRecord(kind, path, raw) {
68
66
  path,
69
67
  kind,
70
68
  deviceId,
69
+ session: kind === "claim" && typeof entry?.session === "string" && entry.session ? entry.session : null,
70
+ workspace: kind === "claim" && typeof entry?.workspace === "string" && entry.workspace ? entry.workspace : null,
71
+ deviceName: kind === "claim" && typeof device?.name === "string" ? device.name : null,
71
72
  readable: Boolean(entry && deviceId),
72
73
  owner: entry ? field(entry, "ownerPid", "ownerStartTime") : null,
73
74
  runner: entry && kind === "runner-lease" ? field(entry, "runnerPid", "runnerStartTime") : null,
@@ -298,7 +299,7 @@ function createActivityReader({ now = Date.now(), home = homedir(), startOf = pr
298
299
  basis: "metro-bundle",
299
300
  at: bundleAt
300
301
  });
301
- const usedAt = Date.parse(String(readWorkspaceState$1(target.workspace)?.lastUsedAt ?? ""));
302
+ const usedAt = Date.parse(String(readWorkspaceState(target.workspace)?.lastUsedAt ?? ""));
302
303
  if (Number.isFinite(usedAt)) evidence.recency.push({
303
304
  basis: "workspace-use",
304
305
  at: usedAt
@@ -309,7 +310,7 @@ function createActivityReader({ now = Date.now(), home = homedir(), startOf = pr
309
310
  }
310
311
  function workspaceActivity(workspace, now = Date.now()) {
311
312
  const dir = workspaceLogsDir(workspace);
312
- const state = readWorkspaceState$1(workspace);
313
+ const state = readWorkspaceState(workspace);
313
314
  const recency = [];
314
315
  const deviceAt = latestRecordAt(tailLines(join(dir, "device.ndjson")) ?? [], (record) => record.src === "device" && isDeviceRecord(record));
315
316
  if (deviceAt !== null) recency.push({
@@ -335,178 +336,4 @@ function workspaceActivity(workspace, now = Date.now()) {
335
336
  }, now);
336
337
  }
337
338
  //#endregion
338
- //#region src/engine/build-progress.ts
339
- const ACTIVE_BUILD_KEY = "activeBuild";
340
- const NO_BUILD_PROGRESS = {
341
- step: () => {},
342
- durations: () => ({}),
343
- clear: () => {}
344
- };
345
- const COLD_PHASES = [
346
- "prebuild",
347
- "pods",
348
- "compile"
349
- ];
350
- const DEVICE_PHASES = ["install", "launch"];
351
- function startBuildProgress({ root, platform, slot, claim, now = Date.now, note = () => {} }) {
352
- const startedAt = new Date(now()).toISOString();
353
- const record = {
354
- platform,
355
- slot,
356
- startedAt,
357
- phase: "prepare",
358
- phaseStartedAt: startedAt,
359
- phases: [{
360
- phase: "prepare",
361
- startedAt
362
- }],
363
- claim: {
364
- root: claim.root,
365
- path: claim.path,
366
- claimId: claim.claimId,
367
- pid: claim.owner.pid
368
- }
369
- };
370
- let warned = false;
371
- const guard = (fn) => {
372
- try {
373
- fn();
374
- } catch (error) {
375
- if (warned) return;
376
- warned = true;
377
- note(`Build progress could not be recorded in the workspace state: ${error?.message || error}`);
378
- }
379
- };
380
- const write = () => guard(() => updateWorkspaceState(root, (state) => ({
381
- ...state,
382
- [ACTIVE_BUILD_KEY]: record
383
- })));
384
- write();
385
- return {
386
- step(phase) {
387
- if (phase === record.phase) return;
388
- const at = new Date(now()).toISOString();
389
- record.phase = phase;
390
- record.phaseStartedAt = at;
391
- record.phases.push({
392
- phase,
393
- startedAt: at
394
- });
395
- write();
396
- },
397
- durations() {
398
- return phaseDurations(record.phases, now());
399
- },
400
- clear() {
401
- guard(() => {
402
- clearWorkspaceStateKey(root, ACTIVE_BUILD_KEY, (value) => parseActiveBuild(value)?.claim.claimId === record.claim.claimId);
403
- });
404
- }
405
- };
406
- }
407
- function phaseDurations(phases, now) {
408
- const out = {};
409
- phases.forEach(({ phase, startedAt }, index) => {
410
- const start = Date.parse(startedAt);
411
- const next = phases[index + 1];
412
- const end = next ? Date.parse(next.startedAt) : now;
413
- if (!Number.isFinite(start) || !Number.isFinite(end)) return;
414
- out[phase] = (out[phase] ?? 0) + Math.max(0, end - start);
415
- });
416
- return out;
417
- }
418
- function parseActiveBuild(value) {
419
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
420
- const record = value;
421
- const claim = record.claim;
422
- if (record.platform !== "ios" && record.platform !== "android") return null;
423
- if (!isPhase(record.phase)) return null;
424
- if (typeof record.startedAt !== "string" || typeof record.phaseStartedAt !== "string") return null;
425
- if (!claim || typeof claim.root !== "string" || typeof claim.claimId !== "string") return null;
426
- const phases = Array.isArray(record.phases) ? record.phases.filter((entry) => isPhase(entry?.phase) && typeof entry?.startedAt === "string") : [];
427
- return {
428
- platform: record.platform,
429
- slot: typeof record.slot === "string" ? record.slot : "default",
430
- startedAt: record.startedAt,
431
- phase: record.phase,
432
- phaseStartedAt: record.phaseStartedAt,
433
- phases,
434
- claim: {
435
- root: claim.root,
436
- path: typeof claim.path === "string" ? claim.path : "",
437
- claimId: claim.claimId,
438
- pid: typeof claim.pid === "number" ? claim.pid : 0
439
- }
440
- };
441
- }
442
- function isPhase(value) {
443
- return BUILD_PHASES.includes(value);
444
- }
445
- function activeBuildState(claim, survey = (0, ownership_claim_exports.readClaimSet)(claim.root)) {
446
- if (survey.live.some((holder) => holder.claimId === claim.claimId)) return "running";
447
- if (survey.dead.some((holder) => holder.claimId === claim.claimId)) return "stale";
448
- return survey.unresolved.length ? "unknown" : "stale";
449
- }
450
- function liveOutcome(record) {
451
- const seen = new Set(record.phases.map((entry) => entry.phase));
452
- if (COLD_PHASES.some((phase) => seen.has(phase))) return "cold";
453
- if (DEVICE_PHASES.some((phase) => seen.has(phase))) return "hit";
454
- return null;
455
- }
456
- function median(values) {
457
- if (!values.length) return null;
458
- const sorted = values.toSorted((a, b) => a - b);
459
- const mid = Math.floor(sorted.length / 2);
460
- return sorted.length % 2 ? sorted[mid] : Math.round((sorted[mid - 1] + sorted[mid]) / 2);
461
- }
462
- function estimateBuild(history, platform, known, phase) {
463
- const lists = history?.[platform];
464
- const outcome = known ?? latestOutcome(lists);
465
- const samples = outcome && lists?.[outcome] || [];
466
- const phaseSamples = samples.flatMap((sample) => phase in sample.phases ? [sample.phases[phase]] : []);
467
- return {
468
- outcome,
469
- expectedMs: median(samples.map((sample) => sample.durationMs)),
470
- expectedPhaseMs: median(phaseSamples),
471
- basis: samples.length
472
- };
473
- }
474
- function latestOutcome(lists) {
475
- let latest = null;
476
- for (const outcome of ["hit", "cold"]) {
477
- const last = lists?.[outcome]?.at(-1);
478
- const at = last ? Date.parse(last.at) : NaN;
479
- if (Number.isFinite(at) && (!latest || at > latest.at)) latest = {
480
- outcome,
481
- at
482
- };
483
- }
484
- return latest?.outcome ?? null;
485
- }
486
- function buildReport(record, { state, history }) {
487
- return {
488
- platform: record.platform,
489
- slot: record.slot,
490
- state,
491
- phase: record.phase,
492
- startedAt: record.startedAt,
493
- phaseStartedAt: record.phaseStartedAt,
494
- ...estimateBuild(history, record.platform, liveOutcome(record), record.phase)
495
- };
496
- }
497
- function remainingText(remainingMs) {
498
- return remainingMs < 6e4 ? "under a minute left" : `about ${Math.ceil(remainingMs / 6e4)} min left`;
499
- }
500
- function buildStatusLine(report, now) {
501
- const slot = report.slot === "default" ? "" : ` [${report.slot}]`;
502
- if (report.state === "stale") return `build: stale ${report.platform}${slot} record from ${report.startedAt} (its run is gone; the next run replaces it)`;
503
- const elapsedMs = Math.max(0, now - Date.parse(report.startedAt));
504
- const head = `build: ${report.platform}${slot} ${report.phase}, ${formatElapsed(elapsedMs)} elapsed`;
505
- if (report.state === "unknown") return `${head} (its native-run claim cannot be resolved, so it may not be running)`;
506
- if (report.expectedMs === null) return head;
507
- const basis = `median of ${plural(report.basis, `${report.outcome} run`)}`;
508
- const remainingMs = report.expectedMs - elapsedMs;
509
- return remainingMs > 0 ? `${head} -- ${remainingText(remainingMs)} (${basis})` : `${head} (usually ~${formatElapsed(report.expectedMs)}, ${basis})`;
510
- }
511
- //#endregion
512
- export { buildStatusLine as a, createActivityReader as c, buildReport as i, idleForMs as l, NO_BUILD_PROGRESS as n, parseActiveBuild as o, activeBuildState as r, startBuildProgress as s, ACTIVE_BUILD_KEY as t, workspaceActivity as u };
339
+ export { workspaceActivity as a, readAgentDeviceRecords as i, createActivityReader as n, idleForMs as r, agentDeviceLiveness as t };
@@ -1,12 +1,33 @@
1
- import { u as getConfigDir } from "./config-BEq0Qw_a.mjs";
1
+ import { _ as loadConfig$1, d as getConfigDir } from "./config-0jCcMM0E.mjs";
2
2
  import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
- import { P as withDirLock, c as signalProcessTree, i as pidExists } from "./metro-BWLOJ_Wr.mjs";
4
- import { c as androidDataPartitionSizeBytes } from "./settings-DOugPlPz.mjs";
3
+ import { P as withDirLock, c as signalProcessTree, i as pidExists } from "./metro-Dzo30hvJ.mjs";
4
+ import { n as stimDesktopInstalled, t as settingDefault } from "./stim-desktop-D0xhpZt8.mjs";
5
+ import { c as androidDataPartitionSizeBytes } from "./settings-BlyDn2Oz.mjs";
5
6
  import { mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
6
7
  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";
7
8
  import { dirname, isAbsolute, join, resolve } from "path";
8
- import { createdDevicesFile, createdDevicesLock, readCreatedDevices, readCreatedDevices as readCreatedDevices$1 } from "@stim-cli/core/state";
9
+ import { ANDROID_EMULATOR_APPS, createdDevicesFile, createdDevicesLock, readCreatedDevices, readCreatedDevices as readCreatedDevices$1, settingDefinition } from "@stim-cli/core/state";
9
10
  import { homedir, tmpdir } from "os";
11
+ //#region src/devices/android-emulator-viewer.ts
12
+ function configuredAndroidEmulatorApp(platform = process.platform) {
13
+ const value = loadConfig$1()?.androidEmulatorApp ?? settingDefault(settingDefinition("androidEmulatorApp"), () => stimDesktopInstalled(platform)).value;
14
+ if (ANDROID_EMULATOR_APPS.includes(value)) return value;
15
+ const error = /* @__PURE__ */ new Error("Invalid androidEmulatorApp in machine config. Use \"emulator\" or \"stim-desktop\".");
16
+ Object.assign(error, { code: "STIM_BAD_ARG" });
17
+ throw error;
18
+ }
19
+ function openEmulatorInStimDesktop(serial) {
20
+ getExecutor().runFileQuiet("open", [
21
+ "-g",
22
+ "-a",
23
+ "Stim",
24
+ `stim-desktop://open?serial=${serial}`
25
+ ], {
26
+ timeoutMs: 5e3,
27
+ killSignal: "SIGKILL"
28
+ });
29
+ }
30
+ //#endregion
10
31
  //#region src/devices/created-devices.ts
11
32
  function update(platform, change) {
12
33
  const dir = getConfigDir();
@@ -70,7 +91,7 @@ const ANDROID_ABIS = /* @__PURE__ */ new Set([
70
91
  "x86",
71
92
  "x86_64"
72
93
  ]);
73
- const DEFAULT_AVD_DEVICE = "pixel_6";
94
+ const DEFAULT_AVD_DEVICE_PROFILE = String(settingDefinition("android.deviceProfile")?.default);
74
95
  function androidHome() {
75
96
  const configured = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
76
97
  if (configured) return configured;
@@ -184,7 +205,19 @@ function pickDefaultSystemImage(images, { systemImage, hostArch } = {}) {
184
205
  if (matching.length === 0) return null;
185
206
  return matching.toSorted((a, b) => pageSizeRank(a) - pageSizeRank(b) || b.api - a.api || (b.tag === "google_apis" ? 1 : 0) - (a.tag === "google_apis" ? 1 : 0))[0] ?? null;
186
207
  }
187
- async function createOwnedAvd(label, { systemImage, spawn = (...args) => getExecutor().spawn(...args) } = {}) {
208
+ const FOLD_FEATURE_PROFILES = /* @__PURE__ */ new Set(["pixel_fold", "resizable"]);
209
+ function profileNeedsFoldFeature(profile) {
210
+ return typeof profile === "string" && FOLD_FEATURE_PROFILES.has(profile);
211
+ }
212
+ function systemImageSupportsFold(pkg, home = androidHome()) {
213
+ try {
214
+ const ini = readFileSync$1(join(home, ...pkg.split(";"), "advancedFeatures.ini"), "utf8");
215
+ return /^\s*SupportPixelFold\s*=\s*on\s*$/im.test(ini);
216
+ } catch {
217
+ return false;
218
+ }
219
+ }
220
+ async function createOwnedAvd(label, { systemImage, deviceProfile = DEFAULT_AVD_DEVICE_PROFILE, spawn = (...args) => getExecutor().spawn(...args) } = {}) {
188
221
  const pick = pickDefaultSystemImage(listInstalledSystemImages(), { systemImage });
189
222
  if (!pick) {
190
223
  const arch = hostSystemImageArch();
@@ -201,7 +234,7 @@ async function createOwnedAvd(label, { systemImage, spawn = (...args) => getExec
201
234
  "-k",
202
235
  pick.pkg,
203
236
  "--device",
204
- DEFAULT_AVD_DEVICE
237
+ deviceProfile
205
238
  ];
206
239
  const child = spawn(tool, args, {
207
240
  detached: true,
@@ -229,17 +262,24 @@ async function createOwnedAvd(label, { systemImage, spawn = (...args) => getExec
229
262
  if (result.code !== 0) throw new Error(`Command failed: ${tool} ${args.join(" ")} (${result.signal ?? result.code})\n${stderr.trim()}`);
230
263
  return {
231
264
  avdName,
232
- systemImage: pick.pkg
265
+ systemImage: pick.pkg,
266
+ deviceProfile
233
267
  };
234
268
  }
235
269
  function parseAvdSystemImage(configIni) {
270
+ const dir = avdIniValue(configIni, "image.sysdir.1")?.replace(/[\\/]+$/, "");
271
+ if (!dir) return null;
272
+ return dir.split(/[\\/]/).join(";");
273
+ }
274
+ function parseAvdDeviceProfile(configIni) {
275
+ return avdIniValue(configIni, "hw.device.name") || null;
276
+ }
277
+ function avdIniValue(configIni, key) {
236
278
  for (const line of String(configIni).split(/\r?\n/)) {
237
279
  const separator = line.indexOf("=");
238
280
  if (separator < 0) continue;
239
- if (line.slice(0, separator).trim() !== "image.sysdir.1") continue;
240
- const dir = line.slice(separator + 1).trim().replace(/[\\/]+$/, "");
241
- if (!dir) return null;
242
- return dir.split(/[\\/]/).join(";");
281
+ if (line.slice(0, separator).trim() !== key) continue;
282
+ return line.slice(separator + 1).trim();
243
283
  }
244
284
  return null;
245
285
  }
@@ -252,6 +292,18 @@ function ownedAvdSystemImage(avdName, { avdDirectory = ownedAvdDirectory, readFi
252
292
  return null;
253
293
  }
254
294
  }
295
+ function ownedAvdDeviceProfile(avdName) {
296
+ const configIni = avdConfigIni(avdName);
297
+ return configIni === null ? null : parseAvdDeviceProfile(configIni);
298
+ }
299
+ const AVDMANAGER_LIST_TIMEOUT_MS = 6e4;
300
+ function listAvdDeviceProfiles() {
301
+ return parseAvdList(getExecutor().runFile(androidToolPath("avdmanager"), [
302
+ "list",
303
+ "device",
304
+ "-c"
305
+ ], { timeoutMs: AVDMANAGER_LIST_TIMEOUT_MS }));
306
+ }
255
307
  function sanitizeAvdLabel(label) {
256
308
  return String(label).replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
257
309
  }
@@ -454,8 +506,9 @@ function nextConsolePort(claimedPorts) {
454
506
  if (claimedPorts.length === 0) return 5554;
455
507
  return Math.max(...claimedPorts) + 2;
456
508
  }
457
- function headlessEmulatorArgs(env = process.env, platform = process.platform) {
509
+ function headlessEmulatorArgs(env = process.env, platform = process.platform, app = "emulator") {
458
510
  const args = [];
511
+ if (platform === "darwin" && app === "stim-desktop") args.push("-no-window", "-gpu", "host");
459
512
  if (platform === "linux" && !env.DISPLAY && !env.WAYLAND_DISPLAY) args.push("-no-window", "-noaudio", "-no-boot-anim", "-gpu", "swiftshader_indirect", "-no-snapshot-save", "-no-snapshot-load");
460
513
  return args;
461
514
  }
@@ -631,10 +684,15 @@ function configureNewOwnedAvd(avdName, { dataPartitionSizeGb, avdConfig = {} },
631
684
  for (const [key, value] of Object.entries(expected)) if (!verified.split(/\r?\n/).includes(`${key}=${value}`)) throw new Error(`Could not verify ${key} in ${configPath}.`);
632
685
  return configPath;
633
686
  }
634
- function avdPoolConfiguration(dataPartitionSizeGb, avdConfig) {
687
+ /**
688
+ * Values every AVD Stim creates gets unless `android.avdConfig` or `android.avdConfigFile` sets the key. The
689
+ * emulator drops gRPC `sendKey` events on an AVD without a hardware keyboard.
690
+ */
691
+ const OWNED_AVD_CONFIG_DEFAULTS = Object.freeze({ "hw.keyboard": "yes" });
692
+ function avdPoolConfiguration(dataPartitionSizeGb, avdConfig, deviceProfile = DEFAULT_AVD_DEVICE_PROFILE) {
635
693
  return JSON.stringify(Object.entries({
636
694
  ...avdConfig,
637
- "hw.device.name": DEFAULT_AVD_DEVICE,
695
+ "hw.device.name": deviceProfile,
638
696
  "disk.dataPartition.size": String(androidDataPartitionSizeBytes(dataPartitionSizeGb))
639
697
  }).toSorted(([a], [b]) => a.localeCompare(b)));
640
698
  }
@@ -751,7 +809,9 @@ function suppressEmulatorCrashConsent({ platform = process.platform, version = e
751
809
  return [];
752
810
  }
753
811
  function bootAndroidEmulator(avdName, consolePort, { logFile, platform = process.platform } = {}) {
754
- const child = getExecutor().spawn(androidToolPath("emulator"), [
812
+ const exec = getExecutor();
813
+ const app = configuredAndroidEmulatorApp(platform);
814
+ const child = exec.spawn(androidToolPath("emulator"), [
755
815
  "-avd",
756
816
  avdName,
757
817
  "-port",
@@ -759,7 +819,7 @@ function bootAndroidEmulator(avdName, consolePort, { logFile, platform = process
759
819
  "-grpc",
760
820
  String(consolePort + 3e3),
761
821
  "-grpc-use-token",
762
- ...headlessEmulatorArgs(),
822
+ ...headlessEmulatorArgs(process.env, platform, app),
763
823
  ...suppressEmulatorCrashConsent({ platform })
764
824
  ], {
765
825
  cwd: androidToolCwd(platform),
@@ -767,7 +827,9 @@ function bootAndroidEmulator(avdName, consolePort, { logFile, platform = process
767
827
  stdio: emulatorStdio(logFile)
768
828
  });
769
829
  child?.unref?.();
770
- return child?.pid ?? null;
830
+ const pid = child?.pid ?? null;
831
+ if (pid !== null && platform === "darwin" && app === "stim-desktop") openEmulatorInStimDesktop(`emulator-${consolePort}`);
832
+ return pid;
771
833
  }
772
834
  function emulatorStdio(logFile) {
773
835
  if (!logFile) return "ignore";
@@ -997,4 +1059,4 @@ function ownedAvdSerialResolver({ timeoutMs } = {}) {
997
1059
  };
998
1060
  }
999
1061
  //#endregion
1000
- export { ownedAvdDirectory as A, resolvePhysicalDevice as B, isStimOwnedAvdName as C, listOrphanedAvdDirectories as D, listInstalledSystemImages as E, physicalDeviceModel as F, wipeAvdUserData as G, sleepSync as H, pickDefaultSystemImage as I, recordCreatedDevice as J, isStimOwnedSim as K, probeEmulatorSerial as L, ownedAvdName as M, ownedAvdSerialResolver as N, memoizeEmulatorProbe as O, ownedAvdSystemImage as P, resetAdoptedAvd as R, hostSystemImageArch as S, listAvds as T, waitForAndroidEmulatorShutdown as U, shutdownAndroidEmulator as V, waitForBoot as W, emulatorDiskSpaceRemedy as _, androidPoolNoCandidatesRefusal as a, findBuildTool as b, androidToolPath as c, avdPoolConfiguration as d, avdStorageRoots as f, deleteAvd as g, createOwnedAvd as h, androidPoolCandidates as i, ownedAvdMatchesConfiguration as j, nextConsolePort as k, assertOwnedAvdStopped as l, configureNewOwnedAvd as m, androidDeviceAbi as n, androidSystemImageAbi as o, bootAndroidEmulator as p, forgetCreatedDevice as q, androidHome as r, androidToolCwd as s, androidBuildToolName as t, avdPathExists as u, emulatorFailureRemedy as v, listAdbDevices as w, getAvdNameForSerial as x, extractEmulatorFailure as y, resolveOwnedAvdSerial as z };
1062
+ export { forgetCreatedDevice as $, listOrphanedAvdDirectories as A, pickDefaultSystemImage as B, getAvdNameForSerial as C, listAvdDeviceProfiles as D, listAdbDevices as E, ownedAvdMatchesConfiguration as F, resolvePhysicalDevice as G, profileNeedsFoldFeature as H, ownedAvdName as I, systemImageSupportsFold as J, shutdownAndroidEmulator as K, ownedAvdSerialResolver as L, nextConsolePort as M, ownedAvdDeviceProfile as N, listAvds as O, ownedAvdDirectory as P, isStimOwnedSim as Q, ownedAvdSystemImage as R, findBuildTool as S, isStimOwnedAvdName as T, resetAdoptedAvd as U, probeEmulatorSerial as V, resolveOwnedAvdSerial as W, waitForBoot as X, waitForAndroidEmulatorShutdown as Y, wipeAvdUserData as Z, createOwnedAvd as _, androidHome as a, emulatorFailureRemedy as b, androidSystemImageAbi as c, assertOwnedAvdStopped as d, recordCreatedDevice as et, avdPathExists as f, configureNewOwnedAvd as g, bootAndroidEmulator as h, androidDeviceAbi as i, memoizeEmulatorProbe as j, listInstalledSystemImages as k, androidToolCwd as l, avdStorageRoots as m, OWNED_AVD_CONFIG_DEFAULTS as n, androidPoolCandidates as o, avdPoolConfiguration as p, sleepSync as q, androidBuildToolName as r, androidPoolNoCandidatesRefusal as s, DEFAULT_AVD_DEVICE_PROFILE as t, configuredAndroidEmulatorApp as tt, androidToolPath as u, deleteAvd as v, hostSystemImageArch as w, extractEmulatorFailure as x, emulatorDiskSpaceRemedy as y, physicalDeviceModel as z };