stim 1.11.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.
Files changed (82) hide show
  1. package/README.md +3 -1
  2. package/dist/{activity-BtG4Va9l.mjs → activity-DfVKZTEB.mjs} +44 -26
  3. package/dist/{android-BA2i_KAV.mjs → android-BHfAu2mf.mjs} +1 -1
  4. package/dist/{android-C1q4l523.mjs → android-CD8pr8Q2.mjs} +112 -54
  5. package/dist/{android-cas-DfJkwCXW.mjs → android-cas-B9Kv9aIy.mjs} +12 -10
  6. package/dist/{android-C4zG2Tuc.mjs → android-qx_31wJo.mjs} +414 -118
  7. package/dist/{app-install-DHut6JnV.mjs → app-install-CDFY2nUL.mjs} +1 -1
  8. package/dist/budget-E5o91NR1.mjs +1739 -0
  9. package/dist/{build-plan-CeAYgKAx.mjs → build-plan-BTa93o_r.mjs} +125 -107
  10. package/dist/{build-progress-CDwogz6R.mjs → build-progress-l7d-X-ZP.mjs} +76 -8
  11. package/dist/{cache-manifest-Pubfp8wV.mjs → cache-manifest-DWPF4RKJ.mjs} +1 -1
  12. package/dist/cache-manifest.mjs +1 -1
  13. package/dist/cdp-Dq_ypAlJ.mjs +115 -0
  14. package/dist/chrome-B5hWgQJS.mjs +61 -0
  15. package/dist/cli.mjs +17 -16
  16. package/dist/collector-run.mjs +15 -9
  17. package/dist/{command-output-D5z2_bcF.mjs → command-output-BIL7rlX7.mjs} +1 -1
  18. package/dist/{config-BEq0Qw_a.mjs → config-0jCcMM0E.mjs} +10 -1
  19. package/dist/created-devices-Cd558h31.mjs +37 -0
  20. package/dist/{dependency-state-r9UesA9a.mjs → dependency-state-DNN0yfx6.mjs} +1 -1
  21. package/dist/{deps-OF7CD5Dx.mjs → deps-2Wo81np9.mjs} +2 -2
  22. package/dist/{device-BwH2iI3O.mjs → device-Cf7vl-so.mjs} +7 -7
  23. package/dist/{device-lease-D83nqeMX.mjs → device-lease-CL9dZf73.mjs} +55 -6
  24. package/dist/{device-pool-DHj7cdPy.mjs → device-pool-MuoiyRcu.mjs} +4 -4
  25. package/dist/{device-remote-BQZSnnmE.mjs → device-remote-DT9ngZiD.mjs} +254 -579
  26. package/dist/{doctor-D8QdMRMf.mjs → doctor-D2iysNNH.mjs} +50 -20
  27. package/dist/{error-diagnostics-UYFSQwRy.mjs → error-diagnostics-BoiOhck8.mjs} +7 -7
  28. package/dist/{gc-COZteyiq.mjs → gc-eA-PRBX8.mjs} +1022 -131
  29. package/dist/{guide-NDvTmMi-.mjs → guide-BfZ0OmiP.mjs} +909 -132
  30. package/dist/{guide-status-CWvti2Lg.mjs → guide-status-BaMYu55M.mjs} +2 -2
  31. package/dist/{idle-NETbeNiQ.mjs → idle-DxLi5OkN.mjs} +43 -10
  32. package/dist/{in-use-DVhlSoyn.mjs → in-use-wYEaKQM9.mjs} +11 -7
  33. package/dist/{ios-CQLKpNfr.mjs → ios-CcfCeZ19.mjs} +103 -58
  34. package/dist/{ios-DArbmwer.mjs → ios-CmEzZiOj.mjs} +31 -22
  35. package/dist/{ios-device-CwDZo0X4.mjs → ios-device-BW-t_Lkv.mjs} +1 -1
  36. package/dist/{launch-verify-CuPDGXdr.mjs → launch-verify-CuJaBpEa.mjs} +33 -20
  37. package/dist/{logs-B44a1UeU.mjs → logs-DcSRbC2t.mjs} +12 -11
  38. package/dist/{merge-state-DDxS-XIZ.mjs → merge-state-ytFQCeh4.mjs} +38 -8
  39. package/dist/{metro-BWLOJ_Wr.mjs → metro-BPeY1YMh.mjs} +5 -5
  40. package/dist/{named-ports-Dc7neAlc.mjs → named-ports-n7tnJsI8.mjs} +78 -6
  41. package/dist/native-runtime-CiiFHt0e.mjs +74 -0
  42. package/dist/{ownership-D2xbLVoz.mjs → ownership-Bz2kETAS.mjs} +2 -2
  43. package/dist/{ownership-BiojdtAp.mjs → ownership-PUP4Dwai.mjs} +175 -217
  44. package/dist/page-BTu7I60K.mjs +29 -0
  45. package/dist/{ports-DwBaRFJs.mjs → ports-C5UutqpJ.mjs} +5 -5
  46. package/dist/{project-BXZzOABt.mjs → project-DGeclhvO.mjs} +55 -87
  47. package/dist/{reload-By9Dwsrt.mjs → reload-C-cV5yy_.mjs} +80 -21
  48. package/dist/remote-cache--aehr4Pr.mjs +526 -0
  49. package/dist/{server-bare-D_RK1RCj.mjs → server-bare-Bq8BpSIo.mjs} +1 -1
  50. package/dist/server-expo-BeYwP_Xy.mjs +2 -0
  51. package/dist/{server-expo-o6H5gcBc.mjs → server-expo-DUCg52-K.mjs} +2 -2
  52. package/dist/{settings-C0ugALSC.mjs → settings-ssVBToDR.mjs} +29 -17
  53. package/dist/{settings-B5oFZEri.mjs → settings-xB0Frkg6.mjs} +13 -4
  54. package/dist/settings.schema.json +74 -2
  55. package/dist/{simslim-CIR3IYJO.mjs → simslim-Bbg9UWrt.mjs} +13 -6
  56. package/dist/{slot-launch-rggO1Esw.mjs → slot-launch-BIWEW-wI.mjs} +4 -4
  57. package/dist/{spawn-claims-BJwXTMPt.mjs → spawn-claims-PT8aQpCa.mjs} +1 -1
  58. package/dist/{start-BSsf2tUs.mjs → start-BZhItkiL.mjs} +110 -18
  59. package/dist/{start-i8zc9VmS.mjs → start-Rw2GD3nV.mjs} +1 -1
  60. package/dist/{state-WGdIbi_d.mjs → state-DwAU2_-D.mjs} +15 -3
  61. package/dist/state-l1pYwOre.mjs +148 -0
  62. package/dist/{stats-DBYJ5bxL.mjs → stats-CaSwmsSZ.mjs} +4 -4
  63. package/dist/{stats-BAgJaLA4.mjs → stats-D8rUTPHd.mjs} +1 -1
  64. package/dist/{status-CitcBRsC.mjs → status-TJlL0HWo.mjs} +649 -116
  65. package/dist/{status-Q-WDEGCB.mjs → status-sszP4XKU.mjs} +130 -28
  66. package/dist/stim-desktop-CXt8-Z3J.mjs +40 -0
  67. package/dist/{stop-DIhrvu0B.mjs → stop-BZ2qMiBP.mjs} +50 -18
  68. package/dist/{stop-rst5mL14.mjs → stop-DskDKNTU.mjs} +2 -2
  69. package/dist/supervisor-run.mjs +11 -11
  70. package/dist/web-run.d.mts +22 -0
  71. package/dist/web-run.mjs +481 -0
  72. package/dist/web-sWXd5iek.mjs +394 -0
  73. package/dist/{worktree-DhxDTK44.mjs → worktree-3cBa-bQt.mjs} +1 -1
  74. package/dist/{worktree-KOjMX3zA.mjs → worktree-D4Do_XiQ.mjs} +229 -21
  75. package/dist/{worktree-BlizKDpZ.mjs → worktree-DeBQSVtR.mjs} +3 -1
  76. package/dist/{xcode-B5SPD--o.mjs → xcode-LM6A8cNZ.mjs} +7 -7
  77. package/package.json +5 -5
  78. package/dist/budget-De3P2rq_.mjs +0 -620
  79. package/dist/native-run-D7yB_AsB.mjs +0 -143
  80. package/dist/server-expo-B-CCUJZj.mjs +0 -2
  81. package/dist/workspace-process-lock-Cx74RSDZ.mjs +0 -57
  82. package/dist/workspaces-COD2xCz-.mjs +0 -1013
