stim 1.19.0 → 1.20.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 (172) hide show
  1. package/README.md +20 -3
  2. package/dist/{activity-BSw0x84R.mjs → activity-BrGh9EAI.mjs} +3 -3
  3. package/dist/{agent-device-usage-output-Bjxu_VmX.mjs → agent-device-usage-output-DVyv_hWj.mjs} +7 -7
  4. package/dist/android-0vv-OruW.mjs +6 -0
  5. package/dist/android-Brx6EKvJ.mjs +1258 -0
  6. package/dist/{android-BdQC6zs1.mjs → android-BsYNR81W.mjs} +103 -45
  7. package/dist/{android-DE5g_5gv.mjs → android-CK3fpEyZ.mjs} +2 -2
  8. package/dist/{android-cas-C87geAg-.mjs → android-cas-DCAuXryb.mjs} +9 -5
  9. package/dist/android-cas-compiler.mjs +1 -1
  10. package/dist/api-run.mjs +159 -8
  11. package/dist/{api-DIf_s86s.mjs → api-zuX3ynYN.mjs} +5 -1
  12. package/dist/api.d.mts +98 -4
  13. package/dist/api.mjs +1 -1
  14. package/dist/{app-install-CFI4ptUL.mjs → app-install-0LgaQRT7.mjs} +9 -8
  15. package/dist/artifact-CnC0mfov.mjs +807 -0
  16. package/dist/artifact-DClXJNWx.mjs +772 -0
  17. package/dist/artifact-lifecycle-DzPpBYvK.mjs +30 -0
  18. package/dist/{browser-web-CDhqQxhv.mjs → browser-web-CzBmhmgs.mjs} +19 -19
  19. package/dist/{budget-Dz-kyXLv.mjs → budget-BONq9vXY.mjs} +3101 -3062
  20. package/dist/build-BixRI6sB.mjs +462 -0
  21. package/dist/{build-facts-B87vo-Im.d.mts → build-facts-DcLv2HSd.d.mts} +1 -32
  22. package/dist/build-plan-CP0tckNU.mjs +553 -0
  23. package/dist/{cache-manifest-DiwKSyv9.mjs → cache-manifest-qA3dT2x2.mjs} +1 -1
  24. package/dist/cache-manifest.mjs +1 -1
  25. package/dist/{manifest-CX0d1IkZ.mjs → ccache-CjHQ5F8g.mjs} +13 -19
  26. package/dist/{chrome-BTUOo8p2.mjs → chrome-Dp2lMLBe.mjs} +1 -1
  27. package/dist/{cli-C9Zvgbdc.mjs → cli-B2R8wH1F.mjs} +20 -20
  28. package/dist/cli.mjs +1 -1
  29. package/dist/{client-CuUo8Nvg.mjs → client-BLy1Pw2V.mjs} +89 -307
  30. package/dist/collector-run.d.mts +4 -0
  31. package/dist/collector-run.mjs +7 -7
  32. package/dist/{command-output-Bt8ygHE7.mjs → command-output-BjPN99ZB.mjs} +2 -2
  33. package/dist/{config-DC5jEtd1.mjs → config-D0LhTY3S.mjs} +8 -12
  34. package/dist/{created-devices-D0s26Hx5.mjs → created-devices-HU_DAb_Q.mjs} +7 -6
  35. package/dist/{dependency-state-DKaS1OM9.mjs → dependency-state-B2m8eqsS.mjs} +2 -2
  36. package/dist/{detached-entry-pB_IPfV4.mjs → detached-entry-BXq4fIl-.mjs} +1 -1
  37. package/dist/{dev-client-CDY_zAmD.mjs → dev-client-ClATaJoH.mjs} +2 -2
  38. package/dist/{device-BHw0WlHl.mjs → device-B7lsC0K1.mjs} +8 -8
  39. package/dist/device-DVCwAFlX.mjs +727 -0
  40. package/dist/{device-capacity-BoN1jQkO.mjs → device-capacity-Ca_ke1LE.mjs} +13 -14
  41. package/dist/device-host-worker.mjs +38 -18
  42. package/dist/device-ios-CRUqPxKw.mjs +413 -0
  43. package/dist/{device-lease-Cu7PtQpZ.mjs → device-lease-DHOBXGgS.mjs} +135 -22
  44. package/dist/{device-lease-run-D38SnZtt.mjs → device-lease-run-CvDeHTkG.mjs} +4 -4
  45. package/dist/{device-pool-CP4rAsHx.mjs → device-pool-L2-5GxMy.mjs} +3 -3
  46. package/dist/{device-remote-B5GiUifk.mjs → device-remote-Bmg42063.mjs} +17 -68
  47. package/dist/doctor-C2MSuMMg.mjs +515 -0
  48. package/dist/{doctor-PRJH94v6.mjs → doctor-DPAqhVCK.mjs} +333 -744
  49. package/dist/{doctor-watchman-wZhyTdp4.mjs → doctor-watchman-CB4NnVZm.mjs} +13 -12
  50. package/dist/{simslim-D9LkJxIO.mjs → eas-build-Dw_ec2Km.mjs} +6 -152
  51. package/dist/{stim-installations-DbmUjsLv.mjs → eas-session-ledger-DZlC-Fq3.mjs} +57 -4
  52. package/dist/{error-diagnostics--v5HGOU0.mjs → error-diagnostics-Du7I_GuY.mjs} +17 -12
  53. package/dist/errors-DzkMYh8Q.mjs +205 -0
  54. package/dist/{exec-i8GDPwq9.mjs → exec-B81Qodjn.mjs} +40 -10
  55. package/dist/{gc-CyHMcKcn.mjs → gc-PWp34zGb.mjs} +21 -20
  56. package/dist/{device-ios-B2ruohE-.mjs → gradle-C2OEKCCI.mjs} +21 -650
  57. package/dist/{guide-dRaQJJk9.mjs → guide-Dai9lphv.mjs} +504 -117
  58. package/dist/{hosted-android-Bs7fytj7.mjs → hosted-android-DQ-Agsd8.mjs} +5 -5
  59. package/dist/{hosted-client-C5G4h_OJ.mjs → hosted-client-Io01s5By.mjs} +9 -3
  60. package/dist/{hosted-ios-Bik035eL.mjs → hosted-ios-DIxFnYp-.mjs} +3 -3
  61. package/dist/{hosted-logs-CI9Az9a4.mjs → hosted-logs-DP-ABU_M.mjs} +8 -8
  62. package/dist/{hosted-macos-DABm3ScO.mjs → hosted-macos-Aziw9T_9.mjs} +7 -6
  63. package/dist/{hosted-native-DbV2aUVw.mjs → hosted-native-BdAQi21M.mjs} +17 -14
  64. package/dist/{idle-shutdown-DYiMBn34.mjs → idle-shutdown-DdMqoSUB.mjs} +7 -7
  65. package/dist/{ios-DYUr9LGc.mjs → ios-BZ9dg2Fr.mjs} +8 -7
  66. package/dist/{ios-BWbHFZVz.mjs → ios-CXLIUxXJ.mjs} +180 -24
  67. package/dist/ios-CZuv6gRv.mjs +1112 -0
  68. package/dist/{ios-DCrkgXEM.mjs → ios-VGvNdsv2.mjs} +1 -1
  69. package/dist/{ios-device-CrW8aMb5.mjs → ios-device-B1B14xND.mjs} +1 -1
  70. package/dist/{ios-device-CJ_ZOGky.mjs → ios-device-BreZOpM0.mjs} +3 -3
  71. package/dist/ios-project-B9drt_aY.mjs +197 -0
  72. package/dist/{ios-state-By0XuLTQ.mjs → ios-state-CjrL2qv-.mjs} +1 -1
  73. package/dist/js-swap-C8icNYIY.mjs +432 -0
  74. package/dist/launch-DxH0THLP.mjs +745 -0
  75. package/dist/{react-native-ios-CSpIxexZ.mjs → launch-YrbqaIFj.mjs} +59 -612
  76. package/dist/{launch-verify-DCveSE6d.mjs → launch-verify-BzRdbuO1.mjs} +26 -7
  77. package/dist/{logs-Bao1HOMy.mjs → logs-DHaRNfg7.mjs} +11 -11
  78. package/dist/macos-C0iljhsN.mjs +2 -0
  79. package/dist/{macos-ciyBdTY5.mjs → macos-V_3Ui7g4.mjs} +42 -50
  80. package/dist/macos-run.mjs +1 -1
  81. package/dist/maintenance-run.mjs +13 -10
  82. package/dist/{metro-DI2AjPF_.mjs → metro-D_j9sN-B.mjs} +18 -5
  83. package/dist/{metro-gateway-BxpJLIwh.mjs → metro-gateway-BBJldOdi.mjs} +2 -2
  84. package/dist/miss-reason-BPAzZS0r.mjs +238 -0
  85. package/dist/{named-ports-B4iWH4E-.mjs → named-ports-C2lvoPsb.mjs} +3 -3
  86. package/dist/native-android-lHzaew7r.mjs +226 -0
  87. package/dist/native-gradle-inputs-BQnIdAlL.mjs +356 -0
  88. package/dist/{native-runtime-BGxsbfny.mjs → native-runtime-BRmWL7ru.mjs} +9 -8
  89. package/dist/native-xcode-inputs-eutS19jU.mjs +307 -0
  90. package/dist/native-xcode-ios-DP3cHLwu.mjs +434 -0
  91. package/dist/{ndjson-LGZAMsqa.mjs → ndjson-GOnbyvso.mjs} +6 -6
  92. package/dist/offload-worker.d.mts +41 -7
  93. package/dist/offload-worker.mjs +409 -74
  94. package/dist/{ownership-EB0ySdGK.mjs → ownership-DXzMMSx6.mjs} +2 -2
  95. package/dist/{placement-log-Dxe34rJm.mjs → placement-log-reFbjqEr.mjs} +1 -1
  96. package/dist/plan-BDk2AeY_.mjs +260 -0
  97. package/dist/plan-placement-D_YYlE6N.mjs +73 -0
  98. package/dist/{ports-ByPc0_97.mjs → ports-jzvAJXXL.mjs} +3 -3
  99. package/dist/{prebuild-BKJ3wJI1.mjs → prebuild-Bw44QxVh.mjs} +4 -4
  100. package/dist/{preview-BbSottmO.mjs → preview-BaBJ72Al.mjs} +50 -32
  101. package/dist/project-B8_duAzW.mjs +1067 -0
  102. package/dist/project-plan-BPwbLwQu.mjs +10 -0
  103. package/dist/{pull-request-IHNdMnG8.mjs → pull-request-vOhLNmIb.mjs} +1 -1
  104. package/dist/pull-requests.mjs +1 -1
  105. package/dist/react-native-android-chm1DMlA.mjs +921 -0
  106. package/dist/react-native-build-oAw6gQeW.mjs +239 -0
  107. package/dist/react-native-doctor-DYXoh-6I.mjs +78 -0
  108. package/dist/react-native-ios-SCTK_CT5.mjs +595 -0
  109. package/dist/{recordings-Bv8McZN4.mjs → recordings-Bk-VivUV.mjs} +1 -1
  110. package/dist/{reload-rAPdqeRR.mjs → reload-D34GkY78.mjs} +17 -13
  111. package/dist/remote-BU9UyhCJ.mjs +192 -0
  112. package/dist/{remote-cache-N3hAEaAL.mjs → remote-cache-Bsts6f6Y.mjs} +5 -5
  113. package/dist/result-BDoRk1k3.mjs +186 -0
  114. package/dist/{run-lAkle1-c.mjs → run-qvff78hv.mjs} +3 -3
  115. package/dist/{server-bare-CkIjnegq.mjs → server-bare-dWjAarki.mjs} +5 -5
  116. package/dist/server-command-7XIKlWtO.mjs +69 -0
  117. package/dist/{server-expo-n3tUi3Em.mjs → server-expo-CsYu2aKL.mjs} +107 -82
  118. package/dist/server-expo-DV43vep4.mjs +2 -0
  119. package/dist/{settings-CxgngUZk.mjs → settings-Bbztm4oV.mjs} +179 -129
  120. package/dist/{settings-2am7TSxp.mjs → settings-CT3E71-i.mjs} +6 -6
  121. package/dist/{state-lKh8shBp.d.mts → settings-vk3mJGSm.d.mts} +4 -2
  122. package/dist/settings.schema.json +84 -0
  123. package/dist/simslim-ChMfe8rg.mjs +149 -0
  124. package/dist/{slot-launch-HV_KtPIw.mjs → slot-launch-B9hn96wd.mjs} +6 -6
  125. package/dist/{stage-BgaZJVZY.mjs → stage-BNKKdDNc.mjs} +6 -3
  126. package/dist/start-CbQCSnXQ.mjs +2 -0
  127. package/dist/{start-DtxthMfI.mjs → start-CdvcTaXr.mjs} +97 -68
  128. package/dist/{state-Tp4uImDS.mjs → state-Boo0IMJq.mjs} +1 -1
  129. package/dist/{state-D2KtnALG.mjs → state-DI84A-uu.mjs} +29 -10
  130. package/dist/{stats-8EJ5ivFT.mjs → stats-BIIDrjA8.mjs} +5 -5
  131. package/dist/{status-BEeL6IKz.mjs → status-BDGHwkCs.mjs} +3 -1
  132. package/dist/{status-BOyp0CUl.mjs → status-DARMkc9q.mjs} +81 -293
  133. package/dist/status-watch-DvmI3SmN.mjs +296 -0
  134. package/dist/{stim-desktop-C65jk_q2.mjs → stim-desktop-8GQX2OV8.mjs} +1 -1
  135. package/dist/{stop-DRipAg-e.mjs → stop-CCZS-C9m.mjs} +2 -2
  136. package/dist/{stop-D0UIpZuh.mjs → stop-WQPQHLuy.mjs} +5 -5
  137. package/dist/{stop-zDlknFoz.mjs → stop-iNemHsJC.mjs} +21 -20
  138. package/dist/supervisor-run.d.mts +7 -2
  139. package/dist/supervisor-run.mjs +32 -20
  140. package/dist/{support-DsNaFg6D.mjs → support-CnE_SIjt.mjs} +17 -7
  141. package/dist/{support-CNqYkV0y.mjs → support-DCco5fut.mjs} +61 -8
  142. package/dist/{swiftpm-macos-aJzPAj1t.mjs → swiftpm-macos-afeNUExW.mjs} +5 -5
  143. package/dist/tailnet-CoJUb2tB.mjs +265 -0
  144. package/dist/toolchain-BIupIro1.mjs +2 -0
  145. package/dist/{toolchain-C3xSoyUl.mjs → toolchain-DqyDXs30.mjs} +206 -53
  146. package/dist/{trigger-CAhmG-4s.mjs → trigger-C0MWBi8O.mjs} +2 -2
  147. package/dist/trigger-SHLOWJ4O.mjs +2 -0
  148. package/dist/{warm-progress-pFLB2e6P.mjs → warm-progress-C3ASD6z5.mjs} +6 -6
  149. package/dist/{watchman-BL1IdOtq.mjs → watchman-CQl51YZu.mjs} +6 -6
  150. package/dist/{web-DbbrPUn0.mjs → web-B3eldHFH.mjs} +9 -13
  151. package/dist/web-BLXpOe6b.mjs +2 -0
  152. package/dist/web-run.mjs +10 -10
  153. package/dist/{workspace-state-CR9X86xJ.mjs → workspace-state-CSIFuuDa.mjs} +33 -33
  154. package/dist/{workspaces-Djb2gfi9.mjs → workspaces-D2xSHBDY.mjs} +1260 -1258
  155. package/dist/{worktree-BL4M1rhB.mjs → worktree-BxDqaTev.mjs} +84 -48
  156. package/dist/worktree-q7_OZNY0.mjs +2 -0
  157. package/package.json +8 -6
  158. package/shim/native-android.gradle +101 -0
  159. package/dist/android-BXc6BmEi.mjs +0 -3732
  160. package/dist/android-BvV1gGdN.mjs +0 -4
  161. package/dist/ios-ITYno9rO.mjs +0 -2241
  162. package/dist/macos-nIOATJmR.mjs +0 -2
  163. package/dist/metro-store-BAHFcJm6.mjs +0 -106
  164. package/dist/miss-reason-CjQCOKqq.mjs +0 -1743
  165. package/dist/plan-placement-BVY59qa8.mjs +0 -290
  166. package/dist/project-B5eQ1USs.mjs +0 -612
  167. package/dist/react-native-ios-BMh2i25I.mjs +0 -2
  168. package/dist/server-expo-DGsdUGX2.mjs +0 -2
  169. package/dist/start-D8NbxNBb.mjs +0 -2
  170. package/dist/trigger-CoalsMTV.mjs +0 -2
  171. package/dist/web-DFpFeZu5.mjs +0 -2
  172. package/dist/worktree-DV-A0LNL.mjs +0 -2
