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
@@ -0,0 +1,921 @@
1
+ import { t as getExecutor } from "./exec-B81Qodjn.mjs";
2
+ import { W as validateDeviceSlot, p as getProject, z as deviceSlotPlatforms } from "./config-D0LhTY3S.mjs";
3
+ import { Ft as removeTemporaryEntry, L as publicUrlSetting, Pt as makeTemporaryDirectory, U as resolveAndroidLayout, V as remoteEasFallbackSetting, Z as settingOriginScope, et as settingsLayers, m as defaultAndroidLayout, tt as tunnelModeSetting, z as realpathInside } from "./settings-Bbztm4oV.mjs";
4
+ import { B as captureAssetManifest, D as fingerprintProject, E as fingerprintDiffRecord, F as storedAssetManifest, H as readAssetManifest, L as untrackedMissLine, M as refingerprintAfterMutation, N as resolveBuild, O as inputsChangedDuringBuild, P as storeBuild, R as untrackedNativeFiles, S as configInputsChanged, T as filesystemBuildCapability, V as compareAssetManifests, b as buildCacheKey, x as changedDuringBuildLine, z as assetDiffReason } from "./device-lease-DHOBXGgS.mjs";
5
+ import { r as readWorkspaceState } from "./workspace-state-CSIFuuDa.mjs";
6
+ import { r as waitForChild, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
7
+ import { i as formatDuration, l as phaseLine, m as shortHash } from "./command-output-BjPN99ZB.mjs";
8
+ import { _ as detectIsExpo } from "./project-B8_duAzW.mjs";
9
+ import { A as listAvds, B as ownedAvdSystemImage, D as hostSystemImageArch, I as ownedAvdDirectory, T as findBuildTool, W as pickDefaultSystemImage, j as listInstalledSystemImages, l as androidSystemImageAbi, r as androidBuildToolName } from "./android-BsYNR81W.mjs";
10
+ import { It as statsProjectKey } from "./budget-BONq9vXY.mjs";
11
+ import { t as cleanLine } from "./server-expo-CsYu2aKL.mjs";
12
+ import { c as loadProjectProvider, i as checkEasAuth, p as resolveRemote } from "./remote-cache-Bsts6f6Y.mjs";
13
+ import { h as detectAppIds, m as detectAndroidPackage } from "./start-CdvcTaXr.mjs";
14
+ import { n as fingerprintErrorMissReason, t as explainBuildMiss } from "./miss-reason-BPAzZS0r.mjs";
15
+ import { o as resolveCcache } from "./ccache-CjHQ5F8g.mjs";
16
+ import { M as startBuildHeartbeat, P as tailLines, n as androidToolchain, t as androidRequirements, y as HEARTBEAT_INTERVAL_MS } from "./toolchain-DqyDXs30.mjs";
17
+ import { a as PLATFORM, c as androidGradleProject, h as displayPath, i as NO_FINGERPRINT, l as androidJobLayout, o as androidBuildOptions, r as NO_DEVICE } from "./support-CnE_SIjt.mjs";
18
+ import { i as AndroidRecipeRefusal, r as resolveAndroidRunPlan } from "./plan-BDk2AeY_.mjs";
19
+ import { c as readProductFlavors, n as buildAndroid, s as productFlavorRefusal } from "./react-native-build-oAw6gQeW.mjs";
20
+ import { n as planFlagRefusal, o as checkEasFallback, r as planPayload, t as planCachedBuild } from "./build-plan-CP0tckNU.mjs";
21
+ import { i as planEasDevelopmentBuild } from "./eas-build-Dw_ec2Km.mjs";
22
+ import { n as androidProcessRuntime, t as androidMetroRuntime } from "./launch-DxH0THLP.mjs";
23
+ import { i as staleNativeDirRefusal, n as recordPrebuild, r as runPrebuild, t as planPrebuild } from "./prebuild-Bw44QxVh.mjs";
24
+ import { n as detectEntryFile, r as refreshUpdatesManifestFile, t as UPDATES_MANIFEST_NAME } from "./js-swap-C8icNYIY.mjs";
25
+ import { t as planHostedDevice } from "./plan-placement-D_YYlE6N.mjs";
26
+ import { constants, copyFileSync, existsSync, mkdirSync, readFileSync, renameSync } from "node:fs";
27
+ import { basename, dirname, isAbsolute, join, relative } from "node:path";
28
+ import { loadCacheProvider } from "@stim-cli/cache";
29
+ import chalk from "chalk";
30
+ //#region src/engine/apk-swap.ts
31
+ const ANDROID_BUNDLE_NAME = "index.android.bundle";
32
+ const LAST_LINES = 5;
33
+ function hermesEnabledFromGradleProperties(text) {
34
+ if (typeof text !== "string") return true;
35
+ let value = null;
36
+ for (const raw of text.split("\n")) {
37
+ const line = raw.trim();
38
+ if (line === "" || line.startsWith("#") || line.startsWith("!")) continue;
39
+ const match = /^hermesEnabled\s*[=:]\s*(.*)$/.exec(line);
40
+ if (match) value = match[1].trim();
41
+ }
42
+ return value === null || value.toLowerCase() !== "false";
43
+ }
44
+ function readAndroidHermesEnabled(root, { gradleRoot, moduleDir, custom } = defaultAndroidLayout(root)) {
45
+ for (const dir of custom ? [moduleDir, gradleRoot] : [gradleRoot]) {
46
+ let text;
47
+ try {
48
+ text = readFileSync(join(dir, "gradle.properties"), "utf-8");
49
+ } catch {
50
+ continue;
51
+ }
52
+ if (/^\s*hermesEnabled\s*[=:]/m.test(text)) return hermesEnabledFromGradleProperties(text);
53
+ }
54
+ return true;
55
+ }
56
+ function hermescBinDir(platform = process.platform) {
57
+ if (platform === "darwin") return "osx-bin";
58
+ return platform === "win32" ? "win64-bin" : "linux64-bin";
59
+ }
60
+ function hermescName(platform) {
61
+ return platform === "win32" ? "hermesc.exe" : "hermesc";
62
+ }
63
+ function hermescCandidates(root, { platform = process.platform, reactNativePath = null } = {}) {
64
+ const bin = hermescBinDir(platform);
65
+ const name = hermescName(platform);
66
+ const rn = reactNativePath ?? join(root, "node_modules", "react-native");
67
+ return [.../* @__PURE__ */ new Set([
68
+ join(dirname(rn), "hermes-compiler", "hermesc", bin, name),
69
+ join(root, "node_modules", "hermes-compiler", "hermesc", bin, name),
70
+ join(root, "node_modules", "react-native", "sdks", "hermesc", bin, name),
71
+ join(root, "node_modules", "react-native", "sdks", "hermes", "build", "bin", name)
72
+ ])];
73
+ }
74
+ function androidHermescPath(root, { exists = existsSync, platform = process.platform, reactNativePath = null } = {}) {
75
+ const candidates = hermescCandidates(root, {
76
+ platform,
77
+ reactNativePath
78
+ });
79
+ return candidates.find(exists) ?? candidates[candidates.length - 1];
80
+ }
81
+ function androidHermescArgs({ bundle, out }) {
82
+ return [
83
+ "-emit-binary",
84
+ "-O",
85
+ "-w",
86
+ "-out",
87
+ out,
88
+ bundle
89
+ ];
90
+ }
91
+ function androidBundleCommand({ isExpo, entryFile, bundleOutput, assetsDest }) {
92
+ if (isExpo) return {
93
+ file: "npx",
94
+ args: [
95
+ "expo",
96
+ "export:embed",
97
+ "--platform",
98
+ "android",
99
+ "--dev",
100
+ "false",
101
+ "--bundle-output",
102
+ bundleOutput,
103
+ "--assets-dest",
104
+ assetsDest
105
+ ]
106
+ };
107
+ return {
108
+ file: "npx",
109
+ args: [
110
+ "react-native",
111
+ "bundle",
112
+ "--platform",
113
+ "android",
114
+ "--dev",
115
+ "false",
116
+ "--entry-file",
117
+ entryFile,
118
+ "--bundle-output",
119
+ bundleOutput,
120
+ "--assets-dest",
121
+ assetsDest
122
+ ]
123
+ };
124
+ }
125
+ function jarPath({ javaHome = process.env.JAVA_HOME } = {}) {
126
+ return javaHome ? join(javaHome, "bin", "jar") : "jar";
127
+ }
128
+ function zipalignArgs({ buildToolsMajor, input, output }) {
129
+ return [
130
+ ...buildToolsMajor >= 35 ? ["-P", "16"] : ["-p"],
131
+ "-f",
132
+ "-v",
133
+ "4",
134
+ input,
135
+ output
136
+ ];
137
+ }
138
+ const KEYSTORE_PASSWORD_ENV = "STIM_KEYSTORE_PASSWORD";
139
+ function keystorePassArg(value) {
140
+ const text = typeof value === "string" ? value.trim() : "";
141
+ if (text === "") return {
142
+ pass: "pass:android",
143
+ password: null
144
+ };
145
+ if (/^(?:file|env):/.test(text) || text === "stdin") return {
146
+ pass: text,
147
+ password: null
148
+ };
149
+ return {
150
+ pass: `env:${KEYSTORE_PASSWORD_ENV}`,
151
+ password: text.startsWith("pass:") ? text.slice(5) : text
152
+ };
153
+ }
154
+ function resolveKeystore(root, settings, layout = defaultAndroidLayout(root), containedIn = null) {
155
+ const android = settings?.["android"];
156
+ const bag = android && typeof android === "object" && !Array.isArray(android) ? android : {};
157
+ const configured = bag["keystore"];
158
+ let path = typeof configured === "string" && configured.trim() !== "" ? isAbsolute(configured.trim()) ? configured.trim() : join(root, configured.trim()) : join(layout.moduleDir, "debug.keystore");
159
+ if (containedIn !== null && typeof configured === "string" && configured.trim() !== "") try {
160
+ path = realpathInside(containedIn, path);
161
+ } catch (error) {
162
+ throw new Error(`Could not use android.keystore ${configured.trim()} from the committed .stim.json: ${String(error?.message || error)}`, { cause: error });
163
+ }
164
+ return {
165
+ path,
166
+ ...keystorePassArg(bag["keystorePassword"])
167
+ };
168
+ }
169
+ function apksignerArgs({ keystore, apkPath }) {
170
+ return [
171
+ "sign",
172
+ "--ks",
173
+ keystore.path,
174
+ "--ks-pass",
175
+ keystore.pass,
176
+ apkPath
177
+ ];
178
+ }
179
+ async function swapApkBundle({ root, isExpo, cachedApkPath, keystore, logWriter = null, exec = null, spawnFn = null, mkdtemp = () => makeTemporaryDirectory(cachedApkPath, "stim-apk-swap-"), exists = existsSync, hermesEnabled = null, layout = defaultAndroidLayout(root), buildTools = null, findTool = findBuildTool, storedAssets = null, readManifest = readAssetManifest, now = Date.now, heartbeatMs = HEARTBEAT_INTERVAL_MS, onHeartbeat = (line) => console.error(line), platform = process.platform }) {
180
+ const e = exec || getExecutor();
181
+ const startedAt = now();
182
+ const elapsed = () => now() - startedAt;
183
+ let tmp;
184
+ const fail = (step, reason, lastLines = []) => {
185
+ logWriter?.write?.({
186
+ src: "build",
187
+ level: "error",
188
+ msg: `APK swap failed at ${step}: ${reason}`,
189
+ event: "apk_swap"
190
+ });
191
+ if (tmp) removeTemporaryEntry(tmp);
192
+ return {
193
+ failed: true,
194
+ step,
195
+ reason,
196
+ lastLines,
197
+ durationMs: elapsed()
198
+ };
199
+ };
200
+ let work;
201
+ let final;
202
+ try {
203
+ tmp = mkdtemp();
204
+ const base = basename(cachedApkPath);
205
+ work = join(tmp, `unaligned-${base}`);
206
+ final = join(tmp, base);
207
+ copyApk(e, cachedApkPath, work, platform);
208
+ } catch (err) {
209
+ return fail("copy", `could not copy ${cachedApkPath} aside: ${describe(err)}`);
210
+ }
211
+ const stage = join(tmp, "stage");
212
+ const bundleOutput = join(stage, "assets", ANDROID_BUNDLE_NAME);
213
+ const assetsDest = join(stage, "res");
214
+ const command = androidBundleCommand({
215
+ isExpo,
216
+ entryFile: isExpo ? "index.js" : detectEntryFile(root),
217
+ bundleOutput,
218
+ assetsDest
219
+ });
220
+ try {
221
+ mkdirSync(join(stage, "assets"), { recursive: true });
222
+ mkdirSync(assetsDest, { recursive: true });
223
+ } catch (err) {
224
+ return fail("bundle", `could not create ${stage}: ${describe(err)}`);
225
+ }
226
+ logWriter?.write?.({
227
+ src: "build",
228
+ level: "info",
229
+ msg: `${command.file} ${command.args.join(" ")}`,
230
+ event: "apk_swap"
231
+ });
232
+ const spawn = spawnFn || ((cmd, args, opts) => e.spawn(cmd, args, opts));
233
+ const transcript = [];
234
+ const push = (line) => {
235
+ const msg = cleanLine(line);
236
+ if (msg.trim() === "") return;
237
+ transcript.push(msg);
238
+ logWriter?.write?.({
239
+ src: "build",
240
+ level: "debug",
241
+ msg,
242
+ raw: true,
243
+ event: "apk_swap"
244
+ });
245
+ };
246
+ let child;
247
+ try {
248
+ child = spawn(command.file, command.args, {
249
+ cwd: root,
250
+ stdio: [
251
+ "ignore",
252
+ "pipe",
253
+ "pipe"
254
+ ],
255
+ env: {
256
+ ...process.env,
257
+ FORCE_COLOR: "0"
258
+ }
259
+ });
260
+ } catch (err) {
261
+ return fail("bundle", `could not run ${command.file} ${command.args[0]}: ${describe(err)}`);
262
+ }
263
+ const reader = {
264
+ out: createLineReader(push),
265
+ err: createLineReader(push)
266
+ };
267
+ child.stdout?.setEncoding?.("utf-8");
268
+ child.stderr?.setEncoding?.("utf-8");
269
+ child.stdout?.on("data", (chunk) => reader.out.push(chunk));
270
+ child.stderr?.on("data", (chunk) => reader.err.push(chunk));
271
+ const stopHeartbeat = startBuildHeartbeat({
272
+ intervalMs: heartbeatMs,
273
+ elapsed,
274
+ emit: onHeartbeat,
275
+ label: "swap"
276
+ });
277
+ let outcome;
278
+ try {
279
+ outcome = await waitForChild(child);
280
+ } finally {
281
+ stopHeartbeat();
282
+ }
283
+ reader.out.flush();
284
+ reader.err.flush();
285
+ if (outcome.error) return fail("bundle", `could not run ${command.file} ${command.args[0]}: ${describe(outcome.error)}`);
286
+ if (outcome.code !== 0) {
287
+ const how = outcome.signal ? `signal ${outcome.signal}` : `exit code ${outcome.code}`;
288
+ return fail("bundle", `\`${command.args.slice(0, 2).join(" ")}\` failed (${how})`, tailLines(transcript, LAST_LINES));
289
+ }
290
+ if (!exists(bundleOutput)) return fail("bundle", `the bundle command exited 0 but wrote no ${ANDROID_BUNDLE_NAME} at ${bundleOutput}`);
291
+ let hermes = false;
292
+ let note;
293
+ if (hermesEnabled ?? readAndroidHermesEnabled(root, layout)) {
294
+ const hermesc = androidHermescPath(root);
295
+ if (!exists(hermesc)) note = `hermesc not found at ${hermesc}; embedding the plain JS bundle instead of Hermes bytecode`;
296
+ else {
297
+ const hbc = `${bundleOutput}.hbc`;
298
+ try {
299
+ e.runFile(hermesc, androidHermescArgs({
300
+ bundle: bundleOutput,
301
+ out: hbc
302
+ }));
303
+ renameSync(hbc, bundleOutput);
304
+ hermes = true;
305
+ } catch (err) {
306
+ return fail("hermesc", `hermesc failed on ${bundleOutput}: ${describe(err)}`);
307
+ }
308
+ }
309
+ }
310
+ const refuse = (reason, assetDiff) => {
311
+ logWriter?.write?.({
312
+ src: "build",
313
+ level: "warn",
314
+ msg: `APK swap refused: ${reason}`,
315
+ event: "apk_swap"
316
+ });
317
+ removeTemporaryEntry(tmp);
318
+ const result = {
319
+ assetMismatch: true,
320
+ reason,
321
+ durationMs: elapsed()
322
+ };
323
+ if (assetDiff) result.assetDiff = assetDiff;
324
+ return result;
325
+ };
326
+ if (!storedAssets) return refuse("this cache entry predates asset tracking (no assets-manifest.json beside the artifact), so its asset set cannot be proven to match this one");
327
+ const fresh = readManifest(assetsDest);
328
+ if (!fresh) return fail("assets", `could not hash the assets emitted into ${assetsDest}, so the asset set cannot be verified`);
329
+ const diff = compareAssetManifests(fresh, storedAssets);
330
+ if (!diff.same) return refuse(assetDiffReason(diff), diff);
331
+ const jar = jarPath();
332
+ const updatesManifest = join(stage, "assets", UPDATES_MANIFEST_NAME);
333
+ try {
334
+ e.runFile(jar, [
335
+ "--extract",
336
+ "--file",
337
+ work,
338
+ `assets/${UPDATES_MANIFEST_NAME}`
339
+ ], { cwd: stage });
340
+ if (exists(updatesManifest)) refreshUpdatesManifestFile(updatesManifest, now());
341
+ } catch (err) {
342
+ return fail("updates", `could not refresh the expo-updates manifest from ${work}: ${describe(err)}`);
343
+ }
344
+ try {
345
+ e.runFile(jar, [
346
+ "--update",
347
+ "--file",
348
+ work,
349
+ "--no-compress",
350
+ "-C",
351
+ stage,
352
+ "assets"
353
+ ]);
354
+ } catch (err) {
355
+ return fail("zip", `${jar} --update ${work} failed: ${describe(err)}`);
356
+ }
357
+ const tools = buildTools ?? findTool(["zipalign"]);
358
+ if (!tools) return fail("zipalign", "no zipalign found under the Android SDK build-tools; install one with `sdkmanager \"build-tools;36.0.0\"`");
359
+ try {
360
+ e.runFile(tools.path, zipalignArgs({
361
+ buildToolsMajor: tools.major,
362
+ input: work,
363
+ output: final
364
+ }));
365
+ } catch (err) {
366
+ return fail("zipalign", `zipalign failed on ${work}: ${describe(err)}`);
367
+ }
368
+ const signer = buildTools ? {
369
+ ...buildTools,
370
+ path: join(dirname(buildTools.path), androidBuildToolName("apksigner"))
371
+ } : findTool(["apksigner"]);
372
+ if (!signer) return fail("apksigner", "no apksigner found under the Android SDK build-tools; install one with `sdkmanager \"build-tools;36.0.0\"`");
373
+ try {
374
+ e.runFile(signer.path, apksignerArgs({
375
+ keystore,
376
+ apkPath: final
377
+ }), keystore.password === null ? void 0 : {
378
+ env: { [KEYSTORE_PASSWORD_ENV]: keystore.password },
379
+ redact: [keystore.password]
380
+ });
381
+ } catch (err) {
382
+ return fail("apksigner", `apksigner sign failed on ${final} with ${keystore.path}: ${describe(err)}`);
383
+ }
384
+ logWriter?.write?.({
385
+ src: "build",
386
+ level: "info",
387
+ msg: `APK swap done: ${hermes ? "hermes bytecode" : "plain JS"} into ${final} in ${elapsed()}ms`,
388
+ event: "apk_swap"
389
+ });
390
+ const result = {
391
+ ok: true,
392
+ apkPath: final,
393
+ tmpDir: tmp,
394
+ hermes,
395
+ durationMs: elapsed()
396
+ };
397
+ if (note) result.note = note;
398
+ return result;
399
+ }
400
+ function copyApk(e, from, to, platform) {
401
+ if (platform === "darwin") try {
402
+ e.runFile("cp", [
403
+ "-c",
404
+ from,
405
+ to
406
+ ]);
407
+ return;
408
+ } catch {}
409
+ copyFileSync(from, to, constants.COPYFILE_FICLONE);
410
+ }
411
+ function describe(err) {
412
+ const e = err;
413
+ const streams = [e?.stdout, e?.stderr].map((s) => s ? String(s).trim() : "").filter(Boolean).join("\n");
414
+ const message = e?.message ? String(e.message).trim() : String(err);
415
+ return streams ? `${message}: ${streams}` : message;
416
+ }
417
+ //#endregion
418
+ //#region src/integrations/react-native-android-plan.ts
419
+ const DEFAULT_PLAN_DEPS = {
420
+ fingerprint: fingerprintProject,
421
+ listSystemImages: listInstalledSystemImages,
422
+ avdSystemImage: ownedAvdSystemImage,
423
+ avdDirectory: ownedAvdDirectory,
424
+ listAvds,
425
+ loadCacheProvider,
426
+ loadProjectProvider,
427
+ checkEasAuth,
428
+ resolveRemote,
429
+ planPrebuild
430
+ };
431
+ function note(line) {
432
+ console.error(line);
433
+ }
434
+ function refuse(refusal, lines = []) {
435
+ return {
436
+ refusal,
437
+ lines
438
+ };
439
+ }
440
+ function emulatorImage(root, slot, requested, deps) {
441
+ const record = deviceSlotPlatforms(getProject(root), slot)?.android;
442
+ if (record?.avdName && !record.setupIncomplete) {
443
+ if (record.owned && deps.avdDirectory(record.avdName)) return { systemImage: deps.avdSystemImage(record.avdName) };
444
+ if (!record.owned && avdListed(record.avdName, deps.listAvds)) return { systemImage: null };
445
+ }
446
+ let images;
447
+ try {
448
+ images = deps.listSystemImages();
449
+ } catch (error) {
450
+ return { refusal: {
451
+ code: NO_DEVICE,
452
+ message: `Could not read the installed Android system images: ${error?.message || error}`,
453
+ remedy: "Check that ANDROID_HOME points at a readable SDK, then try again."
454
+ } };
455
+ }
456
+ const picked = pickDefaultSystemImage(images, requested ? { systemImage: requested } : {});
457
+ if (picked) return { systemImage: picked.pkg };
458
+ const arch = hostSystemImageArch();
459
+ return { refusal: {
460
+ code: NO_DEVICE,
461
+ message: `No ${arch} Android system image is installed, so the emulator build's ABI and cache key are unknown.`,
462
+ remedy: `Install one, e.g.: sdkmanager "system-images;android-36;google_apis;${arch}", then try again.`
463
+ } };
464
+ }
465
+ function avdListed(avdName, list) {
466
+ try {
467
+ return list({ timeoutMs: 1e4 }).includes(avdName);
468
+ } catch {
469
+ return false;
470
+ }
471
+ }
472
+ async function planReactNativeAndroid(root, opts, runtimeKind, { context: settingsContext, settings }, layout, variantProblem, overrides = {}) {
473
+ const deps = {
474
+ ...DEFAULT_PLAN_DEPS,
475
+ ...overrides
476
+ };
477
+ const slot = validateDeviceSlot(opts.slot);
478
+ const planned = resolveAndroidRunPlan({
479
+ settings,
480
+ settingsContext,
481
+ slot,
482
+ easProfile: opts.easProfile,
483
+ variant: opts.variant ?? null,
484
+ systemImage: opts.systemImage ?? null,
485
+ device: null,
486
+ wait: void 0,
487
+ waitConflict: false,
488
+ remote: null,
489
+ buildCache: opts.buildCache !== false
490
+ }, {
491
+ runtimeKind,
492
+ variantProblem,
493
+ warn: (label, message) => note(phaseLine(label, chalk.yellow(message))),
494
+ resolveCompilerCache: ({ optimizations }) => ({
495
+ cas: null,
496
+ optimizations,
497
+ warning: null
498
+ }),
499
+ listSystemImages: deps.listSystemImages
500
+ });
501
+ if (!planned.ok) return refuse(planned, planned.lines);
502
+ const { build, target, isExpo, cacheProviderConfig } = planned.plan;
503
+ const planTarget = {
504
+ platform: PLATFORM,
505
+ slot,
506
+ root,
507
+ projectKey: statsProjectKey({
508
+ root,
509
+ commonDir: settingsContext.gitCommonDir,
510
+ repoRoot: settingsContext.repoRoot
511
+ })
512
+ };
513
+ if (opts.easProfile !== void 0) {
514
+ const eas = await planEasDevelopmentBuild({
515
+ root,
516
+ platform: PLATFORM,
517
+ profile: opts.easProfile,
518
+ note,
519
+ isExpo,
520
+ selectors: [opts.variant],
521
+ buildCache: opts.buildCache
522
+ });
523
+ if (!eas.ok) return refuse(eas);
524
+ return planPayload(planTarget, {
525
+ fingerprint: eas.fingerprint,
526
+ cacheKey: eas.cacheKey,
527
+ cacheHit: eas.buildId ? "remote" : false,
528
+ provider: "eas",
529
+ cacheSkipped: false,
530
+ prebuild: null,
531
+ refusal: eas.missing
532
+ });
533
+ }
534
+ if (target.kind === "physical") return refuse(planFlagRefusal("--device"));
535
+ if (target.kind === "remote") return refuse({
536
+ code: "STIM_BAD_ARG",
537
+ message: `android.remote is ${target.backend}, whose ABI --plan cannot read without a session.`,
538
+ remedy: "Run `stim android` to build for the remote device, or unset android.remote to plan the owned emulator."
539
+ });
540
+ if (build.compilerCache === "cas") return refuse({
541
+ code: "STIM_BAD_ARG",
542
+ message: "The experimental Android compiler CAS keys builds by a toolchain Stim sets up, which --plan does not.",
543
+ remedy: "Run `stim android` to build with the CAS, or set optimizations.android.compilerCache to ccache."
544
+ });
545
+ const abiKeyed = build.targetAbiOnly && !build.release;
546
+ const localImage = () => abiKeyed ? emulatorImage(root, slot, target.systemImage, deps) : { systemImage: null };
547
+ let image;
548
+ let hostedAbi = null;
549
+ let placement;
550
+ if (target.kind === "hosted") {
551
+ const hostedPlan = await planHostedDevice({
552
+ root,
553
+ slot,
554
+ platform: "android",
555
+ machine: target.machine,
556
+ selectors: {
557
+ ...target.systemImage ? { systemImage: target.systemImage } : {},
558
+ ...target.deviceProfile ? { deviceProfile: target.deviceProfile } : {}
559
+ },
560
+ sameKey: (choice) => {
561
+ if (build.targetAbiOnly && build.release) return !("architecture" in choice && choice.architecture);
562
+ if (!abiKeyed) return true;
563
+ const local = localImage();
564
+ if ("refusal" in local || !("systemImage" in choice)) return null;
565
+ return androidSystemImageAbi(local.systemImage) === androidSystemImageAbi(choice.systemImage);
566
+ },
567
+ ...remoteEasFallbackSetting(settings) ? { eas: () => checkEasFallback({
568
+ root,
569
+ platform: "android",
570
+ slot,
571
+ release: build.release,
572
+ isExpo,
573
+ tunnelMode: tunnelModeSetting(settings),
574
+ publicUrl: publicUrlSetting(settings),
575
+ localOnlyFlags: typeof opts.systemImage === "string" ? ["--system-image"] : []
576
+ }) } : {}
577
+ });
578
+ if (hostedPlan.kind === "unknown") return refuse({
579
+ code: "STIM_BAD_ARG",
580
+ message: `android.remote is ${target.machine}, and its emulator ABI is unknown here: ${hostedPlan.reason}.`,
581
+ remedy: "Run `stim android` to build for the remote device, or unset android.remote to plan the owned emulator."
582
+ });
583
+ placement = hostedPlan.placement;
584
+ if (hostedPlan.kind === "hosted" && "architecture" in hostedPlan.choice) hostedAbi = hostedPlan.choice.architecture ?? null;
585
+ image = hostedPlan.kind === "hosted" ? { systemImage: abiKeyed && "systemImage" in hostedPlan.choice ? hostedPlan.choice.systemImage : null } : localImage();
586
+ } else image = localImage();
587
+ if ("refusal" in image) return refuse(image.refusal);
588
+ const { abi, runOptions, remoteRunOptions } = androidBuildOptions({
589
+ release: build.release,
590
+ physical: false,
591
+ device: { systemImage: image.systemImage },
592
+ variant: build.variant,
593
+ deviceAbi: () => null,
594
+ gradleProject: androidGradleProject(layout),
595
+ buildProfile: build.profile,
596
+ targetAbiOnly: build.targetAbiOnly,
597
+ hostedAbi
598
+ });
599
+ let fingerprint;
600
+ try {
601
+ fingerprint = await deps.fingerprint(root, {
602
+ platform: PLATFORM,
603
+ androidLayout: layout
604
+ });
605
+ } catch (error) {
606
+ return refuse({
607
+ code: NO_FINGERPRINT,
608
+ message: `@expo/fingerprint could not fingerprint ${root}: ${error?.message || error}`,
609
+ remedy: "Fix the @expo/fingerprint error above, then retry."
610
+ });
611
+ }
612
+ if (!fingerprint?.hash) return refuse({
613
+ code: NO_FINGERPRINT,
614
+ message: `@expo/fingerprint returned no hash for ${root}, so the build cache cannot be addressed.`,
615
+ remedy: "Check the project native inputs and the @expo/fingerprint error above, then retry."
616
+ });
617
+ const plan = await planCachedBuild({
618
+ ...planTarget,
619
+ isExpo,
620
+ fingerprint: fingerprint.hash,
621
+ sources: fingerprint.sources ?? [],
622
+ cacheKey: buildCacheKey(PLATFORM, fingerprint.hash, runOptions),
623
+ cachePolicy: build.cache,
624
+ providerConfig: cacheProviderConfig,
625
+ expoRemote: abi || build.profile || !build.cache.remote ? null : { runOptions: remoteRunOptions }
626
+ }, {
627
+ ...deps,
628
+ note
629
+ });
630
+ return placement ? {
631
+ ...plan,
632
+ placement
633
+ } : plan;
634
+ }
635
+ //#endregion
636
+ //#region src/integrations/react-native-android.ts
637
+ const runtimeKind = ({ release }) => release ? "embedded-js" : "metro";
638
+ function reactNativeAndroidProject(root, { context, settings }, dependencies = {}) {
639
+ const isExpo = detectIsExpo(root);
640
+ const layout = resolveAndroidLayout(settings, root, context.repoRoot ?? root);
641
+ const variantProblem = (variant) => productFlavorRefusal({
642
+ flavors: readProductFlavors(layout),
643
+ variant
644
+ });
645
+ return {
646
+ plan: (options, resolved) => planReactNativeAndroid(root, options, runtimeKind, resolved, layout, variantProblem, dependencies.plan),
647
+ isExpo,
648
+ appIds: () => detectAppIds(root, void 0, layout.moduleDir),
649
+ packageRemedy: `Set \`expo.android.package\` in app.json / app.config.js, or \`namespace\` in ${relative(root, layout.moduleDir) || "."}/build.gradle.`,
650
+ variantProblem,
651
+ targets: [
652
+ "emulator",
653
+ "physical",
654
+ "hosted",
655
+ "remote"
656
+ ],
657
+ eas: true,
658
+ runtimeKind,
659
+ runtime: ({ build, prepareMetro, phase }) => runtimeKind(build) === "embedded-js" ? androidProcessRuntime(async () => {
660
+ phase("metro", `skipped (${build.variant}: the JS bundle is embedded, no dev server is used)`);
661
+ return {
662
+ ok: true,
663
+ prepared: { metroPort: null }
664
+ };
665
+ }, "embedded-js") : androidMetroRuntime(prepareMetro),
666
+ artifact: (artifactContext) => reactNativeAndroidArtifact(artifactContext, isExpo, layout, context, dependencies)
667
+ };
668
+ }
669
+ function reactNativeAndroidArtifact({ root, buildLog, writer, settings, buildPlan, target, phase, out, step, estimates }, isExpo, layout, context, { fingerprint = fingerprintProject, untracked = untrackedNativeFiles, resolveCached = resolveBuild, storeCached = storeBuild, storedAssets = storedAssetManifest, captureAssets = captureAssetManifest, loadProvider = loadProjectProvider, planPrebuildFor = planPrebuild, prebuild = runPrebuild, build = buildAndroid, ccacheFor = resolveCcache, swapApk = swapApkBundle, readState = readWorkspaceState }) {
670
+ const { variant, release, profile, cas } = buildPlan;
671
+ const runOptions = {
672
+ ...variant ? { variant } : {},
673
+ ...target.abi ? { abi: target.abi } : {},
674
+ ...cas?.id ? { compiler: cas.id } : {},
675
+ ...profile ? { buildProfile: profile } : {},
676
+ ...androidGradleProject(layout) ? { gradleProject: androidGradleProject(layout) } : {}
677
+ };
678
+ let hash = "";
679
+ let sources = [];
680
+ let initialSources = [];
681
+ let prebuildRan = false;
682
+ let editedConfig = [];
683
+ const identity = () => ({
684
+ hash,
685
+ key: buildCacheKey("android", hash, runOptions)
686
+ });
687
+ const materialize = async (key, cachedPath) => {
688
+ if (!release) return {
689
+ apkPath: cachedPath,
690
+ directory: null
691
+ };
692
+ phase("swap", `regenerating this workspace's JS for the cached ${variant} APK`);
693
+ let keystore;
694
+ try {
695
+ keystore = resolveKeystore(root, settings, layout, settingOriginScope(settingsLayers(context), "android.keystore") === "committed" ? context.repoRoot ?? root : null);
696
+ } catch (error) {
697
+ phase("swap", chalk.yellow(`${error.message} -- building fresh instead`));
698
+ return null;
699
+ }
700
+ const swap = await swapApk({
701
+ root,
702
+ isExpo,
703
+ cachedApkPath: cachedPath,
704
+ keystore,
705
+ layout,
706
+ logWriter: writer,
707
+ storedAssets: storedAssets("android", key)
708
+ });
709
+ if (swap?.ok && swap.apkPath) {
710
+ if (swap.note) phase("swap", chalk.yellow(swap.note));
711
+ phase("swap", `${swap.hermes ? "hermes bytecode" : "plain JS"} repacked (store), zipaligned and re-signed (${formatDuration(swap.durationMs)})`);
712
+ return {
713
+ apkPath: swap.apkPath,
714
+ directory: swap.tmpDir ?? null
715
+ };
716
+ }
717
+ if (swap?.assetMismatch) phase("swap", chalk.yellow(`${swap.reason} -- building fresh instead` + (swap.assetDiff ? " (an APK cannot be made to carry an asset AAPT did not package)" : "")));
718
+ else phase("swap", chalk.yellow(`failed at ${swap?.step || "unknown step"}: ${swap?.reason || "unknown reason"} -- building fresh instead (a cached ${variant} APK carries its builder's JS; it is never installed after a failed swap)`));
719
+ for (const line of swap?.lastLines ?? []) phase("", chalk.dim(line));
720
+ return null;
721
+ };
722
+ return {
723
+ async identity() {
724
+ let computed;
725
+ try {
726
+ computed = await fingerprint(root, {
727
+ platform: "android",
728
+ androidLayout: layout
729
+ });
730
+ } catch (error) {
731
+ const message = String(error?.message || error);
732
+ throw new AndroidRecipeRefusal({
733
+ code: NO_FINGERPRINT,
734
+ message: `@expo/fingerprint could not fingerprint ${root}: ${message}`,
735
+ remedy: "Fix the @expo/fingerprint error above, then retry.",
736
+ extra: { lastBuildStatus: true }
737
+ }, fingerprintErrorMissReason(message));
738
+ }
739
+ if (!computed?.hash) throw new AndroidRecipeRefusal({
740
+ code: NO_FINGERPRINT,
741
+ message: `@expo/fingerprint returned no hash for ${root}, so the build cache cannot be addressed.`,
742
+ remedy: "Check the project native inputs and the @expo/fingerprint error above, then retry.",
743
+ extra: { lastBuildStatus: true }
744
+ }, fingerprintErrorMissReason("no hash"));
745
+ hash = computed.hash;
746
+ sources = initialSources = computed.sources ?? [];
747
+ return identity();
748
+ },
749
+ cache: (compiled = false) => filesystemBuildCapability({
750
+ resolve: resolveCached,
751
+ store: storeCached,
752
+ sources,
753
+ ...compiled ? { assetManifest: release ? captureAssets(root, {
754
+ variant,
755
+ layout
756
+ }) : null } : {}
757
+ }),
758
+ async prepare(beforePrepare) {
759
+ const plan = planPrebuildFor(root, "android", {
760
+ isExpo,
761
+ fingerprint: hash,
762
+ sources
763
+ });
764
+ prebuildRan = plan === "generate" || plan === "regenerate";
765
+ if (plan === "refuse") {
766
+ const refusal = staleNativeDirRefusal("android");
767
+ throw new AndroidRecipeRefusal({
768
+ ...refusal,
769
+ extra: { lastBuildStatus: true }
770
+ });
771
+ }
772
+ if (!prebuildRan) return;
773
+ beforePrepare();
774
+ step("prebuild");
775
+ recordPrebuild(root, "android", null);
776
+ const pre = await prebuild(root, "android", writer, {
777
+ isExpo,
778
+ clean: plan === "regenerate"
779
+ });
780
+ if (pre.failed) throw new AndroidRecipeRefusal({
781
+ code: pre.code,
782
+ message: pre.reason,
783
+ remedy: pre.remedy,
784
+ extra: {
785
+ lastBuildStatus: true,
786
+ lines: (pre.lastLines ?? []).slice(-5),
787
+ logPath: displayPath(root, buildLog)
788
+ }
789
+ });
790
+ phase("prebuild", `${plan === "generate" ? "android/ generated" : "android/ not generated from this fingerprint -> regenerated with --clean"} (${formatDuration(pre.durationMs)})`);
791
+ },
792
+ async reconcile() {
793
+ const rekeyedBy = [];
794
+ if (prebuildRan) {
795
+ const after = await refingerprintAfterMutation({
796
+ projectRoot: root,
797
+ platform: "android",
798
+ androidLayout: layout,
799
+ previousHash: hash,
800
+ fingerprint
801
+ });
802
+ editedConfig = after ? configInputsChanged(initialSources, after.sources, { prebuildRan }) : [];
803
+ if (after && !editedConfig.length) recordPrebuild(root, "android", after.hash);
804
+ if (after?.moved && !editedConfig.length) {
805
+ rekeyedBy.push("prebuild");
806
+ phase("fingerprint", chalk.dim(`${shortHash(hash)} -> ${shortHash(after.hash)} (after prebuild)`));
807
+ hash = after.hash;
808
+ sources = after.sources;
809
+ }
810
+ }
811
+ return {
812
+ identity: identity(),
813
+ rekeyedBy,
814
+ cacheRefusal: editedConfig.length ? "prebuild changed config inputs, so the APK cannot be cached" : null,
815
+ ...prebuildRan ? { androidPackage: detectAndroidPackage(root, layout.moduleDir) } : {}
816
+ };
817
+ },
818
+ async validate() {
819
+ const after = editedConfig.length ? null : await refingerprintAfterMutation({
820
+ projectRoot: root,
821
+ platform: "android",
822
+ androidLayout: layout,
823
+ previousHash: hash,
824
+ fingerprint
825
+ });
826
+ const changed = after ? inputsChangedDuringBuild({
827
+ platform: "android",
828
+ lookup: initialSources,
829
+ prebuildRan,
830
+ compiled: sources,
831
+ current: after.sources
832
+ }) : editedConfig;
833
+ if (!after || changed.length) {
834
+ phase("fingerprint", chalk.yellow(changed.length ? changedDuringBuildLine(changed) : "unavailable after Gradle; the build will be installed but not cached"));
835
+ return null;
836
+ }
837
+ if (after.moved) {
838
+ phase("fingerprint", chalk.dim(`${shortHash(hash)} -> ${shortHash(after.hash)} (after Gradle)`));
839
+ hash = after.hash;
840
+ sources = after.sources;
841
+ }
842
+ return identity();
843
+ },
844
+ materialize,
845
+ compile: () => build({
846
+ root,
847
+ layout,
848
+ logWriter: writer,
849
+ variant,
850
+ abi: target.abi
851
+ }, {
852
+ estimateMs: estimates().coldBuildMs,
853
+ ccache: buildPlan.compilerCache === "ccache" ? ccacheFor({
854
+ root,
855
+ layout,
856
+ onNote: out
857
+ }) : null,
858
+ cas,
859
+ buildCache: buildPlan.gradleBuildCache,
860
+ pch: buildPlan.pch,
861
+ compilerCacheDisabled: buildPlan.compilerCache === "none"
862
+ }),
863
+ explain(rekeyedBy) {
864
+ const explained = explainBuildMiss({
865
+ root,
866
+ platform: "android",
867
+ current: {
868
+ hash,
869
+ sources
870
+ },
871
+ rekeyedBy,
872
+ baselineDeps: { readState }
873
+ });
874
+ return {
875
+ reason: explained.reason,
876
+ diff: explained.previousHash && explained.changedNames.length ? fingerprintDiffRecord({
877
+ changed: explained.changedNames,
878
+ previousHash: explained.previousHash,
879
+ hash
880
+ }) : null
881
+ };
882
+ },
883
+ untrackedLine: () => untrackedMissLine(untracked({ projectRoot: root })),
884
+ legacyCache: target.abi || cas || profile || !buildPlan.cache.remote ? null : {
885
+ load: () => loadProvider(root, { isExpo }),
886
+ runOptions: Object.keys(runOptions).length ? runOptions : null
887
+ },
888
+ offload: {
889
+ unsupported: release ? `${variant} builds build here` : cas ? "Apple Clang CAS builds build here" : !buildPlan.cache.write ? "the build cache is off" : null,
890
+ target: () => ({
891
+ platform: "android",
892
+ local: androidToolchain(),
893
+ requires: androidRequirements(root)
894
+ }),
895
+ request: {
896
+ platform: "android",
897
+ isExpo,
898
+ android: {
899
+ variant,
900
+ abi: target.abi,
901
+ gradleBuildCache: buildPlan.gradleBuildCache,
902
+ pch: buildPlan.pch,
903
+ compilerCache: buildPlan.compilerCache === "none" ? "none" : "ccache",
904
+ ...androidJobLayout(layout)
905
+ }
906
+ },
907
+ unchanged: async () => {
908
+ const after = await refingerprintAfterMutation({
909
+ projectRoot: root,
910
+ platform: "android",
911
+ androidLayout: layout,
912
+ previousHash: hash,
913
+ fingerprint
914
+ });
915
+ return Boolean(after && !after.moved);
916
+ }
917
+ }
918
+ };
919
+ }
920
+ //#endregion
921
+ export { reactNativeAndroidProject };