@@ -1,27 +1,25 @@
1
- import { H as projectDeviceSlots, O as acquireAvdClaim, S as setDevice, T as withConfigLock$1, g as loadConfig$1, i as clearDevice, t as allConsolePortsAndSerials, u as getConfigDir, v as releaseAndroidConsolePort, x as saveConfig, z as deviceSlotPlatforms } from "./config-BEq0Qw_a.mjs";
1
+ import { B as deviceSlotPlatforms, C as setDevice, E as withConfigLock$1, S as saveConfig, U as projectDeviceSlots, _ as loadConfig$1, a as clearDevice, d as getConfigDir, i as clearAndroidBootPending, k as acquireAvdClaim, t as allConsolePortsAndSerials, y as releaseAndroidConsolePort } from "./config-0jCcMM0E.mjs";
2
2
  import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
- import { S as sharedBuildCache, g as updateWorkspaceState, i as pidExists, k as workspaceId, m as readWorkspaceState$1, s as resolveProjectMetro, x as ensureWorkspaceStorage } from "./metro-BWLOJ_Wr.mjs";
4
- import { D as makeTemporaryDirectory, O as removeTemporaryEntry, c as gitCommonDirOnDisk } from "./worktree-BlizKDpZ.mjs";
5
- import { a as detectIsExpo, c as ownedDeviceLabel, s as isPackageResolvable } from "./project-BXZzOABt.mjs";
6
- import { a as spawnDeclared } from "./spawn-claims-BJwXTMPt.mjs";
7
- import { t as withWorkspaceProcessLock } from "./workspace-process-lock-Cx74RSDZ.mjs";
3
+ import { A as workspaceId, C as sharedBuildCache, _ as updateWorkspaceState, a as pidExists, h as readWorkspaceState$1 } from "./metro-BPeY1YMh.mjs";
4
+ import { D as makeTemporaryDirectory, O as removeTemporaryEntry, c as gitCommonDirOnDisk } from "./worktree-DeBQSVtR.mjs";
5
+ import { c as ownedDeviceLabel, r as detectIsExpo, s as isPackageResolvable } from "./project-DGeclhvO.mjs";
6
+ import { a as spawnDeclared } from "./spawn-claims-PT8aQpCa.mjs";
7
+ import { a as withWorkspaceProcessLock } from "./named-ports-n7tnJsI8.mjs";
8
8
  import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