@@ -1,1743 +0,0 @@
1
- import { n as ownership_claim_exports } from "./process-identity-Biv3arCO.mjs";
2
- import { t as getExecutor } from "./exec-i8GDPwq9.mjs";
3
- import { r as waitForChild, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
4
- import { l as phaseLine } from "./command-output-Bt8ygHE7.mjs";
5
- import { D as withConfigLock$1, H as projectDeviceSlots, _ as loadConfig$1, a as clearDevice, b as releaseAndroidConsolePort, i as clearAndroidBootPending, k as acquireAvdClaim, p as getProject, t as allConsolePortsAndSerials, w as setDevice, z as deviceSlotPlatforms } from "./config-DC5jEtd1.mjs";
6
- import { d as sharedBuildCache } from "./workspace-state-CR9X86xJ.mjs";
7
- import { E as readMetroTunnel, O as readRemoteSession, o as ownedDeviceLabel } from "./project-B5eQ1USs.mjs";
8
- import { Ct as removeTemporaryEntry, J as planMetroReach, St as makeTemporaryDirectory, a as androidAvdConfigSetting, c as androidDataPartitionSizeGbSetting, q as detectProviders, rt as gitCommonDirOnDisk } from "./settings-CxgngUZk.mjs";
9
- import { s as pidExists } from "./metro-DI2AjPF_.mjs";
10
- import { s as peekBudget } from "./budget-Dz-kyXLv.mjs";
11
- import { A as teardownParkedAvd, D as teardownOwnedAvd } from "./workspaces-Djb2gfi9.mjs";
12
- import { C as diffFingerprintSources, I as storedSources } from "./device-lease-Cu7PtQpZ.mjs";
13
- import { A as listAvds, B as ownedAvdSystemImage, E as getAvdNameForSerial, F as ownedAvdDeviceProfile, J as resolveOwnedAvdSerial, L as ownedAvdMatchesConfiguration, O as listAdbDevices, P as nextConsolePort, R as ownedAvdName, W as pickDefaultSystemImage, b as createOwnedAvd, f as assertOwnedAvdStopped, ft as parkedMaxSetting, g as avdPoolConfiguration, j as listInstalledSystemImages, n as OWNED_AVD_CONFIG_DEFAULTS, pt as readParked, st as adoptParked, t as DEFAULT_AVD_DEVICE_PROFILE, tt as waitForBoot, ut as isLegacyDeletionClaim, v as bootAndroidEmulator, y as configureNewOwnedAvd } from "./android-BdQC6zs1.mjs";
14
- import { M as readHostMemoryPressure, d as listAllIosSims } from "./ios-BWbHFZVz.mjs";
15
- import { E as namedBuildMachine, N as hostingMachines, k as resolveBuildMachine } from "./client-CuUo8Nvg.mjs";
16
- import { t as clearDeviceIdleShutdown } from "./idle-shutdown-DYiMBn34.mjs";
17
- import { i as countLiveOwnedDevices, n as DeviceAdmissionRefusal, p as withDeviceBootAdmission, s as peekDeviceSlots } from "./device-capacity-BoN1jQkO.mjs";
18
- import { E as startBuildHeartbeat, O as tailLines, h as HEARTBEAT_INTERVAL_MS } from "./toolchain-C3xSoyUl.mjs";
19
- import { t as cleanLine } from "./server-expo-n3tUi3Em.mjs";
20
- import { a as call, o as connectHost } from "./hosted-client-C5G4h_OJ.mjs";
21
- import { r as readHostedNative, s as writeHostedNative } from "./ios-state-By0XuLTQ.mjs";
22
- import { n as prepareHostedNative } from "./hosted-native-DbV2aUVw.mjs";
23
- import { f as resolveEasCliBin, s as isEasAuthFailureText } from "./remote-cache-N3hAEaAL.mjs";
24
- import { i as ensureOwnedIosDevice, r as ensureIosBooted } from "./device-ios-B2ruohE-.mjs";
25
- import { _ as MIN_EAS_CLI_SIMULATOR_VERSION, g as MIN_EAS_CLI_DEVICE_VERSION, s as readInstalledEasCliVersion, v as easCliSupport } from "./device-remote-B5GiUifk.mjs";
26
- import { t as reconcileSimSlim } from "./simslim-D9LkJxIO.mjs";
27
- import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
28
- import { basename, dirname, join, relative } from "node:path";
29
- import { LAST_BUILD_KEYS, automaticMachineEnabled, isJsonObject, loadConfig, machineCapacity, parseHostedChoice, parseHostedNativeOffer, readWorkspaceState } from "@stim-cli/core/state";
30
- import chalk from "chalk";
31
- import { isDeepStrictEqual } from "node:util";
32
- import { claimRemoveCommand, clearClaimChild, markClaimChildPending, processGroupAlive, releaseClaim, setClaimChild } from "@stim-cli/core/ownership-claim";
33
- import { captureProcessIdentity } from "@stim-cli/core/process-identity";
34
- import { randomUUID } from "node:crypto";
35
- import * as expoFingerprint from "@expo/fingerprint";
36
- import { request } from "node:http";
37
- //#region src/device-host/placement.ts
38
- function local$1(code, reason, skipped = [], atCapacity = false) {
39
- return {
40
- kind: "local",
41
- code,
42
- reason,
43
- skipped,
44
- ...atCapacity ? { atCapacity: true } : {}
45
- };
46
- }
47
- function decideDevicePlacement(inputs) {
48
- if ("sticky" in inputs) {
49
- if ("machine" in inputs.sticky) {
50
- const reason = `recorded session on ${inputs.sticky.machine}`;
51
- return {
52
- kind: "host",
53
- code: "sticky",
54
- reason,
55
- machines: [{
56
- machine: inputs.sticky.machine,
57
- reason
58
- }],
59
- skipped: []
60
- };
61
- }
62
- return {
63
- kind: "local",
64
- code: "sticky",
65
- reason: "this workspace's device runs here",
66
- skipped: []
67
- };
68
- }
69
- const { platform, here, offers, buildMachine, noWait, eas } = inputs;
70
- const { count, max, queued } = here.devices;
71
- const room = max === 0 || count !== null && count < max;
72
- const localEnabled = here.localEnabled !== false;
73
- const cannotTake = !localEnabled || !room || queued > 0;
74
- const atCapacity = count !== null && (!room || queued > 0);
75
- const usage = max > 0 ? `${count} of ${max} devices in use` : `${count} devices in use, no device cap`;
76
- const localReason = !localEnabled ? "local is disabled in remote.devicePoolDisabled" : count === null ? "cannot tell how many devices run here; local boot admission decides" : cannotTake ? `${usage}${queued ? `, ${queued} runs queued ahead` : ""}` : here.budgetRefusal ?? (here.memoryPressure !== "normal" ? `host memory pressure ${here.memoryPressure ?? "unknown"} here` : `load ${here.loadPerCore.toFixed(1)}/core here, ${usage}`);
77
- const nowhere = (code, reason, skipped = [], waiting = "") => {
78
- if (!localEnabled && !atCapacity) return {
79
- kind: "refused",
80
- code: "pool-unavailable",
81
- reason,
82
- skipped
83
- };
84
- if (!cannotTake || count === null) return local$1(code, reason + waiting, skipped);
85
- if (eas?.usable) return {
86
- kind: "eas",
87
- code: "eas-fallback",
88
- reason: `${reason}; EAS Simulator (remote.easFallback)`,
89
- skipped
90
- };
91
- const why = eas ? [...skipped, {
92
- machine: "eas",
93
- code: eas.code,
94
- reason: eas.reason
95
- }] : skipped;
96
- return localEnabled ? local$1(code, reason + waiting, why, true) : {
97
- kind: "refused",
98
- code: "pool-unavailable",
99
- reason,
100
- skipped: why,
101
- atCapacity: true
102
- };
103
- };
104
- if (!offers.length) return nowhere("no-remote-mac", "no remote Macs configured; " + localReason);
105
- if (localEnabled && count === null) return local$1("device-count-unknown", localReason);
106
- if (!cannotTake && here.memoryPressure === "normal" && !here.budgetRefusal && here.loadPerCore < here.maxLoadPerCore) return local$1("this-mac-free", localReason);
107
- const skipped = [];
108
- const admitted = [];
109
- offers.forEach((probe, index) => {
110
- let reason = null;
111
- let code = "unreachable";
112
- if ("failure" in probe) reason = probe.failure;
113
- else {
114
- const { offer } = probe;
115
- if (offer.declined !== null) [code, reason] = ["declined", `declined: ${offer.declined}`];
116
- else if (offer.platform !== platform || !offer.choice) [code, reason] = ["no-matching-device", `no matching ${platform} device choice`];
117
- else if (offer.resources.memoryPressure !== "normal") [code, reason] = ["memory", "host memory pressure unknown or elevated"];
118
- else if (offer.capacity.available === 0) [code, reason] = ["no-capacity", "no hosted device capacity available"];
119
- else if (!cannotTake && (offer.resources.loadPerCore === null || offer.resources.loadPerCore >= here.loadPerCore)) [code, reason] = ["load", offer.resources.loadPerCore === null ? "host load unknown while this Mac has room" : "host is not less loaded than this Mac"];
120
- else admitted.push({
121
- machine: probe.machine,
122
- offer,
123
- index
124
- });
125
- }
126
- if (reason) skipped.push({
127
- machine: probe.machine,
128
- code,
129
- reason
130
- });
131
- });
132
- admitted.sort((a, b) => Number(b.machine === buildMachine) - Number(a.machine === buildMachine) || (a.offer.resources.loadPerCore ?? Infinity) - (b.offer.resources.loadPerCore ?? Infinity) || b.offer.resources.memoryFreeBytes - a.offer.resources.memoryFreeBytes || a.index - b.index);
133
- if (!admitted.length) return nowhere("no-host-admits", `${localReason}; no host admits`, skipped, localEnabled && cannotTake && !noWait ? "; waiting locally if needed" : "");
134
- const machines = admitted.map(({ machine, offer }) => ({
135
- machine,
136
- reason: `${localReason}; ${machine}${machine === buildMachine ? " is the explicit remote Mac," : ""} load ${offer.resources.loadPerCore === null ? "unknown" : offer.resources.loadPerCore.toFixed(1) + "/core"}`
137
- }));
138
- return {
139
- kind: "host",
140
- code: "placed",
141
- machines,
142
- reason: machines[0].reason,
143
- skipped
144
- };
145
- }
146
- //#endregion
147
- //#region src/device-host/auto-placement.ts
148
- async function probeHost(machine, platform, selectors) {
149
- let host;
150
- const deadline = Date.now() + 3e3;
151
- try {
152
- host = await connectHost(machine, 3e3, true);
153
- const offer = await call(host, "device-host.offer", {
154
- platform,
155
- ...selectors
156
- }, Math.max(1, deadline - Date.now()));
157
- const resources = isJsonObject(offer.resources) ? offer.resources : {};
158
- const parsed = parseHostedNativeOffer({
159
- ...offer,
160
- resources: {
161
- ...resources,
162
- loadPerCore: resources.loadPerCore ?? 0
163
- }
164
- });
165
- let choice = platform === "ios" ? offer.platform === "ios" ? parseHostedChoice(offer.choice) : null : parsed?.platform === "android" ? parsed.choice : null;
166
- if (choice && ("runtime" in choice ? selectors.deviceType && selectors.deviceType !== choice.deviceType || selectors.runtime && selectors.runtime.replace(/^iOS /, "") !== choice.runtime.replace(/^iOS /, "") : selectors.systemImage && selectors.systemImage !== choice.systemImage || selectors.deviceProfile && selectors.deviceProfile !== choice.deviceProfile)) choice = null;
167
- if (!isJsonObject(offer.capacity) || offer.declined !== null && typeof offer.declined !== "string") throw new Error("unreadable device offer");
168
- return {
169
- probe: {
170
- machine,
171
- offer: {
172
- platform,
173
- choice,
174
- declined: offer.declined,
175
- capacity: { available: typeof offer.capacity.available === "number" ? offer.capacity.available : null },
176
- resources: {
177
- loadPerCore: typeof resources.loadPerCore === "number" && Number.isFinite(resources.loadPerCore) && resources.loadPerCore >= 0 ? resources.loadPerCore : null,
178
- memoryFreeBytes: typeof resources.memoryFreeBytes === "number" ? resources.memoryFreeBytes : 0,
179
- memoryPressure: typeof resources.memoryPressure === "string" ? resources.memoryPressure : null
180
- }
181
- }
182
- },
183
- ...choice ? { target: {
184
- host,
185
- choice,
186
- session: null
187
- } } : {}
188
- };
189
- } catch (error) {
190
- return { probe: {
191
- machine,
192
- failure: error instanceof Error ? error.message : String(error)
193
- } };
194
- } finally {
195
- host?.connection.close();
196
- }
197
- }
198
- function hosted(target, code, reason, skipped = []) {
199
- target.selection = {
200
- selected: "auto",
201
- reason
202
- };
203
- return {
204
- target,
205
- placement: {
206
- decision: "hosted",
207
- machine: target.host.machine,
208
- reason
209
- },
210
- code,
211
- skipped
212
- };
213
- }
214
- function local(code, reason, skipped = []) {
215
- return {
216
- target: null,
217
- placement: {
218
- decision: "local",
219
- reason
220
- },
221
- code,
222
- skipped
223
- };
224
- }
225
- function viaEas(code, reason, skipped) {
226
- return {
227
- target: null,
228
- placement: {
229
- decision: "eas",
230
- machine: "eas",
231
- reason
232
- },
233
- code,
234
- skipped
235
- };
236
- }
237
- async function automaticDevicePlacement({ root, slot, platform, selectors, buildMachine, noWait, eas }, { machines = hostingMachines, read = readHostedNative, write = writeHostedNative, resume = prepareHostedNative, peek = () => peekDeviceSlots({
238
- platform,
239
- project: getProject(root),
240
- slot
241
- }), capacity = machineCapacity, memory = readHostMemoryPressure, probe = probeHost, budget = peekBudget } = {}) {
242
- const recorded = read(root, platform, slot)[slot];
243
- if (recorded) {
244
- const target = await resume(recorded.machine, selectors, recorded, platform, true);
245
- if (target) return {
246
- target,
247
- placement: {
248
- decision: "hosted",
249
- machine: recorded.machine,
250
- reason: recorded.reason ?? decideDevicePlacement({ sticky: { machine: recorded.machine } }).reason
251
- },
252
- code: "sticky",
253
- skipped: [],
254
- sticky: true
255
- };
256
- write(root, slot, null, platform);
257
- }
258
- const devices = peek();
259
- if (devices.localLive) return {
260
- ...local("sticky", decideDevicePlacement({ sticky: { local: true } }).reason),
261
- sticky: true
262
- };
263
- const configured = machines();
264
- if (configured === null) throw Object.assign(/* @__PURE__ */ new Error("remote.machines is invalid. Run stim guide settings and correct it."), { code: "STIM_HOSTING_REFUSED" });
265
- const config = loadConfig$1();
266
- const entries = configured.filter((machine) => automaticMachineEnabled("device", machine, config));
267
- const preference = resolveBuildMachine(buildMachine, process.env.STIM_REMOTE_BUILD, config?.remote?.build);
268
- buildMachine = namedBuildMachine(preference) ? preference : void 0;
269
- const load = capacity();
270
- const here = {
271
- localEnabled: automaticMachineEnabled("device", "local", config),
272
- loadPerCore: load.loadPerCore,
273
- maxLoadPerCore: load.maxLoadPerCore,
274
- memoryPressure: memory(),
275
- devices,
276
- budgetRefusal: await budget(root)
277
- };
278
- const inputs = (offers) => ({
279
- platform,
280
- here,
281
- offers,
282
- buildMachine,
283
- noWait
284
- });
285
- const notProbed = entries.map((machine) => ({
286
- machine,
287
- failure: "not probed"
288
- }));
289
- const early = decideDevicePlacement(inputs(notProbed));
290
- const probes = early.kind === "local" && early.skipped.length === 0 ? [] : await Promise.all(entries.map((machine) => probe(machine, platform, selectors)));
291
- const offers = probes.length ? probes.map((each) => each.probe) : notProbed;
292
- let decision = decideDevicePlacement(inputs(offers));
293
- if ((decision.kind === "local" || decision.kind === "refused") && decision.atCapacity && eas) decision = decideDevicePlacement({
294
- ...inputs(offers),
295
- eas: await eas()
296
- });
297
- if (decision.kind === "refused") throw Object.assign(new Error(devicePlacementLine({
298
- decision: "local",
299
- reason: decision.reason
300
- }, decision.skipped)), {
301
- code: "STIM_HOSTING_REFUSED",
302
- remedy: "Enable an available member in remote.devicePoolDisabled or select a machine explicitly."
303
- });
304
- if (decision.kind === "eas") return viaEas(decision.code, decision.reason, decision.skipped);
305
- if (decision.kind === "local") return local(decision.code, decision.reason, decision.skipped);
306
- const target = probes.find((each) => each.probe.machine === decision.machines[0].machine).target;
307
- return hosted(target, decision.code, decision.reason, decision.skipped);
308
- }
309
- function devicePlacementLine(placement, skipped) {
310
- return `${placement.machine ?? "local"}, ${placement.reason}${placement.decision !== "hosted" && skipped.length ? "; " + skipped.map((each) => `${each.machine}: ${each.reason}`).join("; ") : ""}`;
311
- }
312
- //#endregion
313
- //#region src/engine/eas-fallback.ts
314
- const AVAILABILITY_TIMEOUT_MS = 3e4;
315
- function unusable(code, reason) {
316
- return {
317
- usable: false,
318
- code,
319
- reason
320
- };
321
- }
322
- /** Reads `eas simulator:availability --json`: `{"available": true, ...}` on stdout, or a failure on stderr. */
323
- function parseSimulatorAvailability(outcome) {
324
- if ("failure" in outcome) {
325
- if (outcome.timedOut) return unusable("eas-unavailable", "eas simulator:availability timed out");
326
- const detail = outcome.failure.split("\n").map((line) => line.trim()).find((line) => line && !/is now available\.$|^(To upgrade|npm install|Proceeding with|\(node:|\(Use `node)/.test(line));
327
- if (isEasAuthFailureText(outcome.failure)) return unusable("eas-logged-out", "eas-cli is not logged in (run eas login or set EXPO_TOKEN)");
328
- return unusable("eas-unavailable", `eas simulator:availability failed${detail ? `: ${detail}` : ""}`);
329
- }
330
- let data = null;
331
- try {
332
- data = JSON.parse(outcome.stdout.slice(outcome.stdout.indexOf("{")));
333
- } catch {}
334
- if (typeof data !== "object" || data === null || typeof data.available !== "boolean") return unusable("eas-unavailable", "eas simulator:availability printed no availability");
335
- return data.available ? { usable: true } : unusable("eas-not-enabled", "EAS Simulator is not enabled for this EAS account");
336
- }
337
- /**
338
- * Whether this run could use an EAS Simulator the way `--remote eas` does, without starting a session or a build.
339
- * Each refusal names what `--remote eas` would refuse later, so automatic placement falls through instead.
340
- */
341
- async function checkEasFallback({ root, platform, slot, release, isExpo, tunnelMode, publicUrl, deviceTypeFlag, localOnlyFlags, env = process.env, resolveBin = resolveEasCliBin, readVersion = readInstalledEasCliVersion, onPath = (bin) => getExecutor().findExecutable(bin) !== null, readTunnel = readMetroTunnel, readSession = readRemoteSession, metroPort = () => getProject(root)?.metroPort ?? null, availability = async (bin) => {
342
- try {
343
- return { stdout: await getExecutor().runFileAsync(bin, [
344
- "simulator:availability",
345
- "--json",
346
- "--non-interactive"
347
- ], {
348
- cwd: root,
349
- timeoutMs: AVAILABILITY_TIMEOUT_MS
350
- }) };
351
- } catch (error) {
352
- const err = error;
353
- return {
354
- failure: String(err.stderr || err.message || error),
355
- timedOut: err.code === "ETIMEDOUT"
356
- };
357
- }
358
- } }) {
359
- if (slot !== "default") return unusable("eas-named-slot", `an EAS Simulator takes only the default slot, not ${slot}`);
360
- if (localOnlyFlags.length) return unusable("eas-local-flags", `${localOnlyFlags.join(" and ")} applies only to a ${platform} device on a Mac`);
361
- const session = readSession(root);
362
- if (session && session.platform !== platform) return unusable("eas-session-busy", `this workspace's EAS Simulator session ${session.sessionId} runs ${session.platform ?? "another platform"}`);
363
- if (session && deviceTypeFlag?.trim() && session.deviceType !== deviceTypeFlag.trim()) return unusable("eas-session-busy", `this workspace's EAS Simulator session ${session.sessionId} runs ${session.deviceType ?? "the model EAS chose"}, not ${deviceTypeFlag.trim()}`);
364
- if (!onPath("agent-device")) return unusable("eas-no-agent-device", "agent-device is not on PATH");
365
- const bin = resolveBin(root);
366
- if (!bin) return unusable("eas-no-cli", "eas-cli is not installed for this project or on PATH");
367
- const versionOutput = readVersion(bin.file, root);
368
- const minimum = platform === "ios" && deviceTypeFlag?.trim() ? MIN_EAS_CLI_DEVICE_VERSION : MIN_EAS_CLI_SIMULATOR_VERSION;
369
- const { supported, version } = easCliSupport(versionOutput, minimum);
370
- if (!supported) return unusable("eas-cli-too-old", `eas-cli ${version ?? "(unknown version)"} is older than ${minimum}`);
371
- if (!release) {
372
- const mode = tunnelMode ?? "auto";
373
- if (mode === "off") return unusable("eas-metro-unreachable", "metro.tunnel is off, so an EAS Simulator cannot reach Metro");
374
- const plan = planMetroReach({
375
- mode,
376
- metroPort: metroPort() ?? "unknown",
377
- publicUrl: env["STIM_METRO_PUBLIC_URL"]?.trim() || publicUrl,
378
- isExpo,
379
- available: detectProviders(onPath, mode)
380
- });
381
- if ("failed" in plan) return unusable("eas-metro-unreachable", plan.failed);
382
- if ("start" in plan && plan.start === "tailscale") return unusable("eas-metro-unreachable", "metro.tunnel is tailscale, which an EAS Simulator cannot reach");
383
- if ("expoTunnel" in plan && readTunnel(root)?.kind !== "expo") return unusable("eas-metro-unreachable", "Metro has no Expo tunnel; run stim start --remote first");
384
- }
385
- return parseSimulatorAvailability(await availability(bin.file));
386
- }
387
- //#endregion
388
- //#region src/engine/metro-warmup.ts
389
- const WARMUP_TIMEOUT_MS = 6e4;
390
- async function requestLocal(url, platform, prefetch) {
391
- return new Promise((resolve) => {
392
- const req = request(url, { headers: {
393
- accept: "application/json",
394
- "expo-platform": platform,
395
- ...prefetch ? { "x-stim-metro-warmup": "1" } : {}
396
- } });
397
- const timer = setTimeout(() => req.destroy(), WARMUP_TIMEOUT_MS);
398
- timer.unref();
399
- req.on("socket", (socket) => socket.unref());
400
- req.on("error", () => resolve(null));
401
- req.on("close", () => {
402
- clearTimeout(timer);
403
- resolve(null);
404
- });
405
- req.on("response", (res) => {
406
- let body = "";
407
- res.setEncoding("utf8");
408
- res.on("data", (chunk) => {
409
- if (!prefetch) body += chunk;
410
- });
411
- res.on("error", () => resolve(null));
412
- res.on("end", () => resolve(res.statusCode === 200 ? body : null));
413
- });
414
- req.end();
415
- });
416
- }
417
- async function warmMetro({ port, platform, isExpo, appId, bundleUrl }) {
418
- try {
419
- const origin = `http://127.0.0.1:${port}`;
420
- if (await requestLocal(new URL("/_stim/metro-warmup", origin), platform, false) !== "ready") return;
421
- const url = new URL("/index.bundle", origin);
422
- url.search = new URLSearchParams({
423
- platform,
424
- dev: "true",
425
- lazy: "true",
426
- minify: "false",
427
- ...platform === "ios" ? { inlineSourceMap: "false" } : {},
428
- modulesOnly: "false",
429
- runModule: "true",
430
- excludeSource: "true",
431
- sourcePaths: "url-server",
432
- ...appId ? { app: appId } : {}
433
- }).toString();
434
- if (!bundleUrl && isExpo) {
435
- const body = await requestLocal(new URL(origin), platform, false);
436
- if (body === null) return;
437
- const manifest = JSON.parse(body);
438
- bundleUrl = manifest.launchAsset?.url ?? manifest.bundleUrl;
439
- if (typeof bundleUrl !== "string") return;
440
- }
441
- if (bundleUrl) {
442
- const bundle = new URL(bundleUrl, origin);
443
- url.pathname = bundle.pathname;
444
- url.search = bundle.search;
445
- }
446
- await requestLocal(url, platform, true);
447
- } catch {}
448
- }
449
- //#endregion
450
- //#region src/engine/android-avd-setup.ts
451
- var AvdBootError = class extends Error {
452
- remedy;
453
- constructor(message, remedy, cause) {
454
- super(message, { cause });
455
- this.remedy = remedy;
456
- }
457
- };
458
- var AvdRecoveryError = class extends AvdBootError {};
459
- function findOtherOwner(avdName, projectPath, selectedSlot = "default") {
460
- for (const [path, project] of Object.entries(loadConfig$1()?.projects ?? {})) if (projectDeviceSlots(project).some(({ slot, platforms }) => (path !== projectPath || slot !== selectedSlot) && platforms.android?.avdName?.toLowerCase() === avdName.toLowerCase())) return path;
461
- return null;
462
- }
463
- async function prepareOwnedAvd({ projectPath, slot, label, previousAvdName, systemImage, deviceProfile, configuration, configure, teardown = teardownOwnedAvd }) {
464
- const { claim, reservation, previous, avdName } = withConfigLock$1(() => {
465
- const previous = deviceSlotPlatforms(loadConfig$1()?.projects?.[projectPath], slot)?.android;
466
- if (previous?.avdName && previous.avdName !== previousAvdName) throw new Error(`Another Stim run assigned AVD ${previous.avdName} to this workspace. Retry to use it.`);
467
- if (readParked("android").some((entry) => entry.name === ownedAvdName(label)) || findOtherOwner(ownedAvdName(label), projectPath, slot)) label = `${label}-${randomUUID().slice(0, 8)}`;
468
- const avdName = ownedAvdName(label);
469
- const claim = acquireAvdClaim(avdName);
470
- const reservation = {
471
- avdName,
472
- owned: true,
473
- deviceName: avdName,
474
- setupIncomplete: true,
475
- poolConfiguration: configuration
476
- };
477
- try {
478
- setDevice(projectPath, "android", reservation, slot);
479
- return {
480
- claim,
481
- reservation,
482
- previous,
483
- avdName
484
- };
485
- } catch (error) {
486
- releaseClaim(claim);
487
- throw error;
488
- }
489
- });
490
- const currentRecord = () => deviceSlotPlatforms(loadConfig$1()?.projects?.[projectPath], slot)?.android;
491
- const assertReservation = () => {
492
- if (readParked("android").some((entry) => entry.name === avdName)) throw new Error(`AVD ${avdName} was parked by another Stim run. Retry to adopt it safely.`);
493
- const owner = findOtherOwner(avdName, projectPath, slot);
494
- if (owner) throw new Error(`AVD ${avdName} already exists and is owned by another project (${owner}). Retry to allocate a distinct owned emulator.`);
495
- const current = currentRecord();
496
- if (!isDeepStrictEqual(current, reservation)) {
497
- const state = current?.setupIncomplete ? "has incomplete setup" : "was registered or removed";
498
- throw new Error(`AVD ${current?.avdName ?? avdName} ${state} by another concurrent Stim run. Retry after that run finishes so the recorded device is resolved safely.`);
499
- }
500
- };
501
- let prepared;
502
- let child;
503
- const creationRunning = () => child?.pid !== void 0 && processGroupAlive(child.pid);
504
- let configurationFailure;
505
- try {
506
- try {
507
- const creation = await createOwnedAvd(label, {
508
- systemImage,
509
- deviceProfile,
510
- spawn: (...args) => {
511
- markClaimChildPending(claim);
512
- child = getExecutor().spawn(...args);
513
- if (child.pid !== void 0) {
514
- const captured = captureProcessIdentity(child.pid);
515
- if (captured.ok) try {
516
- setClaimChild(claim, {
517
- pid: child.pid,
518
- processToken: captured.token
519
- });
520
- } catch {}
521
- }
522
- return child;
523
- }
524
- }).then((result) => ({ result }), (error) => ({ error }));
525
- if (creationRunning()) throw new AvdRecoveryError(`AVD ${avdName} creation left its process group running (pid ${child.pid}); its reservation remains incomplete.`, `Wait for process group ${child.pid} to finish before retrying stim android. If its identity could not be recorded, inspect the group and remove the claim only after it stops: ${claimRemoveCommand(claim.path)}`);
526
- clearClaimChild(claim);
527
- if ("error" in creation) throw creation.error;
528
- prepared = {
529
- ...creation.result,
530
- created: true
531
- };
532
- } catch (error) {
533
- const message = String(error?.message || error);
534
- if (!message.includes("already exists")) throw error;
535
- try {
536
- if (!listAvds().includes(avdName)) throw new AvdRecoveryError(`AVD ${avdName} already exists on disk but is not listed by the emulator. ${message}`, "Run `npx stim gc` to inspect orphaned owned AVDs, then `npx stim gc --delete` to reclaim those safe to delete. Retry `stim android` after cleanup; keep any AVD that GC cannot verify.", error);
537
- withConfigLock$1(assertReservation);
538
- const resolved = resolveOwnedAvdSerial(avdName);
539
- if (resolved.missing || resolved.notOwned) throw new Error(`AVD ${avdName} could not be verified for recovery. Retry after checking its registration.`, { cause: error });
540
- if (!resolved.serial) assertOwnedAvdStopped(avdName);
541
- prepared = {
542
- avdName,
543
- created: false,
544
- systemImage: ownedAvdSystemImage(avdName),
545
- deviceProfile: ownedAvdDeviceProfile(avdName),
546
- ...resolved.serial ? {
547
- serial: resolved.serial,
548
- consolePort: Number(resolved.serial.replace(/^emulator-/, ""))
549
- } : {}
550
- };
551
- } catch (recoveryError) {
552
- withConfigLock$1(() => {
553
- if (!isDeepStrictEqual(currentRecord(), reservation)) return;
554
- if (previous) setDevice(projectPath, "android", previous, slot);
555
- else clearDevice(projectPath, "android", slot, avdName);
556
- });
557
- if (recoveryError instanceof AvdRecoveryError) throw recoveryError;
558
- throw new AvdRecoveryError(`Could not recover owned AVD ${avdName}: ${String(recoveryError?.message || recoveryError)}`, "Inspect `npx stim status` and `adb devices`. Wait for any other Stim run using this AVD to finish, then retry `stim android`. Keep the AVD and its process locks while its state is unverified.", recoveryError);
559
- }
560
- }
561
- if (prepared.created) try {
562
- configure(avdName);
563
- } catch (error) {
564
- configurationFailure = { error };
565
- }
566
- if (!configurationFailure) withConfigLock$1(() => {
567
- assertReservation();
568
- setDevice(projectPath, "android", {
569
- avdName,
570
- owned: true,
571
- deviceName: avdName,
572
- ...prepared.created ? {
573
- poolConfiguration: configuration,
574
- bootPending: true
575
- } : {},
576
- ...prepared.consolePort ? { consolePort: prepared.consolePort } : {}
577
- }, slot);
578
- });
579
- } finally {
580
- if (!creationRunning()) releaseClaim(claim);
581
- else {
582
- child?.stdout?.destroy();
583
- child?.stderr?.destroy();
584
- child?.unref();
585
- }
586
- }
587
- if (configurationFailure) {
588
- const { error } = configurationFailure;
589
- const cleanup = teardown(avdName, {
590
- del: true,
591
- owner: {
592
- projectPath,
593
- slot,
594
- expectedRecord: reservation
595
- },
596
- onRemoved: () => {
597
- clearDevice(projectPath, "android", slot, avdName);
598
- }
599
- });
600
- const orphan = cleanup.status === "failed" || cleanup.status === "skipped" ? ` The owned AVD remains tracked for cleanup (${cleanup.reason || cleanup.status}); fix the cause, then retry or run \`stim gc --delete\`.` : "";
601
- throw new Error(`Created owned AVD ${avdName}, but could not configure its AVD settings: ${String(error?.message || error)}${orphan}`, { cause: error });
602
- }
603
- return prepared;
604
- }
605
- //#endregion
606
- //#region src/engine/device-android.ts
607
- const ANDROID_FRESH_BOOT_TIMEOUT_MS = process.platform === "win32" ? 12e5 : 12e4;
608
- const ANDROID_BOOT_TIMEOUT_MS = process.platform === "win32" ? 12e5 : 24e4;
609
- async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoot, label, settings, flags, note, out, logFile, alive, configureAvd, teardownAvd }) {
610
- const projectAvdConfig = androidAvdConfigSetting(settings, settingsRoot);
611
- const avdConfig = {
612
- ...OWNED_AVD_CONFIG_DEFAULTS,
613
- ...projectAvdConfig
614
- };
615
- const requestedProfile = flags.deviceProfile || settings.android?.deviceProfile || null;
616
- let replacedProfile = null;
617
- if (record?.setupIncomplete && record.avdName) {
618
- const avdName = record.avdName;
619
- const cleanup = teardownAvd(avdName, {
620
- del: true,
621
- owner: {
622
- projectPath,
623
- slot,
624
- expectedRecord: record
625
- },
626
- onRemoved: () => {
627
- clearDevice(projectPath, "android", slot, avdName);
628
- }
629
- });
630
- 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.");
631
- record = null;
632
- }
633
- const requestedImage = flags.systemImageFlag || null;
634
- const currentImage = requestedImage && record?.owned && record.avdName ? ownedAvdSystemImage(record.avdName) : null;
635
- if (record?.avdName && requestedImage && currentImage && currentImage !== requestedImage) {
636
- const avdName = record.avdName;
637
- 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.");
638
- replacedProfile = ownedAvdDeviceProfile(avdName);
639
- const cleanup = teardownAvd(avdName, {
640
- del: true,
641
- owner: {
642
- projectPath,
643
- slot,
644
- expectedRecord: record
645
- },
646
- onRemoved: () => {
647
- clearDevice(projectPath, "android", slot, avdName);
648
- }
649
- });
650
- 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.");
651
- out(phaseLine("device", `deleted ${avdName}, which never finished a boot, to create one on ${requestedImage}`));
652
- record = null;
653
- }
654
- const deviceProfile = requestedProfile ?? replacedProfile ?? DEFAULT_AVD_DEVICE_PROFILE;
655
- const configuration = avdPoolConfiguration(androidDataPartitionSizeGbSetting(settings), avdConfig, deviceProfile);
656
- const configurationBeforeDefaults = avdPoolConfiguration(androidDataPartitionSizeGbSetting(settings), projectAvdConfig, deviceProfile);
657
- if (record?.avdName) {
658
- if (record.owned) {
659
- const resolved = resolveOwnedAvdSerial(record.avdName);
660
- const currentProfile = resolved.notOwned || resolved.missing ? null : ownedAvdDeviceProfile(record.avdName);
661
- 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.");
662
- if (resolved.notOwned) note(chalk.yellow(`Note: recorded AVD ${record.avdName} is not Stim-owned -- creating a fresh owned AVD instead of reusing it.`));
663
- else if (resolved.serial) {
664
- const consolePort = Number(resolved.serial.replace(/^emulator-/, ""));
665
- 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}`));
666
- const updated = {
667
- ...record,
668
- avdName: record.avdName,
669
- consolePort,
670
- owned: true,
671
- deviceName: record.deviceName ?? record.avdName
672
- };
673
- setDevice(projectPath, "android", updated, slot);
674
- return {
675
- ...updated,
676
- systemImage: ownedAvdSystemImage(record.avdName),
677
- deviceProfile: currentProfile
678
- };
679
- } else if (!resolved.missing) {
680
- out(chalk.dim(`Recorded port for owned AVD ${record.avdName} is not currently ours; booting it on a freshly allocated port...`));
681
- return {
682
- ...await bootOwnedAvdOnFreshPort({
683
- deviceSlotWait: flags.deviceSlotWait,
684
- avdName: record.avdName,
685
- metadata: record,
686
- projectPath,
687
- slot,
688
- deviceName: record.deviceName,
689
- out,
690
- logFile,
691
- alive
692
- }),
693
- systemImage: ownedAvdSystemImage(record.avdName),
694
- deviceProfile: currentProfile
695
- };
696
- }
697
- } else if (listAvds().includes(record.avdName)) {
698
- if (!listAdbDevices().emulators.some((e) => e.consolePort === record.consolePort)) {
699
- note(chalk.yellow(`Note: assigned AVD ${record.avdName} (emulator-${record.consolePort}) is shut down and is not owned by Stim, so it will not be booted automatically.`));
700
- note(chalk.dim("Boot it yourself, or run `stim gc --delete` to clear the assignment so Stim can create an owned AVD."));
701
- }
702
- return record;
703
- }
704
- } else if (record?.serial) {
705
- note(chalk.yellow(`Note: this project has a stored assignment to physical device ${record.serial}, which Stim no longer keeps.`));
706
- note(chalk.dim("Creating an owned emulator instead. Pass `--device` to build for a connected device."));
707
- }
708
- if (parkedMaxSetting("android").max > 0) {
709
- const systemImage = pickDefaultSystemImage(listInstalledSystemImages(), { systemImage: flags.systemImage || settings.android?.systemImage })?.pkg;
710
- const candidates = readParked("android").filter((entry) => entry.systemImage === systemImage && (entry.configuration === configuration || entry.configuration === configurationBeforeDefaults)).toSorted((a, b) => a.parkedAt.localeCompare(b.parkedAt));
711
- for (const parked of candidates) {
712
- if (isLegacyDeletionClaim(parked.deletionClaim)) continue;
713
- const resolved = resolveOwnedAvdSerial(parked.name);
714
- if (resolved.missing) {
715
- const result = teardownParkedAvd(parked.name);
716
- if (result.status === "failed") out(phaseLine("device", `kept ${parked.name}: ${result.reason}`));
717
- continue;
718
- }
719
- if (resolved.notOwned || resolved.serial) continue;
720
- try {
721
- assertOwnedAvdStopped(parked.name);
722
- } catch (error) {
723
- out(phaseLine("device", `kept parked ${parked.name}: ${String(error?.message || error)}`));
724
- continue;
725
- }
726
- if (ownedAvdSystemImage(parked.name) !== systemImage || !ownedAvdMatchesConfiguration(parked.name, parked.configuration)) {
727
- const result = teardownParkedAvd(parked.name);
728
- if (result.status === "failed") out(phaseLine("device", `kept ${parked.name}: ${result.reason}`));
729
- continue;
730
- }
731
- const adopted = {
732
- avdName: parked.name,
733
- deviceName: parked.name,
734
- owned: true,
735
- poolConfiguration: parked.configuration,
736
- adoptionPending: true
737
- };
738
- if (!adoptParked({
739
- platform: "android",
740
- projectPath,
741
- slot,
742
- udid: parked.udid,
743
- device: adopted
744
- })) continue;
745
- if (parked.configuration !== configuration) try {
746
- configureAvd(parked.name, {
747
- dataPartitionSizeGb: androidDataPartitionSizeGbSetting(settings),
748
- avdConfig
749
- });
750
- adopted.poolConfiguration = configuration;
751
- } catch (error) {
752
- out(phaseLine("device", `${parked.name} keeps its old AVD settings: ${String(error?.message || error)}`));
753
- }
754
- return {
755
- ...await bootOwnedAvdOnFreshPort({
756
- deviceSlotWait: flags.deviceSlotWait,
757
- avdName: parked.name,
758
- projectPath,
759
- slot,
760
- metadata: adopted,
761
- out,
762
- logFile,
763
- alive
764
- }),
765
- adopted: true,
766
- systemImage,
767
- deviceProfile
768
- };
769
- }
770
- }
771
- const created = await prepareOwnedAvd({
772
- projectPath,
773
- slot,
774
- label,
775
- previousAvdName: record?.avdName,
776
- systemImage: flags.systemImage || settings.android?.systemImage || void 0,
777
- deviceProfile,
778
- configuration,
779
- configure: (avdName) => configureAvd(avdName, {
780
- dataPartitionSizeGb: androidDataPartitionSizeGbSetting(settings),
781
- avdConfig
782
- }),
783
- teardown: teardownAvd
784
- });
785
- if (!created.created) {
786
- out(chalk.dim(phaseLine("device", `recovered ${created.avdName} (unrecorded from a prior run)`)));
787
- if (created.serial) return {
788
- ...created,
789
- owned: true,
790
- deviceName: created.avdName
791
- };
792
- }
793
- return {
794
- ...await bootOwnedAvdOnFreshPort({
795
- deviceSlotWait: flags.deviceSlotWait,
796
- avdName: created.avdName,
797
- metadata: created.created ? {
798
- poolConfiguration: configuration,
799
- bootPending: true
800
- } : void 0,
801
- projectPath,
802
- slot,
803
- deviceName: created.avdName,
804
- out,
805
- logFile,
806
- alive
807
- }),
808
- created: created.created,
809
- systemImage: created.systemImage,
810
- deviceProfile: created.deviceProfile
811
- };
812
- }
813
- function claimAndroidConsolePort({ projectPath, slot, avdName, deviceName, livePorts = [], metadata }, { lock = withConfigLock$1, recordedPorts = () => allConsolePortsAndSerials().androidConsolePorts, record = setDevice } = {}) {
814
- return lock(() => {
815
- const consolePort = nextConsolePort([...recordedPorts(), ...livePorts]);
816
- const claim = {
817
- ...metadata?.poolConfiguration ? { poolConfiguration: metadata.poolConfiguration } : {},
818
- ...metadata?.adoptionPending ? { adoptionPending: true } : {},
819
- ...metadata?.bootPending ? { bootPending: true } : {},
820
- avdName,
821
- consolePort,
822
- owned: true,
823
- deviceName: deviceName ?? avdName
824
- };
825
- record(projectPath, "android", claim, slot);
826
- return claim;
827
- });
828
- }
829
- function liveAndroidConsolePorts() {
830
- const adbLive = listAdbDevices();
831
- return [...adbLive.emulators.map((e) => e.consolePort), ...adbLive.unhealthy.map((u) => u.consolePort).filter((p) => p != null)];
832
- }
833
- async function bootOwnedAvdOnFreshPort({ avdName, deviceSlotWait, metadata, projectPath, slot, deviceName, out, logFile = null, alive = pidExists }) {
834
- return withDeviceBootAdmission({
835
- platform: "android",
836
- key: avdName
837
- }, async () => {
838
- const claim = claimAndroidConsolePort({
839
- projectPath,
840
- slot,
841
- avdName,
842
- deviceName,
843
- livePorts: liveAndroidConsolePorts(),
844
- metadata
845
- });
846
- const serial = `emulator-${claim.consolePort}`;
847
- try {
848
- reportAndroidMemoryPressure(out);
849
- const pid = bootAndroidEmulator(avdName, claim.consolePort, { logFile });
850
- out(chalk.dim(phaseLine("device", `waiting for ${serial} to finish booting`)));
851
- const result = await waitForAndroidBoot({
852
- serial,
853
- timeoutMs: ANDROID_FRESH_BOOT_TIMEOUT_MS,
854
- pid,
855
- alive,
856
- out
857
- });
858
- if (result.failed) throw new AvdBootError(result.reason, result.remedy);
859
- const running = getAvdNameForSerial(serial);
860
- if (running && running !== avdName) throw new Error(`${serial} is running AVD ${running}, not this workspace's owned AVD ${avdName}; refusing to use it.`);
861
- clearAndroidBootPending(projectPath, avdName, slot);
862
- const { bootPending: _booted, ...booted } = claim;
863
- return {
864
- ...booted,
865
- serial,
866
- setup: true
867
- };
868
- } catch (error) {
869
- releaseAndroidConsolePort(projectPath, claim.consolePort, slot);
870
- throw error;
871
- }
872
- }, {
873
- ...deviceSlotWait,
874
- root: projectPath,
875
- out
876
- });
877
- }
878
- function reportAndroidMemoryPressure(out) {
879
- const pressure = readHostMemoryPressure();
880
- if (pressure === "warning" || pressure === "critical") out(chalk.dim(phaseLine("memory", `macOS reports ${pressure} host memory pressure; emulator boot may be slow. Free memory with \`stim stop\` only in workspaces you own; ask before closing other apps or devices.`)));
881
- }
882
- function androidBootRemedy(pressure) {
883
- let count = null;
884
- try {
885
- const live = countLiveOwnedDevices({
886
- sims: () => process.platform === "darwin" ? listAllIosSims({ timeoutMs: 2e3 }) : [],
887
- adb: () => ({
888
- ...listAdbDevices({ timeoutMs: 2e3 }),
889
- unhealthy: []
890
- }),
891
- booting: []
892
- });
893
- count = typeof live === "number" ? live : null;
894
- } catch {}
895
- const observation = pressure === "warning" || pressure === "critical" ? `macOS reports ${pressure} host memory pressure. ` : "";
896
- const devices = count ? `${count} Stim-owned ${count === 1 ? "device is" : "devices are"} running. ` : "";
897
- if (observation || devices) return `${observation}${devices}Stop an unneeded device with \`stim stop\` only in a workspace you own, then run \`stim android\` again; ask before closing other apps or devices. These observations do not establish the cause of the timeout or an OOM crash.`;
898
- return "Inspect the emulator log and run `stim doctor` to check JAVA_HOME, ANDROID_HOME, and installed system images, then run `stim android` again.";
899
- }
900
- async function waitForAndroidBoot({ serial, timeoutMs, pid = null, alive = pidExists, out }) {
901
- const aborted = () => pid !== null && !alive(pid);
902
- const wait = (windowMs) => waitForBoot(serial, windowMs, {
903
- aborted,
904
- commandTimeoutMs: 5e3
905
- });
906
- let result = await wait(timeoutMs);
907
- if (result.ok) return {
908
- ok: true,
909
- serial
910
- };
911
- let pressure = readHostMemoryPressure();
912
- let elapsedMs = timeoutMs;
913
- if (!result.exited && pid !== null && !aborted() && (pressure === "warning" || pressure === "critical")) {
914
- const extensionMs = 24e4;
915
- out(chalk.dim(phaseLine("memory", `macOS reports ${pressure} host memory pressure; retrying the boot wait once for the same live emulator ${serial} (up to ${extensionMs / 1e3}s more).`)));
916
- result = await wait(extensionMs);
917
- if (result.ok) return {
918
- ok: true,
919
- serial
920
- };
921
- elapsedMs += extensionMs;
922
- pressure = readHostMemoryPressure();
923
- }
924
- const exited = result.exited || aborted();
925
- return {
926
- failed: true,
927
- reason: `${exited ? `The emulator process for ${serial} exited before the device finished booting.` : `Emulator ${serial} did not finish booting within ${Math.round(elapsedMs / 1e3)}s.`} Diagnostic: ${JSON.stringify(result.diagnostic)}`,
928
- remedy: exited ? "Inspect the emulator log for the process exit, fix what it reports, then run `stim android` again." : androidBootRemedy(pressure)
929
- };
930
- }
931
- async function ensureAndroidBooted({ device, deviceSlotWait, projectPath, slot, timeoutMs, out, logFile = null, alive = pidExists }) {
932
- if (!device?.avdName || !projectPath) return {
933
- failed: true,
934
- reason: "No owned Android emulator is recorded for this project."
935
- };
936
- let resolved;
937
- try {
938
- resolved = resolveOwnedAvdSerial(device.avdName);
939
- } catch (e) {
940
- return {
941
- failed: true,
942
- reason: `Could not list AVDs: ${e?.message || e}`
943
- };
944
- }
945
- if (resolved.missing) return {
946
- failed: true,
947
- reason: `AVD ${device.avdName} no longer exists. Run \`stim android\` again to create a fresh owned AVD.`
948
- };
949
- if (resolved.notOwned) return {
950
- failed: true,
951
- reason: `AVD ${device.avdName} is not Stim-owned; refusing to boot it.`
952
- };
953
- const avdName = device.avdName;
954
- const booted = (result) => {
955
- if (!result.failed) clearAndroidBootPending(projectPath, avdName, slot);
956
- return result;
957
- };
958
- if (resolved.serial) return booted(await waitForAndroidBoot({
959
- serial: resolved.serial,
960
- timeoutMs,
961
- out
962
- }));
963
- const freshSerial = `emulator-${device.consolePort}`;
964
- if (device.owned && device.serial === freshSerial) return booted(await waitForAndroidBoot({
965
- serial: freshSerial,
966
- timeoutMs,
967
- out
968
- }));
969
- const boot = async () => {
970
- const claim = claimAndroidConsolePort({
971
- projectPath,
972
- slot,
973
- avdName,
974
- deviceName: device.deviceName,
975
- livePorts: liveAndroidConsolePorts(),
976
- metadata: device
977
- });
978
- const serial = `emulator-${claim.consolePort}`;
979
- reportAndroidMemoryPressure(out);
980
- out(chalk.dim(phaseLine("device", `booting ${avdName} as ${serial}`)));
981
- let pid = null;
982
- try {
983
- pid = bootAndroidEmulator(avdName, claim.consolePort, { logFile });
984
- } catch (e) {
985
- releaseAndroidConsolePort(projectPath, claim.consolePort, slot);
986
- return {
987
- failed: true,
988
- reason: `Could not start emulator for AVD ${avdName}: ${e?.message || e}`
989
- };
990
- }
991
- const result = await waitForAndroidBoot({
992
- serial,
993
- timeoutMs,
994
- pid,
995
- alive,
996
- out
997
- });
998
- if (result.failed) releaseAndroidConsolePort(projectPath, claim.consolePort, slot);
999
- return booted(result);
1000
- };
1001
- try {
1002
- return await withDeviceBootAdmission({
1003
- platform: "android",
1004
- key: avdName
1005
- }, boot, {
1006
- ...deviceSlotWait,
1007
- root: projectPath,
1008
- out
1009
- });
1010
- } catch (error) {
1011
- const refusal = error instanceof DeviceAdmissionRefusal ? error : (0, ownership_claim_exports.claimFailure)(error, "stim android");
1012
- if (!refusal) throw error;
1013
- return {
1014
- failed: true,
1015
- code: refusal.code,
1016
- reason: refusal.message,
1017
- remedy: refusal.remedy
1018
- };
1019
- }
1020
- }
1021
- //#endregion
1022
- //#region src/engine/device.ts
1023
- /**
1024
- * Whether `ensureOwnedDevice` created, adopted or started booting the device, rather than finding it booted.
1025
- * Undefined for a remote device, which those functions do not describe.
1026
- */
1027
- function didSetUpDevice(device, remote) {
1028
- return remote ? void 0 : Boolean(device.setup || device.booting);
1029
- }
1030
- async function ensureOwnedDevice({ platform, project, projectPath, slot, settingsRoot = projectPath, label = ownedDeviceLabel(projectPath), settings, flags = {}, note = () => {}, out = () => {}, logFile = null, alive = pidExists, configureAvd = configureNewOwnedAvd, teardownAvd = teardownOwnedAvd, reconcileIosSimulator = reconcileSimSlim, simSnapshot }) {
1031
- clearDeviceIdleShutdown(projectPath, platform, slot);
1032
- if (slot && slot !== "default") label = `${label}-${slot}`;
1033
- const record = deviceSlotPlatforms(project, slot)?.[platform] ?? null;
1034
- if (platform === "ios") return ensureOwnedIosDevice({
1035
- record,
1036
- projectPath,
1037
- slot,
1038
- settingsRoot,
1039
- label,
1040
- settings,
1041
- flags,
1042
- note,
1043
- out,
1044
- reconcileIosSimulator,
1045
- simSnapshot
1046
- });
1047
- return ensureOwnedAndroidDevice({
1048
- record,
1049
- projectPath,
1050
- slot,
1051
- settingsRoot,
1052
- label,
1053
- settings,
1054
- flags,
1055
- note,
1056
- out,
1057
- logFile,
1058
- alive,
1059
- configureAvd,
1060
- teardownAvd
1061
- });
1062
- }
1063
- const BOOT_POLL_MS = 500;
1064
- async function ensureBooted({ platform, device, simulatorApp, timeoutMs, pollMs = BOOT_POLL_MS, out = () => {}, logFile = null, alive = pidExists, projectPath, slot, deviceSlotWait }) {
1065
- if (platform === "ios") return ensureIosBooted({
1066
- device,
1067
- projectPath,
1068
- deviceSlotWait,
1069
- simulatorApp,
1070
- timeoutMs: timeoutMs ?? 6e5,
1071
- pollMs,
1072
- out
1073
- });
1074
- if (platform === "android") return ensureAndroidBooted({
1075
- device,
1076
- deviceSlotWait,
1077
- projectPath,
1078
- slot,
1079
- timeoutMs: timeoutMs ?? ANDROID_BOOT_TIMEOUT_MS,
1080
- out,
1081
- logFile,
1082
- alive
1083
- });
1084
- return {
1085
- failed: true,
1086
- reason: `Unknown platform "${platform}".`
1087
- };
1088
- }
1089
- //#endregion
1090
- //#region src/engine/js-swap.ts
1091
- const JS_BUNDLE_NAME = "main.jsbundle";
1092
- const LAST_LINES = 5;
1093
- function hermesEnabledFromProperties(text) {
1094
- if (typeof text !== "string") return true;
1095
- let data;
1096
- try {
1097
- data = JSON.parse(text);
1098
- } catch {
1099
- return true;
1100
- }
1101
- if (!data || typeof data !== "object") return true;
1102
- const raw = data.hermesEnabled;
1103
- return raw !== "false" && raw !== false;
1104
- }
1105
- function readHermesEnabled(root) {
1106
- try {
1107
- return hermesEnabledFromProperties(readFileSync(join(root, "ios", "Podfile.properties.json"), "utf-8"));
1108
- } catch {
1109
- return true;
1110
- }
1111
- }
1112
- const ENTRY_CANDIDATES = [
1113
- "index.js",
1114
- "index.ts",
1115
- "index.tsx",
1116
- "index.jsx"
1117
- ];
1118
- function pickEntryFile(entries) {
1119
- const names = new Set((Array.isArray(entries) ? entries : []).filter((e) => typeof e === "string"));
1120
- return ENTRY_CANDIDATES.find((c) => names.has(c)) ?? "index.js";
1121
- }
1122
- function detectEntryFile(root) {
1123
- let entries;
1124
- try {
1125
- entries = readdirSync(root);
1126
- } catch {
1127
- return "index.js";
1128
- }
1129
- return pickEntryFile(entries);
1130
- }
1131
- function bundleCommand({ isExpo, entryFile, bundleOutput, assetsDest, assetCatalogDest = null }) {
1132
- const catalogArgs = assetCatalogDest ? ["--asset-catalog-dest", assetCatalogDest] : [];
1133
- if (isExpo) return {
1134
- file: "npx",
1135
- args: [
1136
- "expo",
1137
- "export:embed",
1138
- "--platform",
1139
- "ios",
1140
- "--dev",
1141
- "false",
1142
- "--bundle-output",
1143
- bundleOutput,
1144
- "--assets-dest",
1145
- assetsDest,
1146
- ...catalogArgs
1147
- ]
1148
- };
1149
- return {
1150
- file: "npx",
1151
- args: [
1152
- "react-native",
1153
- "bundle",
1154
- "--platform",
1155
- "ios",
1156
- "--dev",
1157
- "false",
1158
- "--entry-file",
1159
- entryFile,
1160
- "--bundle-output",
1161
- bundleOutput,
1162
- "--assets-dest",
1163
- assetsDest,
1164
- ...catalogArgs
1165
- ]
1166
- };
1167
- }
1168
- const ASSET_CATALOG_BUNDLE = "RNAssets.bundle";
1169
- /**
1170
- * Where React Native 0.88+ keeps the app's packager images. With the `RCTUseAssetCatalog` Info.plist
1171
- * key, `react-native-xcode.sh` compiles them with actool into RNAssets.bundle and the native image
1172
- * loader reads only that catalog, so a swap has to rebuild it. Null means loose files under assets/.
1173
- */
1174
- function assetCatalogTarget(infoPlist) {
1175
- if (!infoPlist || typeof infoPlist !== "object") return null;
1176
- const plist = infoPlist;
1177
- const flag = plist.RCTUseAssetCatalog;
1178
- if (!(flag === true || flag === 1 || typeof flag === "string" && [
1179
- "true",
1180
- "yes",
1181
- "1"
1182
- ].includes(flag.toLowerCase()))) return null;
1183
- const families = Array.isArray(plist.UIDeviceFamily) ? plist.UIDeviceFamily : [1];
1184
- const targetDevices = [...families.includes(1) ? ["iphone"] : [], ...families.includes(2) ? ["ipad"] : []];
1185
- return {
1186
- platform: typeof plist.DTPlatformName === "string" ? plist.DTPlatformName : "iphoneos",
1187
- minimumDeploymentTarget: typeof plist.MinimumOSVersion === "string" ? plist.MinimumOSVersion : "15.1",
1188
- targetDevices: targetDevices.length ? targetDevices : ["iphone"]
1189
- };
1190
- }
1191
- function readPlistKey(e, plist, key, format) {
1192
- try {
1193
- return e.runFile("plutil", [
1194
- "-extract",
1195
- key,
1196
- format,
1197
- "-o",
1198
- "-",
1199
- plist
1200
- ]);
1201
- } catch (err) {
1202
- if (/No value at that key path/.test(describe(err))) return null;
1203
- throw err;
1204
- }
1205
- }
1206
- function readAssetCatalogTarget(e, plist) {
1207
- const flag = readPlistKey(e, plist, "RCTUseAssetCatalog", "raw");
1208
- if (flag === null) return null;
1209
- const families = readPlistKey(e, plist, "UIDeviceFamily", "json");
1210
- return assetCatalogTarget({
1211
- RCTUseAssetCatalog: flag,
1212
- DTPlatformName: readPlistKey(e, plist, "DTPlatformName", "raw"),
1213
- MinimumOSVersion: readPlistKey(e, plist, "MinimumOSVersion", "raw"),
1214
- UIDeviceFamily: families === null ? void 0 : JSON.parse(families)
1215
- });
1216
- }
1217
- function actoolArgs({ catalog, out, target }) {
1218
- return [
1219
- "actool",
1220
- catalog,
1221
- "--compile",
1222
- out,
1223
- "--output-format",
1224
- "human-readable-text",
1225
- "--errors",
1226
- "--warnings",
1227
- "--notices",
1228
- "--platform",
1229
- target.platform,
1230
- "--minimum-deployment-target",
1231
- target.minimumDeploymentTarget,
1232
- ...target.targetDevices.flatMap((device) => ["--target-device", device])
1233
- ];
1234
- }
1235
- const ASSET_CATALOG_BUNDLE_INFO_PLIST = `<?xml version="1.0" encoding="UTF-8"?>
1236
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1237
- <plist version="1.0">
1238
- <dict>
1239
- <key>CFBundleIdentifier</key>
1240
- <string>org.reactjs.RNAssets</string>
1241
- <key>CFBundleInfoDictionaryVersion</key>
1242
- <string>6.0</string>
1243
- <key>CFBundleName</key>
1244
- <string>RNAssets</string>
1245
- <key>CFBundlePackageType</key>
1246
- <string>BNDL</string>
1247
- </dict>
1248
- </plist>
1249
- `;
1250
- const UPDATES_MANIFEST_NAME = "app.manifest";
1251
- /**
1252
- * expo-updates launches the update with the newest `commitTime` among those built for the binary's
1253
- * runtime version, and the embedded one is only a row in that race. A swapped copy keeps the id and
1254
- * `commitTime` the cached build wrote, so an update downloaded after that build would launch instead
1255
- * of the injected bundle. A full build writes a fresh id and `commitTime`; this does the same.
1256
- */
1257
- function refreshUpdatesManifest(text, { id, commitTime }) {
1258
- const manifest = JSON.parse(text);
1259
- if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) throw new Error(`${UPDATES_MANIFEST_NAME} is not a JSON object`);
1260
- return JSON.stringify({
1261
- ...manifest,
1262
- id,
1263
- commitTime
1264
- });
1265
- }
1266
- function refreshUpdatesManifestFile(path, commitTime) {
1267
- writeFileSync(path, refreshUpdatesManifest(readFileSync(path, "utf-8"), {
1268
- id: randomUUID(),
1269
- commitTime
1270
- }));
1271
- }
1272
- const IOS_UPDATES_MANIFESTS = [join("EXUpdates.bundle", UPDATES_MANIFEST_NAME), UPDATES_MANIFEST_NAME];
1273
- const IOS_FRAMEWORK_UPDATES_MANIFEST = join("Frameworks", "EXUpdates.framework", "EXUpdates.bundle", UPDATES_MANIFEST_NAME);
1274
- function hermescPath(root, { exists = existsSync } = {}) {
1275
- const candidates = [
1276
- join(root, "node_modules", "hermes-compiler", "hermesc", "osx-bin", "hermesc"),
1277
- join(root, "ios", "Pods", "hermes-engine", "destroot", "bin", "hermesc"),
1278
- join(root, "node_modules", "react-native", "sdks", "hermesc", "osx-bin", "hermesc")
1279
- ];
1280
- return candidates.find(exists) ?? candidates[candidates.length - 1];
1281
- }
1282
- function hermescArgs({ bundle, out }) {
1283
- return [
1284
- "-emit-binary",
1285
- "-out",
1286
- out,
1287
- bundle
1288
- ];
1289
- }
1290
- async function swapJsBundle({ root, isExpo, cachedAppPath, logWriter = null, exec = null, spawnFn = null, mkdtemp = () => makeTemporaryDirectory(cachedAppPath, "stim-js-swap-"), exists = existsSync, hermesEnabled = null, now = Date.now, heartbeatMs = HEARTBEAT_INTERVAL_MS, onHeartbeat = (line) => console.error(line) }) {
1291
- const e = exec || getExecutor();
1292
- const startedAt = now();
1293
- const elapsed = () => now() - startedAt;
1294
- let tmp;
1295
- const fail = (step, reason, lastLines = []) => {
1296
- logWriter?.write?.({
1297
- src: "build",
1298
- level: "error",
1299
- msg: `JS swap failed at ${step}: ${reason}`,
1300
- event: "js_swap"
1301
- });
1302
- if (tmp) removeTemporaryEntry(tmp);
1303
- return {
1304
- failed: true,
1305
- step,
1306
- reason,
1307
- lastLines,
1308
- durationMs: elapsed()
1309
- };
1310
- };
1311
- let appCopy;
1312
- try {
1313
- tmp = mkdtemp();
1314
- appCopy = join(tmp, basename(cachedAppPath));
1315
- try {
1316
- e.runFile("cp", [
1317
- "-c",
1318
- "-R",
1319
- cachedAppPath,
1320
- appCopy
1321
- ]);
1322
- } catch {
1323
- removeTemporaryEntry(appCopy);
1324
- e.runFile("cp", [
1325
- "-R",
1326
- cachedAppPath,
1327
- appCopy
1328
- ]);
1329
- }
1330
- } catch (err) {
1331
- return fail("copy", `could not copy ${cachedAppPath} aside: ${describe(err)}`);
1332
- }
1333
- let catalogTarget;
1334
- const infoPlistPath = join(appCopy, "Info.plist");
1335
- try {
1336
- catalogTarget = readAssetCatalogTarget(e, infoPlistPath);
1337
- } catch (err) {
1338
- return fail("catalog", `could not read ${infoPlistPath}: ${describe(err)}`);
1339
- }
1340
- const bundleOutput = join(tmp, JS_BUNDLE_NAME);
1341
- const assetsDest = join(tmp, "assets");
1342
- const catalogStaging = catalogTarget ? join(tmp, "rn-assets") : null;
1343
- const catalogDir = catalogStaging ? join(catalogStaging, "RNAssets.xcassets") : null;
1344
- const command = bundleCommand({
1345
- isExpo,
1346
- entryFile: isExpo ? "index.js" : detectEntryFile(root),
1347
- bundleOutput,
1348
- assetsDest,
1349
- assetCatalogDest: catalogStaging
1350
- });
1351
- try {
1352
- mkdirSync(assetsDest, { recursive: true });
1353
- } catch (err) {
1354
- return fail("bundle", `could not create ${assetsDest}: ${describe(err)}`);
1355
- }
1356
- if (catalogDir) try {
1357
- mkdirSync(catalogDir, { recursive: true });
1358
- } catch (err) {
1359
- return fail("bundle", `could not create ${catalogDir}: ${describe(err)}`);
1360
- }
1361
- logWriter?.write?.({
1362
- src: "build",
1363
- level: "info",
1364
- msg: `${command.file} ${command.args.join(" ")}`,
1365
- event: "js_swap"
1366
- });
1367
- const spawn = spawnFn || ((cmd, args, opts) => e.spawn(cmd, args, opts));
1368
- const transcript = [];
1369
- const push = (line) => {
1370
- const msg = cleanLine(line);
1371
- if (msg.trim() === "") return;
1372
- transcript.push(msg);
1373
- logWriter?.write?.({
1374
- src: "build",
1375
- level: "debug",
1376
- msg,
1377
- raw: true,
1378
- event: "js_swap"
1379
- });
1380
- };
1381
- let child;
1382
- try {
1383
- child = spawn(command.file, command.args, {
1384
- cwd: root,
1385
- stdio: [
1386
- "ignore",
1387
- "pipe",
1388
- "pipe"
1389
- ],
1390
- env: {
1391
- ...process.env,
1392
- FORCE_COLOR: "0"
1393
- }
1394
- });
1395
- } catch (err) {
1396
- return fail("bundle", `could not run ${command.file} ${command.args[0]}: ${describe(err)}`);
1397
- }
1398
- const reader = {
1399
- out: createLineReader(push),
1400
- err: createLineReader(push)
1401
- };
1402
- child.stdout?.setEncoding?.("utf-8");
1403
- child.stderr?.setEncoding?.("utf-8");
1404
- child.stdout?.on("data", (chunk) => reader.out.push(chunk));
1405
- child.stderr?.on("data", (chunk) => reader.err.push(chunk));
1406
- const stopHeartbeat = startBuildHeartbeat({
1407
- intervalMs: heartbeatMs,
1408
- elapsed,
1409
- emit: onHeartbeat,
1410
- label: "swap"
1411
- });
1412
- let outcome;
1413
- try {
1414
- outcome = await waitForChild(child);
1415
- } finally {
1416
- stopHeartbeat();
1417
- }
1418
- reader.out.flush();
1419
- reader.err.flush();
1420
- if (outcome.error) return fail("bundle", `could not run ${command.file} ${command.args[0]}: ${describe(outcome.error)}`);
1421
- if (outcome.code !== 0) {
1422
- const how = outcome.signal ? `signal ${outcome.signal}` : `exit code ${outcome.code}`;
1423
- return fail("bundle", `\`${command.args.slice(0, 2).join(" ")}\` failed (${how})`, tailLines(transcript, LAST_LINES));
1424
- }
1425
- if (!exists(bundleOutput)) return fail("bundle", `the bundle command exited 0 but wrote no ${JS_BUNDLE_NAME} at ${bundleOutput}`);
1426
- let hermes = false;
1427
- let note;
1428
- if (hermesEnabled ?? readHermesEnabled(root)) {
1429
- const hermesc = hermescPath(root);
1430
- if (!exists(hermesc)) note = `hermesc not found at ${hermesc}; embedding the plain JS bundle instead of Hermes bytecode`;
1431
- else {
1432
- const hbc = join(tmp, `${JS_BUNDLE_NAME}.hbc`);
1433
- try {
1434
- e.runFile(hermesc, hermescArgs({
1435
- bundle: bundleOutput,
1436
- out: hbc
1437
- }));
1438
- e.runFile("mv", [hbc, bundleOutput]);
1439
- hermes = true;
1440
- } catch (err) {
1441
- return fail("hermesc", `hermesc failed on ${bundleOutput}: ${describe(err)}`);
1442
- }
1443
- }
1444
- }
1445
- try {
1446
- e.runFile("cp", [bundleOutput, join(appCopy, JS_BUNDLE_NAME)]);
1447
- e.runFile("cp", [
1448
- "-R",
1449
- `${assetsDest}/.`,
1450
- `${appCopy}/`
1451
- ]);
1452
- } catch (err) {
1453
- return fail("replace", `could not replace the JS bundle inside ${appCopy}: ${describe(err)}`);
1454
- }
1455
- if (catalogTarget && catalogDir) {
1456
- const catalogBundle = join(appCopy, ASSET_CATALOG_BUNDLE);
1457
- try {
1458
- rmSync(catalogBundle, {
1459
- recursive: true,
1460
- force: true
1461
- });
1462
- if (readdirSync(catalogDir).some((name) => name.endsWith(".imageset"))) {
1463
- mkdirSync(catalogBundle);
1464
- const output = e.runFile("xcrun", actoolArgs({
1465
- catalog: catalogDir,
1466
- out: catalogBundle,
1467
- target: catalogTarget
1468
- }));
1469
- if (!exists(join(catalogBundle, "Assets.car"))) return fail("actool", `actool wrote no Assets.car into ${catalogBundle}`, tailLines(output.split("\n"), LAST_LINES));
1470
- writeFileSync(join(catalogBundle, "Info.plist"), ASSET_CATALOG_BUNDLE_INFO_PLIST);
1471
- }
1472
- } catch (err) {
1473
- const stdout = String(err?.stdout ?? "");
1474
- return fail("actool", `could not compile the image asset catalog into ${catalogBundle}: ${describe(err)}`, tailLines(stdout.split("\n"), LAST_LINES));
1475
- }
1476
- }
1477
- if (exists(join(appCopy, IOS_FRAMEWORK_UPDATES_MANIFEST))) return fail("updates", `the expo-updates manifest sits inside a nested framework (${IOS_FRAMEWORK_UPDATES_MANIFEST}), where a swap cannot refresh it without re-signing that framework`);
1478
- for (const manifest of IOS_UPDATES_MANIFESTS.map((name) => join(appCopy, name)).filter(exists)) try {
1479
- refreshUpdatesManifestFile(manifest, now());
1480
- } catch (err) {
1481
- return fail("updates", `could not refresh the expo-updates manifest ${manifest}: ${describe(err)}`);
1482
- }
1483
- try {
1484
- e.runFile("codesign", [
1485
- "--force",
1486
- "--sign",
1487
- "-",
1488
- appCopy
1489
- ]);
1490
- } catch (err) {
1491
- return fail("codesign", `codesign --force --sign - ${appCopy} failed: ${describe(err)}`);
1492
- }
1493
- logWriter?.write?.({
1494
- src: "build",
1495
- level: "info",
1496
- msg: `JS swap done: ${hermes ? "hermes bytecode" : "plain JS"} into ${appCopy} in ${elapsed()}ms`,
1497
- event: "js_swap"
1498
- });
1499
- const result = {
1500
- ok: true,
1501
- appPath: appCopy,
1502
- tmpDir: tmp,
1503
- hermes,
1504
- durationMs: elapsed()
1505
- };
1506
- if (note) result.note = note;
1507
- return result;
1508
- }
1509
- function describe(err) {
1510
- return String(err?.message || err);
1511
- }
1512
- //#endregion
1513
- //#region src/cache/miss-reason.ts
1514
- const MISS_CHANGE_CAP = 20;
1515
- function recordedBuilds(state, platform) {
1516
- const builds = [];
1517
- for (const value of [state?.[LAST_BUILD_KEYS[platform]], state?.lastBuild]) {
1518
- const record = value;
1519
- if (record?.platform !== platform) continue;
1520
- const startedAt = typeof record.startedAt === "string" ? record.startedAt : "";
1521
- if (typeof record.fingerprint === "string" && typeof record.cacheKey === "string") builds.push({
1522
- fingerprint: record.fingerprint,
1523
- cacheKey: record.cacheKey,
1524
- startedAt
1525
- });
1526
- const carried = record.missReason?.baseline;
1527
- if (typeof carried?.fingerprint === "string" && typeof carried.cacheKey === "string") builds.push({
1528
- fingerprint: carried.fingerprint,
1529
- cacheKey: carried.cacheKey,
1530
- startedAt
1531
- });
1532
- }
1533
- return builds;
1534
- }
1535
- function projectIdentity(root) {
1536
- let real;
1537
- try {
1538
- real = realpathSync.native(root);
1539
- } catch {
1540
- return null;
1541
- }
1542
- for (let dir = real;; dir = dirname(dir)) {
1543
- if (existsSync(join(dir, ".git"))) {
1544
- const common = gitCommonDirOnDisk(dir);
1545
- return common ? `${common}\n${relative(dir, real)}` : null;
1546
- }
1547
- if (dirname(dir) === dir) return null;
1548
- }
1549
- }
1550
- function findMissBaseline(root, platform, { readState = readWorkspaceState, projectRoots = () => Object.keys(loadConfig()?.projects ?? {}), identity = projectIdentity, sourcesOf = (p, key) => storedSources(p, key, sharedBuildCache()) } = {}) {
1551
- for (const build of recordedBuilds(readState(root), platform)) {
1552
- const sources = sourcesOf(platform, build.cacheKey);
1553
- if (sources) return {
1554
- fingerprint: build.fingerprint,
1555
- cacheKey: build.cacheKey,
1556
- sources,
1557
- from: "workspace"
1558
- };
1559
- }
1560
- const project = identity(root);
1561
- if (!project) return null;
1562
- const others = [];
1563
- for (const other of projectRoots()) {
1564
- if (other === root || identity(other) !== project) continue;
1565
- others.push(...recordedBuilds(readState(other), platform));
1566
- }
1567
- for (const build of others.toSorted((a, b) => (Date.parse(b.startedAt) || 0) - (Date.parse(a.startedAt) || 0))) {
1568
- const sources = sourcesOf(platform, build.cacheKey);
1569
- if (sources) return {
1570
- fingerprint: build.fingerprint,
1571
- cacheKey: build.cacheKey,
1572
- sources,
1573
- from: "project"
1574
- };
1575
- }
1576
- return null;
1577
- }
1578
- const CATEGORY_RANK = {
1579
- "native-dependency": 0,
1580
- "app-config": 1,
1581
- "config-plugin": 2,
1582
- package: 3,
1583
- "native-dir": 4,
1584
- file: 5,
1585
- "app-asset": 6,
1586
- other: 7,
1587
- "package-scripts": 8,
1588
- autolinking: 9
1589
- };
1590
- function packageIn(path) {
1591
- return [...path.matchAll(/(?:^|\/)node_modules\/((?:@[^/]+\/)?[^/]+)/g)].at(-1)?.[1] ?? null;
1592
- }
1593
- function describeChange({ name, change, reasons }) {
1594
- const pkg = packageIn(name);
1595
- if (name === "expoConfig" || reasons.includes("expoConfig")) return {
1596
- category: "app-config",
1597
- label: "app config changed"
1598
- };
1599
- if (pkg && reasons.some((reason) => /^(expo|rncore)Autolinking(Ios|Android)?$/.test(reason))) return {
1600
- category: "native-dependency",
1601
- label: `native dependency ${change}: ${pkg}`
1602
- };
1603
- if (pkg && reasons.includes("expoConfigPlugins")) return {
1604
- category: "config-plugin",
1605
- label: `config plugin ${change}: ${pkg}`
1606
- };
1607
- if (/^(expoAutolinkingConfig|rncoreAutolinkingConfig)(:|$)/.test(name)) return {
1608
- category: "autolinking",
1609
- label: "autolinking config changed"
1610
- };
1611
- if (name.startsWith("package:")) return {
1612
- category: "package",
1613
- label: `${name.slice(8)} ${change}`
1614
- };
1615
- if (name === "packageJson:scripts") return {
1616
- category: "package-scripts",
1617
- label: "package.json scripts changed"
1618
- };
1619
- if (reasons.includes("expoConfigExternalFile")) return {
1620
- category: "app-asset",
1621
- label: `app asset ${change}: ${name}`
1622
- };
1623
- if (reasons.includes("bareNativeDir")) return {
1624
- category: "native-dir",
1625
- label: `${name}/ ${change}`
1626
- };
1627
- if (reasons.length || name.includes("/") || name.includes(".")) return {
1628
- category: "file",
1629
- label: `${name} ${change}`
1630
- };
1631
- return {
1632
- category: "other",
1633
- label: `${name} ${change}`
1634
- };
1635
- }
1636
- function rekeyPrefix(rekeyedBy) {
1637
- return rekeyedBy.length ? `${rekeyedBy.join(" and ")} changed native inputs; ` : "";
1638
- }
1639
- function missReasonFromChanges({ changes, baseline, rekeyedBy = [] }) {
1640
- const prefix = rekeyPrefix(rekeyedBy);
1641
- if (!baseline) return {
1642
- kind: "no-baseline",
1643
- summary: `${prefix}no earlier build of this project in the cache to compare with`,
1644
- changes: [],
1645
- changeCount: 0,
1646
- baseline: null,
1647
- rekeyedBy
1648
- };
1649
- if (!changes.length) return {
1650
- kind: "same-sources",
1651
- summary: `${prefix}native inputs match the last build; no cached app for this configuration or target`,
1652
- changes: [],
1653
- changeCount: 0,
1654
- baseline,
1655
- rekeyedBy
1656
- };
1657
- const described = changes.map((change, index) => ({
1658
- change,
1659
- index,
1660
- ...describeChange(change)
1661
- })).toSorted((a, b) => CATEGORY_RANK[a.category] - CATEGORY_RANK[b.category] || a.index - b.index);
1662
- const dependencyMoved = described.some((entry) => entry.category === "native-dependency");
1663
- const labels = [...new Set(described.filter((entry) => !(dependencyMoved && entry.category === "autolinking")).map((entry) => entry.label))];
1664
- const more = labels.length > 1 ? ` (+${labels.length - 1} more)` : "";
1665
- return {
1666
- kind: "changed",
1667
- summary: `${prefix}${labels[0]}${more}`,
1668
- changes: described.slice(0, MISS_CHANGE_CAP).map(({ change, category }) => ({
1669
- source: change.name,
1670
- change: change.change,
1671
- category
1672
- })),
1673
- changeCount: changes.length,
1674
- baseline,
1675
- rekeyedBy
1676
- };
1677
- }
1678
- function explainBuildMiss({ root, platform, current, rekeyedBy = [], baselineDeps, differ = expoFingerprint.diffFingerprints }) {
1679
- const baseline = findMissBaseline(root, platform, baselineDeps);
1680
- const changes = baseline ? diffFingerprintSources({
1681
- previous: baseline.sources,
1682
- previousHash: baseline.fingerprint,
1683
- current,
1684
- differ
1685
- }) : [];
1686
- return {
1687
- reason: missReasonFromChanges({
1688
- changes,
1689
- baseline: baseline ? {
1690
- fingerprint: baseline.fingerprint,
1691
- cacheKey: baseline.cacheKey,
1692
- from: baseline.from
1693
- } : null,
1694
- rekeyedBy
1695
- }),
1696
- changedNames: changes.map((change) => change.name),
1697
- previousHash: baseline?.fingerprint ?? null
1698
- };
1699
- }
1700
- function predictBuildMiss({ root, platform, current, prebuild }) {
1701
- const { reason } = explainBuildMiss({
1702
- root,
1703
- platform,
1704
- current
1705
- });
1706
- const baseline = reason.baseline && {
1707
- fingerprint: reason.baseline.fingerprint,
1708
- from: reason.baseline.from
1709
- };
1710
- if (!prebuild || !baseline) return {
1711
- ...reason,
1712
- baseline
1713
- };
1714
- const dir = platform === "ios" ? "ios/" : "android/";
1715
- return {
1716
- ...reason,
1717
- kind: "prebuild-pending",
1718
- summary: `${reason.summary} (before prebuild ${prebuild}s ${dir})`,
1719
- baseline
1720
- };
1721
- }
1722
- function skippedMissReason(summary) {
1723
- return {
1724
- kind: "cache-skipped",
1725
- summary,
1726
- changes: [],
1727
- changeCount: 0,
1728
- baseline: null,
1729
- rekeyedBy: []
1730
- };
1731
- }
1732
- function fingerprintErrorMissReason(message) {
1733
- return {
1734
- kind: "fingerprint-error",
1735
- summary: `fingerprinting failed: ${message}`,
1736
- changes: [],
1737
- changeCount: 0,
1738
- baseline: null,
1739
- rekeyedBy: []
1740
- };
1741
- }
1742
- //#endregion
1743
- export { devicePlacementLine as _, UPDATES_MANIFEST_NAME as a, swapJsBundle as c, ensureOwnedDevice as d, AvdBootError as f, automaticDevicePlacement as g, checkEasFallback as h, skippedMissReason as i, didSetUpDevice as l, warmMetro as m, fingerprintErrorMissReason as n, detectEntryFile as o, AvdRecoveryError as p, predictBuildMiss as r, refreshUpdatesManifestFile as s, explainBuildMiss as t, ensureBooted as u, probeHost as v };