9
- import { i as formatDuration, l as phaseLine, m as shortHash, u as plural } from "./command-output-D5z2_bcF.mjs";
10
- import { N as storedSources, S as entryDir, x as diffFingerprintSources, y as artifactIn } from "./device-lease-D83nqeMX.mjs";
11
- import { D as listAvds, F as ownedAvdName, H as resolveOwnedAvdSerial, L as ownedAvdSystemImage, M as ownedAvdDeviceProfile, O as listInstalledSystemImages, P as ownedAvdMatchesConfiguration, S as getAvdNameForSerial, T as listAdbDevices, f as avdPoolConfiguration, g as createOwnedAvd, h as configureNewOwnedAvd, j as nextConsolePort, m as bootAndroidEmulator, q as waitForBoot, t as DEFAULT_AVD_DEVICE_PROFILE, u as assertOwnedAvdStopped, z as pickDefaultSystemImage } from "./android-C4zG2Tuc.mjs";
12
- import { l as androidDataPartitionSizeGbSetting, o as androidAvdConfigSetting, v as iosSimSlimProfileSetting } from "./settings-B5oFZEri.mjs";
13
- import { _ as dropParked, b as readParked, d as teardownOwnedAvd, g as adoptParked, m as teardownParkedIosSim, p as teardownParkedAvd, v as isLegacyDeletionClaim, x as selectParked, y as parkedMaxSetting } from "./ownership-BiojdtAp.mjs";
14
- import { E as configuredIosSimulatorViewer, O as hostMemoryPressureAdvice, S as resolveOwnedIosSim, b as resetIosPrivacy, c as iosSimulatorFailureAdvice, d as listIosDeviceTypes, g as parseRuntimeVersion, k as readHostMemoryPressure, l as listAllIosSims, m as ownedSimName, n as bootIosSim, r as createOwnedIosSim, v as renameIosSim, x as resolveIosCreation, y as resetIosKeychain } from "./ios-DArbmwer.mjs";
15
- import { o as estimateBuild } from "./build-progress-CDwogz6R.mjs";
16
- import { i as expoBinRefusal, r as expoBinPath, t as cleanLine } from "./server-expo-o6H5gcBc.mjs";
17
- import { N as easAuthNote } from "./device-remote-BQZSnnmE.mjs";
18
- import { r as LAUNCH_UNVERIFIED, t as LAUNCH_BUNDLING } from "./launch-verify-CuPDGXdr.mjs";
19
- import { a as liveOwnedDeviceCount, i as deviceTypeMismatch, t as reconcileSimSlim } from "./simslim-CIR3IYJO.mjs";
20
- import { s as startDevServer } from "./start-BSsf2tUs.mjs";
21
- import { r as readStats } from "./stats-BAgJaLA4.mjs";
22
- import { d as startBuildHeartbeat, f as tailLines, n as HEARTBEAT_INTERVAL_MS } from "./xcode-B5SPD--o.mjs";
9
+ import { i as formatDuration, l as phaseLine, m as shortHash, u as plural } from "./command-output-BIL7rlX7.mjs";
10
+ import { C as diffFingerprintSources, I as storedSources, w as entryDir, y as artifactIn } from "./device-lease-CL9dZf73.mjs";
11
+ import { $ as waitForBoot, A as listInstalledSystemImages, D as listAdbDevices, I as ownedAvdMatchesConfiguration, L as ownedAvdName, N as nextConsolePort, P as ownedAvdDeviceProfile, T as getAvdNameForSerial, U as pickDefaultSystemImage, _ as bootAndroidEmulator, at as dropParked, ct as parkedMaxSetting, d as assertOwnedAvdStopped, dt as selectParked, h as avdPoolConfiguration, it as adoptParked, k as listAvds, lt as readParked, n as OWNED_AVD_CONFIG_DEFAULTS, ot as isLegacyDeletionClaim, q as resolveOwnedAvdSerial, t as DEFAULT_AVD_DEVICE_PROFILE, v as configureNewOwnedAvd, y as createOwnedAvd, z as ownedAvdSystemImage } from "./android-qx_31wJo.mjs";
12
+ import { l as androidDataPartitionSizeGbSetting, o as androidAvdConfigSetting, v as iosSimSlimProfileSetting } from "./settings-xB0Frkg6.mjs";
13
+ import { f as teardownOwnedAvd, g as teardownParkedIosSim, h as teardownParkedAvd } from "./ownership-PUP4Dwai.mjs";
14
+ import { E as configuredIosSimulatorViewer, O as hostMemoryPressureAdvice, S as resolveOwnedIosSim, b as resetIosPrivacy, c as iosSimulatorFailureAdvice, d as listIosDeviceTypes, f as listIosRuntimes, g as parseRuntimeVersion, k as readHostMemoryPressure, l as listAllIosSims, m as ownedSimName, n as bootIosSim, r as createOwnedIosSim, v as renameIosSim, x as resolveIosCreation, y as resetIosKeychain } from "./ios-CmEzZiOj.mjs";
15
+ import { o as estimateBuild } from "./build-progress-l7d-X-ZP.mjs";
16
+ import { i as expoBinRefusal, r as expoBinPath, t as cleanLine } from "./server-expo-DUCg52-K.mjs";
17
+ import { a as easAuthNote } from "./remote-cache--aehr4Pr.mjs";
18
+ import { a as liveOwnedDeviceCount, i as deviceTypeMismatch, o as runtimeMismatch, t as reconcileSimSlim } from "./simslim-Bbg9UWrt.mjs";
19
+ import { r as readStats } from "./stats-D8rUTPHd.mjs";
20
+ import { d as startBuildHeartbeat, f as tailLines, n as HEARTBEAT_INTERVAL_MS } from "./xcode-LM6A8cNZ.mjs";
23
21
  import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync } from "node:fs";
24
- import { LAST_BUILD_KEYS, isJsonObject, loadConfig, readIdleStop, readJsonObject, readWorkspaceState } from "@stim-cli/core/state";
22
+ import { LAST_BUILD_KEYS, isJsonObject, loadConfig, readJsonObject, readWorkspaceState } from "@stim-cli/core/state";
25
23
  import { basename, dirname, join, relative } from "node:path";
26
24
  import { claimRemoveCommand, clearClaimChild, markClaimChildPending, processGroupAlive, releaseClaim, setClaimChild } from "@stim-cli/core/ownership-claim";
27
25
  import { captureProcessIdentity } from "@stim-cli/core/process-identity";
@@ -93,73 +91,6 @@ async function warmMetro({ port, platform, isExpo, appId, bundleUrl }) {
93
91
  } catch {}
94
92
  }
95
93
  //#endregion
96
- //#region src/commands/native-runtime.ts
97
- const sleep$1 = (ms) => new Promise((r) => setTimeout(r, ms));
98
- async function ensureDevServer({ root, port, settings, remote, note, resolve = resolveProjectMetro, start = startDevServer, readState = readWorkspaceState$1 }) {
99
- const held = port === null ? null : (await resolve(port, root)).metro;
100
- if (port !== null && held) return {
101
- ok: true,
102
- port,
103
- pid: held.pid ?? null,
104
- devServer: null,
105
- reclaimed: []
106
- };
107
- const reason = readIdleStop(readState(root)) ? "stopped (idle)" : "not running";
108
- note(chalk.dim(phaseLine("metro", `dev server ${reason}; starting it${remote ? " for a remote device" : ""}`)));
109
- const result = await start({
110
- root,
111
- settings,
112
- remote,
113
- out: note,
114
- note
115
- });
116
- if (!result.ok) return {
117
- ok: false,
118
- code: result.error.code,
119
- message: `Could not start this workspace's dev server: ${result.error.message}`,
120
- remedy: result.error.remedy,
121
- lines: result.lines,
122
- reclaimed: result.reclaimed
123
- };
124
- const { facts } = result;
125
- return {
126
- ok: true,
127
- port: facts.port,
128
- pid: facts.supervisorPid,
129
- devServer: facts.alreadyRunning ? null : {
130
- started: true,
131
- reason
132
- },
133
- reclaimed: result.reclaimed
134
- };
135
- }
136
- async function ensureWorkspaceStorageSafely(root, { note = (_line) => {} } = {}) {
137
- try {
138
- return ensureWorkspaceStorage(root);
139
- } catch (err) {
140
- note(chalk.dim(`Could not prepare this workspace's Stim state: ${err?.message || err}`));
141
- throw err;
142
- }
143
- }
144
- function launchOutcomeRecord({ launchState, release, bundleId, configuration, metroPort }) {
145
- const unverified = launchState === LAUNCH_UNVERIFIED;
146
- const bundling = launchState === LAUNCH_BUNDLING;
147
- let msg;
148
- if (release) msg = unverified ? `${bundleId} could not be verified as running after its ${configuration} launch` : `${bundleId} is running its embedded ${configuration} bundle`;
149
- else if (unverified) msg = `no bundle request from ${bundleId} reached this workspace's Metro on port ${metroPort}`;
150
- else if (bundling) msg = `${bundleId} requested a bundle from this workspace's Metro on port ${metroPort}; it was still being built when the launch check ended`;
151
- else msg = `${bundleId} fetched a bundle from this workspace's Metro on port ${metroPort}`;
152
- return {
153
- src: "build",
154
- level: unverified ? "warn" : "info",
155
- event: unverified ? "launch_unverified" : bundling ? "launch_bundling" : "launch_verified",
156
- msg
157
- };
158
- }
159
- function isPhysicalDeviceRequest(flag) {
160
- return flag !== null && flag !== void 0 && flag !== false;
161
- }
162
- //#endregion
163
94
  //#region src/engine/device-ios.ts
164
95
  function startIosBoot(udid, configure, label, out, simulatorApp) {
165
96
  const done = (async () => {
@@ -183,13 +114,15 @@ async function ensureOwnedIosDevice({ record, projectPath, slot, settingsRoot, l
183
114
  if (record?.deviceUdid) {
184
115
  if (record.owned) {
185
116
  const resolved = resolveOwnedIosSim(record.deviceUdid);
186
- if (resolved.notOwned) note(chalk.yellow(`Note: recorded sim is now named "${resolved.notOwned}", not Stim-owned by name -- creating a fresh owned sim instead of booting it.`));
117
+ if (resolved.notOwned) note(chalk.yellow(`Note: recorded sim is now named "${resolved.notOwned}", not Stim-owned -- creating a fresh owned sim instead of booting it.`));
187
118
  else if (resolved.missing) {} else {
188
119
  const sim = resolved.sim;
189
120
  const wantedType = flags.deviceType || settings?.ios?.deviceType;
190
121
  const deviceTypes = listIosDeviceTypes();
191
122
  const mismatch = deviceTypeMismatch(sim.deviceTypeIdentifier, wantedType, deviceTypes);
192
123
  if (mismatch) throw new Error(`${mismatch}. Stim will not silently boot a different model. Run \`stim worktree remove\` (or \`stim gc --delete\`) to reap the current sim, then \`stim ios\` again to create the requested one.`);
124
+ const versionMismatch = flags.runtimeFlag ? runtimeMismatch(sim.runtime, flags.runtimeFlag, listIosRuntimes()) : null;
125
+ if (versionMismatch) throw new Error(`${versionMismatch}. Stim will not silently boot a different iOS version. Run \`stim worktree remove\` (or \`stim gc --delete\`) to reap the current sim, then \`stim ios\` again to create the requested one, or pass \`--slot <name>\` to create it beside the current one.`);
193
126
  const model = {
194
127
  model: deviceTypes.find((d) => d.identifier === sim.deviceTypeIdentifier)?.name ?? null,
195
128
  runtime: parseRuntimeVersion(sim.runtime)
@@ -641,7 +574,10 @@ async function prepareOwnedAvd({ projectPath, slot, label, previousAvdName, syst
641
574
  avdName,
642
575
  owned: true,
643
576
  deviceName: avdName,
644
- ...prepared.created ? { poolConfiguration: configuration } : {},
577
+ ...prepared.created ? {
578
+ poolConfiguration: configuration,
579
+ bootPending: true
580
+ } : {},
645
581
  ...prepared.consolePort ? { consolePort: prepared.consolePort } : {}
646
582
  }, slot);
647
583
  });
@@ -676,10 +612,13 @@ async function prepareOwnedAvd({ projectPath, slot, label, previousAvdName, syst
676
612
  const ANDROID_FRESH_BOOT_TIMEOUT_MS = process.platform === "win32" ? 12e5 : 12e4;
677
613
  const ANDROID_BOOT_TIMEOUT_MS = process.platform === "win32" ? 12e5 : 24e4;
678
614
  async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoot, label, settings, flags, note, out, logFile, alive, configureAvd, teardownAvd }) {
679
- const avdConfig = androidAvdConfigSetting(settings, settingsRoot);
615
+ const projectAvdConfig = androidAvdConfigSetting(settings, settingsRoot);
616
+ const avdConfig = {
617
+ ...OWNED_AVD_CONFIG_DEFAULTS,
618
+ ...projectAvdConfig
619
+ };
680
620
  const requestedProfile = flags.deviceProfile || settings.android?.deviceProfile || null;
681
- const deviceProfile = requestedProfile ?? DEFAULT_AVD_DEVICE_PROFILE;
682
- const configuration = avdPoolConfiguration(androidDataPartitionSizeGbSetting(settings), avdConfig, deviceProfile);
621
+ let replacedProfile = null;
683
622
  if (record?.setupIncomplete && record.avdName) {
684
623
  const avdName = record.avdName;
685
624
  const cleanup = teardownAvd(avdName, {
@@ -696,12 +635,36 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
696
635
  if (cleanup.status === "failed" || cleanup.status === "skipped") throw new AvdRecoveryError(`Owned AVD ${record.avdName} has incomplete setup and could not be deleted (${cleanup.reason || cleanup.status}). Fix the cause, then retry; Stim kept the device record for cleanup.`, "Wait for any active AVD operation to finish. If the refusal names an unresolved claim, inspect its creator and native processes before removing only that claim. Retry `stim android` to reconcile the incomplete device.");
697
636
  record = null;
698
637
  }
638
+ const requestedImage = flags.systemImageFlag || null;
639
+ const currentImage = requestedImage && record?.owned && record.avdName ? ownedAvdSystemImage(record.avdName) : null;
640
+ if (record?.avdName && requestedImage && currentImage && currentImage !== requestedImage) {
641
+ const avdName = record.avdName;
642
+ if (!record.bootPending) throw new AvdRecoveryError(`this project's emulator ${avdName} uses system image ${currentImage}, but ${requestedImage} was requested. --system-image replaces only an emulator that never finished a boot, and this one has.`, "Run `stim worktree remove` (or `stim gc --delete`) to reap the current emulator, then `stim android` again to create the requested one, or pass `--slot <name>` to create it beside the current one.");
643
+ replacedProfile = ownedAvdDeviceProfile(avdName);
644
+ const cleanup = teardownAvd(avdName, {
645
+ del: true,
646
+ owner: {
647
+ projectPath,
648
+ slot,
649
+ expectedRecord: record
650
+ },
651
+ onRemoved: () => {
652
+ clearDevice(projectPath, "android", slot, avdName);
653
+ }
654
+ });
655
+ if (cleanup.status === "failed" || cleanup.status === "skipped") throw new AvdRecoveryError(`Owned AVD ${avdName} never finished a boot, but could not be deleted to create one on ${requestedImage} (${cleanup.reason || cleanup.status}). Stim kept the device record.`, "Fix the cause, then retry, or pass `--slot <name>` to create the requested emulator beside it.");
656
+ out(phaseLine("device", `deleted ${avdName}, which never finished a boot, to create one on ${requestedImage}`));
657
+ record = null;
658
+ }
659
+ const deviceProfile = requestedProfile ?? replacedProfile ?? DEFAULT_AVD_DEVICE_PROFILE;
660
+ const configuration = avdPoolConfiguration(androidDataPartitionSizeGbSetting(settings), avdConfig, deviceProfile);
661
+ const configurationBeforeDefaults = avdPoolConfiguration(androidDataPartitionSizeGbSetting(settings), projectAvdConfig, deviceProfile);
699
662
  if (record?.avdName) {
700
663
  if (record.owned) {
701
664
  const resolved = resolveOwnedAvdSerial(record.avdName);
702
665
  const currentProfile = resolved.notOwned || resolved.missing ? null : ownedAvdDeviceProfile(record.avdName);
703
666
  if (requestedProfile && currentProfile && currentProfile !== requestedProfile) throw new AvdRecoveryError(`this project's emulator uses device profile ${currentProfile}, but ${requestedProfile} was requested. Stim will not silently boot a different model.`, "Run `stim worktree remove` (or `stim gc --delete`) to reap the current emulator, then `stim android` again to create the requested one, or pass `--slot <name>` to create it beside the current one.");
704
- if (resolved.notOwned) note(chalk.yellow(`Note: recorded AVD ${record.avdName} is not Stim-owned by name -- creating a fresh owned AVD instead of reusing it.`));
667
+ if (resolved.notOwned) note(chalk.yellow(`Note: recorded AVD ${record.avdName} is not Stim-owned -- creating a fresh owned AVD instead of reusing it.`));
705
668
  else if (resolved.serial) {
706
669
  const consolePort = Number(resolved.serial.replace(/^emulator-/, ""));
707
670
  if (record.consolePort && record.consolePort !== consolePort) out(phaseLine("device", `${record.avdName} changed serial (emulator-${record.consolePort} -> ${resolved.serial}); reconnecting this run, reopen agent-device on ${resolved.serial}`));
@@ -748,7 +711,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
748
711
  }
749
712
  if (parkedMaxSetting("android").max > 0) {
750
713
  const systemImage = pickDefaultSystemImage(listInstalledSystemImages(), { systemImage: flags.systemImage || settings.android?.systemImage })?.pkg;
751
- const candidates = readParked("android").filter((entry) => entry.systemImage === systemImage && entry.configuration === configuration).toSorted((a, b) => a.parkedAt.localeCompare(b.parkedAt));
714
+ const candidates = readParked("android").filter((entry) => entry.systemImage === systemImage && (entry.configuration === configuration || entry.configuration === configurationBeforeDefaults)).toSorted((a, b) => a.parkedAt.localeCompare(b.parkedAt));
752
715
  for (const parked of candidates) {
753
716
  if (isLegacyDeletionClaim(parked.deletionClaim)) continue;
754
717
  const resolved = resolveOwnedAvdSerial(parked.name);
@@ -764,7 +727,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
764
727
  out(phaseLine("device", `kept parked ${parked.name}: ${String(error?.message || error)}`));
765
728
  continue;
766
729
  }
767
- if (ownedAvdSystemImage(parked.name) !== systemImage || !ownedAvdMatchesConfiguration(parked.name, configuration)) {
730
+ if (ownedAvdSystemImage(parked.name) !== systemImage || !ownedAvdMatchesConfiguration(parked.name, parked.configuration)) {
768
731
  const result = teardownParkedAvd(parked.name);
769
732
  if (result.status === "failed") out(phaseLine("device", `kept ${parked.name}: ${result.reason}`));
770
733
  continue;
@@ -773,7 +736,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
773
736
  avdName: parked.name,
774
737
  deviceName: parked.name,
775
738
  owned: true,
776
- poolConfiguration: configuration,
739
+ poolConfiguration: parked.configuration,
777
740
  adoptionPending: true
778
741
  };
779
742
  if (!adoptParked({
@@ -783,6 +746,15 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
783
746
  udid: parked.udid,
784
747
  device: adopted
785
748
  })) continue;
749
+ if (parked.configuration !== configuration) try {
750
+ configureAvd(parked.name, {
751
+ dataPartitionSizeGb: androidDataPartitionSizeGbSetting(settings),
752
+ avdConfig
753
+ });
754
+ adopted.poolConfiguration = configuration;
755
+ } catch (error) {
756
+ out(phaseLine("device", `${parked.name} keeps its old AVD settings: ${String(error?.message || error)}`));
757
+ }
786
758
  return {
787
759
  ...await bootOwnedAvdOnFreshPort({
788
760
  avdName: parked.name,
@@ -824,7 +796,10 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
824
796
  return {
825
797
  ...await bootOwnedAvdOnFreshPort({
826
798
  avdName: created.avdName,
827
- metadata: created.created ? { poolConfiguration: configuration } : void 0,
799
+ metadata: created.created ? {
800
+ poolConfiguration: configuration,
801
+ bootPending: true
802
+ } : void 0,
828
803
  projectPath,
829
804
  slot,
830
805
  deviceName: created.avdName,
@@ -843,6 +818,7 @@ function claimAndroidConsolePort({ projectPath, slot, avdName, deviceName, liveP
843
818
  const claim = {
844
819
  ...metadata?.poolConfiguration ? { poolConfiguration: metadata.poolConfiguration } : {},
845
820
  ...metadata?.adoptionPending ? { adoptionPending: true } : {},
821
+ ...metadata?.bootPending ? { bootPending: true } : {},
846
822
  avdName,
847
823
  consolePort,
848
824
  owned: true,
@@ -880,8 +856,10 @@ async function bootOwnedAvdOnFreshPort({ avdName, metadata, projectPath, slot, d
880
856
  if (result.failed) throw new AvdBootError(result.reason, result.remedy);
881
857
  const running = getAvdNameForSerial(serial);
882
858
  if (running && running !== avdName) throw new Error(`${serial} is running AVD ${running}, not this workspace's owned AVD ${avdName}; refusing to use it.`);
859
+ clearAndroidBootPending(projectPath, avdName, slot);
860
+ const { bootPending: _booted, ...booted } = claim;
883
861
  return {
884
- ...claim,
862
+ ...booted,
885
863
  serial
886
864
  };
887
865
  } catch (error) {
@@ -958,19 +936,24 @@ async function ensureAndroidBooted({ device, projectPath, slot, timeoutMs, out,
958
936
  };
959
937
  if (resolved.notOwned) return {
960
938
  failed: true,
961
- reason: `AVD ${device.avdName} is not Stim-owned by name; refusing to boot it.`
939
+ reason: `AVD ${device.avdName} is not Stim-owned; refusing to boot it.`
962
940
  };
963
- if (resolved.serial) return waitForAndroidBoot({
941
+ const avdName = device.avdName;
942
+ const booted = (result) => {
943
+ if (!result.failed) clearAndroidBootPending(projectPath, avdName, slot);
944
+ return result;
945
+ };
946
+ if (resolved.serial) return booted(await waitForAndroidBoot({
964
947
  serial: resolved.serial,
965
948
  timeoutMs,
966
949
  out
967
- });
950
+ }));
968
951
  const freshSerial = `emulator-${device.consolePort}`;
969
- if (device.owned && device.serial === freshSerial) return waitForAndroidBoot({
952
+ if (device.owned && device.serial === freshSerial) return booted(await waitForAndroidBoot({
970
953
  serial: freshSerial,
971
954
  timeoutMs,
972
955
  out
973
- });
956
+ }));
974
957
  const claim = claimAndroidConsolePort({
975
958
  projectPath,
976
959
  slot,
@@ -1000,7 +983,7 @@ async function ensureAndroidBooted({ device, projectPath, slot, timeoutMs, out,
1000
983
  out
1001
984
  });
1002
985
  if (result.failed) releaseAndroidConsolePort(projectPath, claim.consolePort, slot);
1003
- return result;
986
+ return booted(result);
1004
987
  }
1005
988
  //#endregion
1006
989
  //#region src/engine/device.ts
@@ -1776,6 +1759,28 @@ function explainBuildMiss({ root, platform, current, rekeyedBy = [], baselineDep
1776
1759
  previousHash: baseline?.fingerprint ?? null
1777
1760
  };
1778
1761
  }
1762
+ function predictBuildMiss({ root, platform, current, prebuild }) {
1763
+ const { reason } = explainBuildMiss({
1764
+ root,
1765
+ platform,
1766
+ current
1767
+ });
1768
+ const baseline = reason.baseline && {
1769
+ fingerprint: reason.baseline.fingerprint,
1770
+ from: reason.baseline.from
1771
+ };
1772
+ if (!prebuild || !baseline) return {
1773
+ ...reason,
1774
+ baseline
1775
+ };
1776
+ const dir = platform === "ios" ? "ios/" : "android/";
1777
+ return {
1778
+ ...reason,
1779
+ kind: "prebuild-pending",
1780
+ summary: `${reason.summary} (before prebuild ${prebuild}s ${dir})`,
1781
+ baseline
1782
+ };
1783
+ }
1779
1784
  function skippedMissReason(summary) {
1780
1785
  return {
1781
1786
  kind: "cache-skipped",
@@ -1887,6 +1892,7 @@ async function planCachedBuild(lookup, deps) {
1887
1892
  }
1888
1893
  let prebuild = null;
1889
1894
  let refusal = null;
1895
+ let missReason;
1890
1896
  if (!cacheHit) {
1891
1897
  prebuild = deps.planPrebuild(root, platform, {
1892
1898
  isExpo,
@@ -1894,6 +1900,15 @@ async function planCachedBuild(lookup, deps) {
1894
1900
  sources
1895
1901
  });
1896
1902
  if (prebuild === "refuse") refusal = staleNativeDirRefusal(platform);
1903
+ else if (cachePolicy.read) missReason = predictBuildMiss({
1904
+ root,
1905
+ platform,
1906
+ current: {
1907
+ hash: fingerprint,
1908
+ sources
1909
+ },
1910
+ prebuild: prebuild === "generate" || prebuild === "regenerate" ? prebuild : null
1911
+ });
1897
1912
  }
1898
1913
  return planPayload(lookup, {
1899
1914
  fingerprint,
@@ -1902,11 +1917,12 @@ async function planCachedBuild(lookup, deps) {
1902
1917
  provider,
1903
1918
  cacheSkipped: !cachePolicy.read,
1904
1919
  prebuild,
1920
+ ...missReason ? { missReason } : {},
1905
1921
  refusal
1906
1922
  });
1907
1923
  }
1908
1924
  function planPayload({ platform, slot, projectKey }, found) {
1909
- const { refusal, ...rest } = found;
1925
+ const { refusal, missReason, ...rest } = found;
1910
1926
  const outcome = refusal ? null : found.cacheHit ? "hit" : "cold";
1911
1927
  const estimate = outcome ? estimateBuild(readStats().record?.history?.[projectKey], platform, outcome, "prepare") : {
1912
1928
  expectedMs: null,
@@ -1919,6 +1935,7 @@ function planPayload({ platform, slot, projectKey }, found) {
1919
1935
  outcome,
1920
1936
  expectedMs: estimate.expectedMs,
1921
1937
  basis: estimate.basis,
1938
+ ...missReason ? { missReason } : {},
1922
1939
  ...refusal ? { refusal } : {}
1923
1940
  };
1924
1941
  }
@@ -1935,6 +1952,7 @@ function printPlan(plan, json) {
1935
1952
  }
1936
1953
  const slot = plan.slot ? ` [${plan.slot}]` : "";
1937
1954
  console.log(phaseLine("plan", `${plan.platform}${slot} ${shortHash(plan.fingerprint)} -> ${cacheLine(plan)}`));
1955
+ if (plan.missReason) console.log(phaseLine("cache", `miss: ${plan.missReason.summary}`));
1938
1956
  if (plan.refusal) {
1939
1957
  console.log(phaseLine("error", plan.refusal.message));
1940
1958
  console.log(phaseLine("remedy", plan.refusal.remedy));
@@ -1963,4 +1981,4 @@ function planFlagRefusal(flag) {
1963
1981
  };
1964
1982
  }
1965
1983
  //#endregion
1966
- export { ensureDevServer as C, sleep$1 as D, launchOutcomeRecord as E, warmMetro as O, clearIosAdoptionPending as S, isPhysicalDeviceRequest as T, staleNativeDirRefusal as _, refusePlan as a, AvdBootError as b, skippedMissReason as c, schemesFromInfoPlist as d, detectEntryFile as f, runPrebuild as g, recordPrebuild as h, printPlan as i, devClientScheme as l, planPrebuild as m, planFlagRefusal as n, explainBuildMiss as o, swapJsBundle as p, planPayload as r, fingerprintErrorMissReason as s, planCachedBuild as t, pickDevClientScheme as u, ensureBooted as v, ensureWorkspaceStorageSafely as w, AvdRecoveryError as x, ensureOwnedDevice as y };
1984
+ export { warmMetro as C, clearIosAdoptionPending as S, staleNativeDirRefusal as _, refusePlan as a, AvdBootError as b, skippedMissReason as c, schemesFromInfoPlist as d, detectEntryFile as f, runPrebuild as g, recordPrebuild as h, printPlan as i, devClientScheme as l, planPrebuild as m, planFlagRefusal as n, explainBuildMiss as o, swapJsBundle as p, planPayload as r, fingerprintErrorMissReason as s, planCachedBuild as t, pickDevClientScheme as u, ensureBooted as v, AvdRecoveryError as x, ensureOwnedDevice as y };
@@ -1,7 +1,7 @@
1
- import { k as ownership_claim_exports } from "./config-BEq0Qw_a.mjs";
2
- import { f as clearWorkspaceStateKey, g as updateWorkspaceState } from "./metro-BWLOJ_Wr.mjs";
3
- import { a as formatElapsed, u as plural } from "./command-output-D5z2_bcF.mjs";
4
- import { BUILD_PHASES } from "@stim-cli/core/state";
1
+ import { A as ownership_claim_exports } from "./config-0jCcMM0E.mjs";
2
+ import { _ as updateWorkspaceState, p as clearWorkspaceStateKey } from "./metro-BPeY1YMh.mjs";
3
+ import { a as formatElapsed, u as plural } from "./command-output-BIL7rlX7.mjs";
4
+ import { BUILD_HISTORY_KEY, BUILD_HISTORY_LIMIT, BUILD_PHASES, LAST_BUILD_KEYS } from "@stim-cli/core/state";
5
5
  //#region src/engine/build-progress.ts
6
6
  const ACTIVE_BUILD_KEY = "activeBuild";
7
7
  const NO_BUILD_PROGRESS = {
@@ -44,11 +44,11 @@ function startBuildProgress({ root, platform, slot, claim, now = Date.now, note
44
44
  note(`Build progress could not be recorded in the workspace state: ${error?.message || error}`);
45
45
  }
46
46
  };
47
- const write = () => guard(() => updateWorkspaceState(root, (state) => ({
48
- ...state,
47
+ const write = (before = (state) => state) => guard(() => updateWorkspaceState(root, (state) => ({
48
+ ...before(state),
49
49
  [ACTIVE_BUILD_KEY]: record
50
50
  })));
51
- write();
51
+ write(withInterruptedBuild);
52
52
  return {
53
53
  step(phase) {
54
54
  if (phase === record.phase) return;
@@ -82,6 +82,74 @@ function phaseDurations(phases, now) {
82
82
  });
83
83
  return out;
84
84
  }
85
+ const HISTORY_FIELDS = [
86
+ "platform",
87
+ "status",
88
+ "configuration",
89
+ "fingerprint",
90
+ "cacheKey",
91
+ "cacheHit",
92
+ "cacheSkipped",
93
+ "durationMs",
94
+ "startedAt",
95
+ "errorCode",
96
+ "missReason",
97
+ "diagnostics"
98
+ ];
99
+ function withHistoryEntry(state, platform, entry) {
100
+ const saved = state[BUILD_HISTORY_KEY];
101
+ const history = saved && typeof saved === "object" && !Array.isArray(saved) ? saved : {};
102
+ const list = Array.isArray(history[platform]) ? history[platform] : [];
103
+ return {
104
+ ...state,
105
+ [BUILD_HISTORY_KEY]: {
106
+ ...history,
107
+ [platform]: [entry, ...list].slice(0, BUILD_HISTORY_LIMIT)
108
+ }
109
+ };
110
+ }
111
+ function finishedResult(record) {
112
+ if (record.status === "ok") return "succeeded";
113
+ return record.errorCode === "STIM_CANCELLED" ? "cancelled" : "failed";
114
+ }
115
+ function recordFinishedBuild(root, record, { update = updateWorkspaceState, now = Date.now } = {}) {
116
+ const platform = record.platform;
117
+ update(root, (state) => {
118
+ const active = parseActiveBuild(state[ACTIVE_BUILD_KEY]);
119
+ const own = active?.platform === platform ? active : null;
120
+ const entry = {
121
+ ...Object.fromEntries(HISTORY_FIELDS.filter((key) => key in record).map((key) => [key, record[key]])),
122
+ result: finishedResult(record),
123
+ slot: own?.slot ?? "default",
124
+ phases: own ? phaseDurations(own.phases, now()) : {}
125
+ };
126
+ return withHistoryEntry({
127
+ ...state,
128
+ lastBuild: record,
129
+ [LAST_BUILD_KEYS[platform]]: record
130
+ }, platform, entry);
131
+ });
132
+ }
133
+ function withInterruptedBuild(state) {
134
+ const left = parseActiveBuild(state[ACTIVE_BUILD_KEY]);
135
+ if (!left) return state;
136
+ const last = state[LAST_BUILD_KEYS[left.platform]];
137
+ if (Date.parse(String(last?.startedAt)) >= Date.parse(left.startedAt)) return state;
138
+ return withHistoryEntry(state, left.platform, {
139
+ platform: left.platform,
140
+ status: "failed",
141
+ result: "interrupted",
142
+ slot: left.slot,
143
+ configuration: null,
144
+ fingerprint: null,
145
+ cacheKey: null,
146
+ cacheHit: false,
147
+ cacheSkipped: false,
148
+ durationMs: null,
149
+ startedAt: left.startedAt,
150
+ phases: phaseDurations(left.phases, Date.parse(left.phaseStartedAt))
151
+ });
152
+ }
85
153
  function parseActiveBuild(value) {
86
154
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
87
155
  const record = value;
@@ -176,4 +244,4 @@ function buildStatusLine(report, now) {
176
244
  return remainingMs > 0 ? `${head} -- ${remainingText(remainingMs)} (${basis})` : `${head} (usually ~${formatElapsed(report.expectedMs)}, ${basis})`;
177
245
  }
178
246
  //#endregion
179
- export { buildStatusLine as a, startBuildProgress as c, buildReport as i, NO_BUILD_PROGRESS as n, estimateBuild as o, activeBuildState as r, parseActiveBuild as s, ACTIVE_BUILD_KEY as t };
247
+ export { buildStatusLine as a, recordFinishedBuild as c, buildReport as i, startBuildProgress as l, NO_BUILD_PROGRESS as n, estimateBuild as o, activeBuildState as r, parseActiveBuild as s, ACTIVE_BUILD_KEY as t };
@@ -1,4 +1,4 @@
1
- import { u as getConfigDir } from "./config-BEq0Qw_a.mjs";
1
+ import { d as getConfigDir } from "./config-0jCcMM0E.mjs";
2
2
  import { existsSync } from "fs";
3
3
  import { isAbsolute, join, resolve } from "path";
4
4
  import { readCacheManifest, updateCacheManifest } from "@stim-cli/core";
@@ -1,2 +1,2 @@
1
- import { a as unregister, i as registeredCaches, n as readManifest, r as register, t as manifestPath } from "./cache-manifest-Pubfp8wV.mjs";
1
+ import { a as unregister, i as registeredCaches, n as readManifest, r as register, t as manifestPath } from "./cache-manifest-DWPF4RKJ.mjs";
2
2
  export { manifestPath, readManifest, register, registeredCaches, unregister };
@@ -0,0 +1,115 @@
1
+ import { WebSocket } from "ws";
2
+ //#region src/web/cdp.ts
3
+ const COMMAND_TIMEOUT_MS = 15e3;
4
+ function connectCdp(url, { timeoutMs = 5e3 } = {}) {
5
+ return new Promise((resolve, reject) => {
6
+ const socket = new WebSocket(url, {
7
+ perMessageDeflate: false,
8
+ maxPayload: 268435456
9
+ });
10
+ const pending = /* @__PURE__ */ new Map();
11
+ const eventListeners = [];
12
+ const closeListeners = [];
13
+ let nextId = 1;
14
+ let closed = false;
15
+ const timer = setTimeout(() => {
16
+ socket.terminate();
17
+ reject(/* @__PURE__ */ new Error(`DevTools did not accept a connection at ${url} within ${timeoutMs}ms`));
18
+ }, timeoutMs);
19
+ socket.on("message", (data) => {
20
+ let message;
21
+ try {
22
+ message = JSON.parse(String(data));
23
+ } catch {
24
+ return;
25
+ }
26
+ if (typeof message.id === "number") {
27
+ const waiter = pending.get(message.id);
28
+ if (!waiter) return;
29
+ pending.delete(message.id);
30
+ if (message.error) waiter.reject(new Error(message.error.message ?? "DevTools command failed"));
31
+ else waiter.resolve(message.result ?? {});
32
+ return;
33
+ }
34
+ if (typeof message.method === "string") {
35
+ const event = {
36
+ method: message.method,
37
+ params: message.params ?? {},
38
+ ...message.sessionId ? { sessionId: message.sessionId } : {}
39
+ };
40
+ for (const listener of eventListeners) listener(event);
41
+ }
42
+ });
43
+ socket.on("close", () => {
44
+ closed = true;
45
+ for (const waiter of pending.values()) waiter.reject(/* @__PURE__ */ new Error("DevTools connection closed"));
46
+ pending.clear();
47
+ for (const listener of closeListeners) listener();
48
+ });
49
+ socket.on("error", (error) => {
50
+ clearTimeout(timer);
51
+ reject(error);
52
+ });
53
+ socket.on("open", () => {
54
+ clearTimeout(timer);
55
+ resolve({
56
+ send(method, params = {}, sessionId) {
57
+ if (closed) return Promise.reject(/* @__PURE__ */ new Error("DevTools connection closed"));
58
+ const id = nextId++;
59
+ return new Promise((resolveCommand, rejectCommand) => {
60
+ const commandTimer = setTimeout(() => {
61
+ pending.delete(id);
62
+ rejectCommand(/* @__PURE__ */ new Error(`DevTools command ${method} timed out`));
63
+ }, COMMAND_TIMEOUT_MS);
64
+ pending.set(id, {
65
+ resolve: (value) => {
66
+ clearTimeout(commandTimer);
67
+ resolveCommand(value);
68
+ },
69
+ reject: (error) => {
70
+ clearTimeout(commandTimer);
71
+ rejectCommand(error);
72
+ }
73
+ });
74
+ socket.send(JSON.stringify({
75
+ id,
76
+ method,
77
+ params,
78
+ ...sessionId ? { sessionId } : {}
79
+ }));
80
+ });
81
+ },
82
+ onEvent(listener) {
83
+ eventListeners.push(listener);
84
+ },
85
+ onClose(listener) {
86
+ if (closed) listener();
87
+ else closeListeners.push(listener);
88
+ },
89
+ close() {
90
+ socket.close();
91
+ }
92
+ });
93
+ });
94
+ });
95
+ }
96
+ /**
97
+ * Connects to the browser DevTools endpoint on loopback `port` only when the process serving it is `chromePid`,
98
+ * the Chrome Stim started. Chrome reports its own browser process through `SystemInfo.getProcessInfo`.
99
+ */
100
+ async function connectOwnedBrowser(port, chromePid) {
101
+ const { webSocketDebuggerUrl } = await (await fetch(`http://127.0.0.1:${port}/json/version`, { signal: AbortSignal.timeout(2e3) })).json();
102
+ if (typeof webSocketDebuggerUrl !== "string" || !webSocketDebuggerUrl.startsWith(`ws://127.0.0.1:${port}/`)) throw new Error(`port ${port} does not serve a browser DevTools endpoint`);
103
+ const cdp = await connectCdp(webSocketDebuggerUrl);
104
+ try {
105
+ const { processInfo } = await cdp.send("SystemInfo.getProcessInfo");
106
+ const browser = processInfo?.find((entry) => entry.type === "browser")?.id;
107
+ if (browser !== chromePid) throw new Error(`port ${port} is served by browser pid ${browser ?? "unknown"}, not the owned Chrome ${chromePid}`);
108
+ return cdp;
109
+ } catch (error) {
110
+ cdp.close();
111
+ throw error;
112
+ }
113
+ }
114
+ //#endregion
115
+ export { connectOwnedBrowser as t };