stim 1.25.0 → 1.27.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 (182) hide show
  1. package/README.md +1 -1
  2. package/dist/{acquire-artifact-zA4S7RHE.mjs → acquire-artifact-BKN2Yuhg.mjs} +2 -2
  3. package/dist/{act-BbHS3oV8.mjs → act-ROviTF_t.mjs} +415 -118
  4. package/dist/{activity-BpjwrQXU.mjs → activity-Cd-ysFes.mjs} +6 -7
  5. package/dist/{agent-device-usage-output-Bnkerfod.mjs → agent-device-usage-output-C5nCKWp9.mjs} +5 -5
  6. package/dist/{android-CNZgRC-4.mjs → android--HBgpvpW.mjs} +5 -5
  7. package/dist/{android-BGwuK3Mc.mjs → android-Cd6w7_qw.mjs} +134 -57
  8. package/dist/{android-saBeB4lr.mjs → android-Deb9X3_Y.mjs} +37 -15
  9. package/dist/{android-Cjcj8MCw.mjs → android-DriNoAVy.mjs} +99 -47
  10. package/dist/{android-cas-BVqh2Zep.mjs → android-cas-D5e6ecUg.mjs} +4 -4
  11. package/dist/android-cas-compiler.mjs +1 -1
  12. package/dist/{android-project-CTYaETc0.d.mts → android-project-DUGMbq28.d.mts} +55 -1
  13. package/dist/api.d.mts +20 -7
  14. package/dist/api.mjs +42 -32
  15. package/dist/apk-signed-tail-BHQHx3Rm.mjs +142 -0
  16. package/dist/{artifact-Co8zixVx.mjs → artifact-0AGJYB9O.mjs} +28 -28
  17. package/dist/{artifact-DN8JNwqS.mjs → artifact-faemIZv5.mjs} +40 -30
  18. package/dist/{artifact-platform-CMRc0M_r.mjs → artifact-platform-BO2Uvk_A.mjs} +1 -1
  19. package/dist/{attempt-DW-mQqM7.mjs → attempt-ClyJlZPT.mjs} +4 -2
  20. package/dist/auto-placement-z-H5pJza.mjs +350 -0
  21. package/dist/{browser-web-CCgZsHmI.mjs → browser-web-slD8AvLp.mjs} +16 -17
  22. package/dist/{build-cache-CKCzxzxA.mjs → build-cache-gBJ-WKzA.mjs} +141 -37
  23. package/dist/{build-CAiw8Ezm.mjs → build-hS0QyFlQ.mjs} +38 -31
  24. package/dist/{build-plan-zCuHGlA8.mjs → build-plan-CBqYbzYM.mjs} +7 -6
  25. package/dist/{cache-manifest-BPXRONJz.mjs → cache-manifest-DEjqGGat.mjs} +1 -1
  26. package/dist/cache-manifest.mjs +1 -1
  27. package/dist/{budget-HyShrIpL.mjs → ccache-BWTze1Ls.mjs} +634 -1514
  28. package/dist/{cdp-BG3FJCCx.mjs → cdp-C3svvXiE.mjs} +1 -1
  29. package/dist/{chrome-BvV8ssI3.mjs → chrome-DyDWurWG.mjs} +1 -1
  30. package/dist/{cli-C4w607Pz.mjs → cli-ftXDLto1.mjs} +24 -23
  31. package/dist/cli.mjs +1 -1
  32. package/dist/{client-BQsmyp_-.mjs → client-xsdxrhWA.mjs} +77 -116
  33. package/dist/collector-run.d.mts +10 -9
  34. package/dist/collector-run.mjs +42 -27
  35. package/dist/{command-output-BBvS1H6v.mjs → command-output-Dj-WT4BP.mjs} +1 -1
  36. package/dist/{config-BZhXY15H.mjs → config-Cn94Jc0g.mjs} +343 -18
  37. package/dist/{launch-DscF1QfM.mjs → dependencies-BGokZ-Ng.mjs} +330 -279
  38. package/dist/{dependency-state-BwNCYG5Q.mjs → dependency-state-C3OtgG94.mjs} +14 -7
  39. package/dist/{detached-entry-KmieOKgN.mjs → detached-entry-BPlSaCwC.mjs} +1 -1
  40. package/dist/{dev-client-CHoXXZeg.mjs → dev-client-BMca82xo.mjs} +2 -2
  41. package/dist/{device-F5-4z9ef.mjs → device-BJm0irrq.mjs} +16 -16
  42. package/dist/{device-capacity-ZYaRALv5.mjs → device-capacity-6LP7QsaA.mjs} +103 -25
  43. package/dist/device-host-worker.mjs +19 -21
  44. package/dist/{device-ios-DkHBrmvH.mjs → device-ios-534c14fz.mjs} +288 -28
  45. package/dist/{device-lease-run-ogEeJ62K.mjs → device-lease-run-DwsGfjUN.mjs} +5 -5
  46. package/dist/{device-pool-BIg8MU8l.mjs → device-pool-BnQqvs6o.mjs} +3 -3
  47. package/dist/{device-B9FqTBYW.mjs → device-tM41-TUq.mjs} +25 -24
  48. package/dist/{doctor-A_Nr_fDe.mjs → doctor-Dbx1ZpD2.mjs} +1 -1
  49. package/dist/{doctor-k3P3PizH.mjs → doctor-DyqE9n1J.mjs} +28 -16
  50. package/dist/{doctor-Bm8TVpGn.mjs → doctor-F-3VUUyN.mjs} +128 -35
  51. package/dist/{doctor-fingerprint-zlujA1Gs.mjs → doctor-fingerprint-o7ZI9YLn.mjs} +9 -3
  52. package/dist/doctor-fingerprint.mjs +1 -1
  53. package/dist/{doctor-watchman-BovI1y3-.mjs → doctor-watchman-CxDbyrrt.mjs} +5 -5
  54. package/dist/{eas-build-CyDKNTBP.mjs → eas-build-CjNSWmjo.mjs} +4 -4
  55. package/dist/eas-fallback-9fqb46kW.mjs +87 -0
  56. package/dist/{eas-session-ledger-BMCA1gdO.mjs → eas-session-ledger-DrP4OuuM.mjs} +6 -6
  57. package/dist/{error-diagnostics-B1Mj_-7h.mjs → error-diagnostics-snej9k4j.mjs} +51 -25
  58. package/dist/{exec-VFRpu1iA.mjs → exec-aULwtNly.mjs} +57 -6
  59. package/dist/{gc-eaz403W6.mjs → gc-BRwAY3qT.mjs} +2 -2
  60. package/dist/{gc-B9jcnmIk.mjs → gc-CGZ1Yg88.mjs} +218 -72
  61. package/dist/{guide-DR0K0xW9.mjs → guide-_rfEh_zM.mjs} +839 -222
  62. package/dist/{hosted-android-Bvb2-ZxD.mjs → hosted-android-BMKzUNnY.mjs} +4 -4
  63. package/dist/{hosted-client-rsRqysJc.mjs → hosted-client-D7Pk_Oo1.mjs} +13 -9
  64. package/dist/{hosted-logs-CWKGYWNX.mjs → hosted-logs-B1gqQuva.mjs} +18 -12
  65. package/dist/hosted-logs-sync-Dg3qrZmV.mjs +195 -0
  66. package/dist/{hosted-macos-d0pv9Tth.mjs → hosted-macos-B9w4H8hc.mjs} +6 -6
  67. package/dist/{hosted-native-BxTizGU2.mjs → hosted-native-CdgaiuyP.mjs} +17 -17
  68. package/dist/{idle-shutdown-BIyfl0Zj.mjs → idle-shutdown-Cbvo5KEh.mjs} +21 -9
  69. package/dist/{ios-BRVM8b0P.mjs → ios-B5HlVMhQ.mjs} +138 -55
  70. package/dist/{ios-Dvt97HgQ.mjs → ios-Bk4mWbtS.mjs} +8 -8
  71. package/dist/{ios-ENC4sRPA.mjs → ios-CsRVik64.mjs} +7 -1
  72. package/dist/{ios-BQzvJlzw.mjs → ios-E26qSpkb.mjs} +94 -11
  73. package/dist/{ios-device-CKSvk0bu.mjs → ios-device-C_QkIf1Z.mjs} +3 -3
  74. package/dist/{ios-device-B6HK951C.mjs → ios-device-CiBA-er9.mjs} +1 -1
  75. package/dist/{ios-project-DCUDo06I.mjs → ios-project-D8Jcy_br.mjs} +22 -9
  76. package/dist/{ios-signing-4c_qXvQm.mjs → ios-signing-BJzvDPfU.mjs} +2 -2
  77. package/dist/{ios-state-CT9nV5SP.mjs → ios-state-BZ03NFgF.mjs} +1 -1
  78. package/dist/{js-swap-Z9HbC4cj.mjs → js-swap-Dg2a945i.mjs} +8 -7
  79. package/dist/{launch-D0lPvFFu.mjs → launch-CRyBeNsA.mjs} +53 -44
  80. package/dist/lightened-BCsdAGj_.mjs +171 -0
  81. package/dist/{logs-BsIRre8x.mjs → logs-CC_-ZzEE.mjs} +10 -174
  82. package/dist/{logs-Crs9y34Z.mjs → logs-aJQb-RDg.mjs} +112 -38
  83. package/dist/{macos-CcUlJPMu.mjs → macos-BGmTpnv9.mjs} +1 -1
  84. package/dist/{macos-D477nfzH.mjs → macos-DlCBhMw5.mjs} +73 -39
  85. package/dist/macos-run.mjs +1 -1
  86. package/dist/maintenance-run.mjs +30 -13
  87. package/dist/{metro-1pJgCX4U.mjs → metro-CvyD-xwe.mjs} +4 -4
  88. package/dist/{metro-gateway-Bp9gJQAN.mjs → metro-gateway-BnHdqMO_.mjs} +3 -4
  89. package/dist/{named-ports-BVbn8SvC.mjs → named-ports-BCyMWA6b.mjs} +3 -3
  90. package/dist/{native-android-BvMizJn1.mjs → native-android-DlpxCyOP.mjs} +17 -18
  91. package/dist/{native-gradle-artifact-inputs-BJscK4vN.mjs → native-gradle-artifact-inputs-C13qT7R_.mjs} +5 -5
  92. package/dist/{native-runtime-B9w_Qyz3.mjs → native-runtime-DqaIBO5w.mjs} +9 -9
  93. package/dist/{native-source-D-4Dh4JU.mjs → native-source-D1AzWiuP.mjs} +1 -1
  94. package/dist/{native-xcode-inputs-Cv4l-obn.mjs → native-xcode-inputs-B1W8SrxB.mjs} +3 -3
  95. package/dist/{native-xcode-ios-DGrg90hE.mjs → native-xcode-ios-QPP0Rm68.mjs} +17 -18
  96. package/dist/{ndjson-CgE_o3Dl.mjs → ndjson-Dy2W4vRa.mjs} +2 -2
  97. package/dist/offload-worker.d.mts +3 -1
  98. package/dist/offload-worker.mjs +34 -25
  99. package/dist/{operation-exec-CUZifp6Z.mjs → operation-exec-Dp4FuIGR.mjs} +1 -1
  100. package/dist/{ownership-jxxz-19m.mjs → ownership-Bx-0cku-.mjs} +3 -3
  101. package/dist/{page-CzEA_-5_.mjs → page-CQJlcj3x.mjs} +2 -2
  102. package/dist/{phone-reverses-CYb7RRAx.mjs → phone-reverses-C9yAaKEv.mjs} +18 -4
  103. package/dist/{placement-log-DjOXJRIH.mjs → placement-log-BXUbiZM6.mjs} +3 -3
  104. package/dist/{plan-placement-BqKgKHCi.mjs → plan-placement-UxnQjv5Z.mjs} +36 -10
  105. package/dist/{ports-DtS1Amb0.mjs → ports-uUrghqWN.mjs} +3 -3
  106. package/dist/prebuild-BDpToDgR.mjs +1246 -0
  107. package/dist/{project-BiC68mIp.mjs → project-DYj-HVx6.mjs} +17 -18
  108. package/dist/{pull-request-CbNlkFok.mjs → pull-request-BzLmzAIQ.mjs} +2 -2
  109. package/dist/pull-requests.mjs +1 -1
  110. package/dist/{react-native-android-DRYqoJc4.mjs → react-native-android-DDBccgJw.mjs} +56 -36
  111. package/dist/{react-native-build-CwZB-agu.mjs → react-native-build-DaK2IFyP.mjs} +9 -9
  112. package/dist/{react-native-doctor-BCrytK7h.mjs → react-native-doctor-B-Mw6DcR.mjs} +50 -9
  113. package/dist/{react-native-ios-Cr1buNFo.mjs → react-native-ios-DjYwUF9U.mjs} +45 -25
  114. package/dist/{recordings-BaoQ0t-J.mjs → recordings-C20iUZfp.mjs} +1 -1
  115. package/dist/{registration-CN6nlhcD.mjs → registration-BwCe64PH.mjs} +1 -1
  116. package/dist/{reload-Beftw2X3.mjs → reload-DJBeF_rC.mjs} +1 -1
  117. package/dist/{reload-FbSgMF8x.mjs → reload-fUdkH2_p.mjs} +16 -16
  118. package/dist/{remote-BuZj6AyU.mjs → remote-y6n99WMW.mjs} +22 -9
  119. package/dist/{result-CR2vk2cj.mjs → result-CTqBw2Te.mjs} +32 -21
  120. package/dist/{run-BtzE09Dl.mjs → run-DaCeNKRm.mjs} +3 -3
  121. package/dist/{selection-D5cxEl3t.mjs → selection-Bs8phwhN.mjs} +183 -62
  122. package/dist/{server-bare-CH9X0RLn.mjs → server-bare-BOh-iZEj.mjs} +2 -2
  123. package/dist/{server-command-CaBbDNU2.mjs → server-command-8CuA51Cv.mjs} +6 -6
  124. package/dist/{server-expo-Ck8EIidM.mjs → server-expo-ABQs2XFA.mjs} +4 -4
  125. package/dist/server-expo-Ba_Y21vB.mjs +2 -0
  126. package/dist/{settings-B5tCoq9N.mjs → settings-BDVI8Ivc.mjs} +331 -62
  127. package/dist/{settings-DzH9mGQZ.mjs → settings-CMHhQNwI.mjs} +24 -3
  128. package/dist/settings-payload-cm60Zhib.mjs +257 -0
  129. package/dist/settings-write-BdNRLlBn.mjs +185 -0
  130. package/dist/settings.schema.json +84 -39
  131. package/dist/{simslim-BjxszUAb.mjs → simslim-DCQwzoPy.mjs} +4 -3
  132. package/dist/{slot-launch-QeuJMlnJ.mjs → slot-launch-0l7RqSpr.mjs} +6 -6
  133. package/dist/{stage-D8xlRU2D.mjs → stage-F4cAfJGi.mjs} +2 -2
  134. package/dist/{start-C1JLjkNw.mjs → start-DcqnF9p8.mjs} +1 -1
  135. package/dist/{start-YMpNwLhh.mjs → start-DrPwER_2.mjs} +27 -23
  136. package/dist/{state-DMRNP7-4.mjs → state-BX1y5syV.mjs} +4 -5
  137. package/dist/{state-DPaM3u5K.mjs → state-CZxLyetD.mjs} +2 -3
  138. package/dist/{stats-CP7STzZw.mjs → stats-GxuCbles.mjs} +5 -5
  139. package/dist/{stats-report-n2U5plhk.mjs → stats-report-B86Cv7Uc.mjs} +1 -1
  140. package/dist/{status-Fect2w9R.mjs → status-7ULDEJ1e.mjs} +1 -1
  141. package/dist/{status-CRLiJ-O_.mjs → status-BfnK9pUd.mjs} +25 -26
  142. package/dist/{status-cNw6i0_t.mjs → status-Bj7ZYvvc.mjs} +1 -1
  143. package/dist/{status-watch-Bc2J4TZn.mjs → status-watch-BfabQn03.mjs} +5 -5
  144. package/dist/status-worker.mjs +1 -1
  145. package/dist/{stim-desktop-DS0mzR8U.mjs → stim-desktop-CbWo21kb.mjs} +2 -2
  146. package/dist/{stop-COP6PQqw.mjs → stop-CH79S3IZ.mjs} +5 -6
  147. package/dist/{stop-Db9Kh-UA.mjs → stop-D-EAr25B.mjs} +25 -22
  148. package/dist/{stop-Br7LKxBE.mjs → stop-x1Yu3He4.mjs} +2 -2
  149. package/dist/supervisor-run.d.mts +1 -0
  150. package/dist/supervisor-run.mjs +17 -18
  151. package/dist/{support-qKcbgmI5.mjs → support-Be0UeVZU.mjs} +10 -10
  152. package/dist/{support-Cxj9CmII.mjs → support-CNg_YsOk.mjs} +6 -6
  153. package/dist/{swiftpm-artifact-inputs-iY9fsmOc.mjs → swiftpm-artifact-inputs-uPqYvGBv.mjs} +4 -4
  154. package/dist/{swiftpm-macos-Bp_sn9P4.mjs → swiftpm-macos-Ch4YjY_A.mjs} +11 -12
  155. package/dist/toolchain-CbCG6C5Z.mjs +2413 -0
  156. package/dist/toolchain-JYAOquPg.mjs +2 -0
  157. package/dist/{trigger-CBMe2YLH.mjs → trigger-DMou-X7j.mjs} +3 -3
  158. package/dist/trigger-qp2ji5wE.mjs +2 -0
  159. package/dist/{warm-progress-BV3ZPmcY.mjs → warm-progress-C0RsbngN.mjs} +731 -728
  160. package/dist/{web-B4Yjyt1G.mjs → web-Cq_AQ4DK.mjs} +9 -10
  161. package/dist/web-Je6abcpz.mjs +2 -0
  162. package/dist/web-run.mjs +9 -9
  163. package/dist/windows-report.mjs +1 -1
  164. package/dist/{workspace-state-CPSp3mav.mjs → workspace-state-BjqdjnJR.mjs} +46 -8
  165. package/dist/{workspaces-C9w4aeXF.mjs → workspaces-DFbj07jp.mjs} +1801 -76
  166. package/dist/{worktree-CclFIe1Z.mjs → worktree-COAJ3-OD.mjs} +934 -511
  167. package/dist/{worktree-Ct9Gk3KL.mjs → worktree-JGUd_53L.mjs} +1 -1
  168. package/package.json +8 -8
  169. package/dist/ccache-h1FKhZeg.mjs +0 -143
  170. package/dist/created-devices-D2Aca-xu.mjs +0 -52
  171. package/dist/eas-fallback-gaZi-dJL.mjs +0 -381
  172. package/dist/hosted-ios-RtVcJH3u.mjs +0 -15
  173. package/dist/native-run-D5OJsGwA.mjs +0 -276
  174. package/dist/paths-DQtTgsBf.mjs +0 -42
  175. package/dist/prebuild-JUaqOLpc.mjs +0 -501
  176. package/dist/server-expo-C16Zf2k7.mjs +0 -2
  177. package/dist/settings-write-n0zwfohx.mjs +0 -360
  178. package/dist/toolchain-DB1aeqxO.mjs +0 -2
  179. package/dist/toolchain-VzLJzZ7O.mjs +0 -908
  180. package/dist/trigger-BLsHgAGR.mjs +0 -2
  181. package/dist/web-DS4UDWvf.mjs +0 -2
  182. package/dist/xcode-CiIIDnuD.mjs +0 -910
@@ -0,0 +1,1246 @@
1
+ import { E as debugLog, _ as spawnDeclared, t as getExecutor } from "./exec-aULwtNly.mjs";
2
+ import { M as withDirLock$1, l as withWorkspaceStateLockAsync, o as updateWorkspaceState, r as readWorkspaceState$1, y as sharedBuildCache } from "./workspace-state-BjqdjnJR.mjs";
3
+ import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
4
+ import { m as shortHash } from "./command-output-Dj-WT4BP.mjs";
5
+ import { o as git, s as gitAsync } from "./operation-exec-Dp4FuIGR.mjs";
6
+ import { St as gitCommonDirOnDisk } from "./settings-BDVI8Ivc.mjs";
7
+ import { v as detectIsExpo } from "./project-DYj-HVx6.mjs";
8
+ import { Vn as remoteCliInvocation } from "./ccache-BWTze1Ls.mjs";
9
+ import { r as register } from "./cache-manifest-DEjqGGat.mjs";
10
+ import { $ as STIM_VERSION } from "./workspaces-DFbj07jp.mjs";
11
+ import { D as storedSources, m as fingerprintProject, o as diffFingerprintSources, p as fingerprintOptions, u as expoEnvFiles, v as packageIn } from "./build-cache-gBJ-WKzA.mjs";
12
+ import { i as expoBinRefusal, o as expoSdkMajor, r as expoBinPath, s as expoTelemetryEnv } from "./server-expo-ABQs2XFA.mjs";
13
+ import { t as readDoctorFingerprint } from "./doctor-fingerprint-o7ZI9YLn.mjs";
14
+ import { createRequire } from "node:module";
15
+ import { createHash } from "node:crypto";
16
+ import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
17
+ import { dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
18
+ import { ambientContext } from "@stim-cli/core";
19
+ import { FINGERPRINT_MEMO_SCHEMA, LAST_BUILD_KEYS, fingerprintMemoDir, fingerprintMemoFile, fingerprintMemoLock, loadConfig, readFingerprintMemo, readWorkspaceState, trace } from "@stim-cli/core/state";
20
+ import { readFile } from "node:fs/promises";
21
+ import * as expoFingerprint from "@expo/fingerprint";
22
+ import { normalizeOptionsAsync } from "@expo/fingerprint/build/Options.js";
23
+ import { isIgnoredPathWithMatchObjects } from "@expo/fingerprint/build/utils/Path.js";
24
+ //#region src/cache/static-fingerprint.ts
25
+ const STATIC_SCHEMA = 1;
26
+ /** More files than this outside git, or ignored by git, means the static check reads too much to be worth it. */
27
+ const UNTRACKED_FILE_CAP = 2e3;
28
+ const CONFIG_FILES = [
29
+ "app.json",
30
+ "app.config.ts",
31
+ "app.config.js",
32
+ "app.config.mjs",
33
+ "app.config.cjs",
34
+ "app.config.json"
35
+ ];
36
+ const FINGERPRINT_CONFIGS = ["fingerprint.config.js", "fingerprint.config.cjs"];
37
+ const RN_CONFIGS = [
38
+ "react-native.config.js",
39
+ "react-native.config.ts",
40
+ "react-native.config.cjs",
41
+ "react-native.config.mjs"
42
+ ];
43
+ const ROOT_FILES = [
44
+ "package.json",
45
+ ...CONFIG_FILES,
46
+ ...FINGERPRINT_CONFIGS,
47
+ ...RN_CONFIGS,
48
+ ".fingerprintignore",
49
+ ".gitignore",
50
+ "eas.json",
51
+ ".easignore",
52
+ "expo-module.config.json"
53
+ ];
54
+ const ROOT_DIRS = ["patches", "cng-patches"];
55
+ const LOCKFILES = [
56
+ "pnpm-lock.yaml",
57
+ "yarn.lock",
58
+ "package-lock.json",
59
+ "bun.lock",
60
+ "bun.lockb"
61
+ ];
62
+ const NATIVE_MARKERS = ["expo-module.config.json", ...RN_CONFIGS];
63
+ const RISKY_IMPORTS = /* @__PURE__ */ new Set([
64
+ "fs",
65
+ "fs/promises",
66
+ "child_process",
67
+ "http",
68
+ "https",
69
+ "http2",
70
+ "net",
71
+ "dgram",
72
+ "worker_threads",
73
+ "process",
74
+ "dotenv",
75
+ "dotenv/config",
76
+ "@dotenvx/dotenvx",
77
+ "fs-extra",
78
+ "graceful-fs",
79
+ "execa",
80
+ "shelljs",
81
+ "cross-spawn",
82
+ "node-fetch",
83
+ "undici",
84
+ "axios",
85
+ "got"
86
+ ]);
87
+ const FILE_IMPORTS = /* @__PURE__ */ new Set([
88
+ "fs",
89
+ "fs/promises",
90
+ "fs-extra",
91
+ "graceful-fs"
92
+ ]);
93
+ const BUILTINS = /* @__PURE__ */ new Set([
94
+ "path",
95
+ "os",
96
+ "url",
97
+ "util",
98
+ "crypto",
99
+ "module",
100
+ "assert",
101
+ "events"
102
+ ]);
103
+ const ASSET = /['"`]([^'"`\n]+\.(?:png|jpe?g|webp|gif|svg|ttf|otf|json|plist|xml|mp3|wav|caf|lottie|p8|mobileprovision))['"`]/g;
104
+ /** An Expo module that declares its platforms is linked only on those; any other native package counts on both. */
105
+ function linkedOn(info, platform) {
106
+ if (!info.native) return false;
107
+ if (!Array.isArray(info.platforms)) return true;
108
+ const wanted = platform === "ios" ? ["ios", "apple"] : ["android"];
109
+ return info.platforms.some((each) => wanted.includes(String(each))) || (platform === "ios" ? info.native.includes("*.podspec") : info.native.includes("android/build.gradle"));
110
+ }
111
+ const field = (value, key) => value && typeof value === "object" && key in value ? Reflect.get(value, key) : void 0;
112
+ const sha256 = (value) => createHash("sha256").update(value).digest("hex");
113
+ function blobId(content) {
114
+ return createHash("sha1").update(`blob ${content.length}\0`).update(content).digest("hex");
115
+ }
116
+ function readJson(path) {
117
+ return JSON.parse(readFileSync(path, "utf8"));
118
+ }
119
+ function stringList(value) {
120
+ return value && typeof value === "object" ? Object.keys(value) : [];
121
+ }
122
+ var Uncertain = class extends Error {};
123
+ const local = (dir) => !dir.split(sep).includes("node_modules");
124
+ function resolvePackage(from, name) {
125
+ for (let dir = from;; dir = dirname(dir)) {
126
+ const candidate = join(dir, "node_modules", name);
127
+ if (existsSync(join(candidate, "package.json"))) try {
128
+ return realpathSync(candidate);
129
+ } catch {
130
+ return null;
131
+ }
132
+ if (dirname(dir) === dir) return null;
133
+ }
134
+ }
135
+ /**
136
+ * The installed packages, keyed by real path: every package reachable from the app's manifest, and every entry of a
137
+ * node_modules directory above the app, which older autolinking scans.
138
+ */
139
+ function packageGraph(root, manifest) {
140
+ const packages = /* @__PURE__ */ new Map();
141
+ const read = (dir) => {
142
+ const known = packages.get(dir);
143
+ if (known) return known;
144
+ let json;
145
+ let entries;
146
+ try {
147
+ json = readJson(join(dir, "package.json"));
148
+ entries = readdirSync(dir);
149
+ } catch {
150
+ return null;
151
+ }
152
+ const markers = NATIVE_MARKERS.filter((name) => entries.includes(name));
153
+ if (entries.some((name) => name.endsWith(".podspec"))) markers.push("*.podspec");
154
+ if (entries.includes("android") && ["build.gradle", "build.gradle.kts"].some((f) => existsSync(join(dir, "android", f)))) markers.push("android/build.gradle");
155
+ if (field(json, "codegenConfig")) markers.push("codegenConfig");
156
+ let platforms = null;
157
+ if (markers.includes("expo-module.config.json")) try {
158
+ platforms = field(readJson(join(dir, "expo-module.config.json")), "platforms") ?? null;
159
+ } catch {
160
+ platforms = "unreadable";
161
+ }
162
+ const info = {
163
+ name: String(field(json, "name") ?? ""),
164
+ version: String(field(json, "version") ?? ""),
165
+ deps: [
166
+ "dependencies",
167
+ "optionalDependencies",
168
+ "peerDependencies"
169
+ ].flatMap((key) => stringList(field(json, key))),
170
+ native: markers.length ? markers : null,
171
+ platforms
172
+ };
173
+ packages.set(dir, info);
174
+ return info;
175
+ };
176
+ const queue = [];
177
+ const visit = (from, names, required, follow = true) => {
178
+ for (const name of names) {
179
+ const real = resolvePackage(from, name);
180
+ if (!real) {
181
+ if (required) throw new Uncertain(`${name} is not installed`);
182
+ continue;
183
+ }
184
+ if (!packages.has(real) && read(real) && follow) queue.push(real);
185
+ }
186
+ };
187
+ visit(root, [...stringList(field(manifest, "dependencies")), ...stringList(field(manifest, "devDependencies"))], true);
188
+ visit(root, stringList(field(manifest, "optionalDependencies")), false);
189
+ const hoisted = [];
190
+ for (let dir = root;; dir = dirname(dir)) {
191
+ const modules = join(dir, "node_modules");
192
+ let names = [];
193
+ try {
194
+ names = readdirSync(modules).flatMap((name) => {
195
+ if (name.startsWith(".")) return [];
196
+ if (!name.startsWith("@")) return [name];
197
+ try {
198
+ return readdirSync(join(modules, name)).map((scoped) => `${name}/${scoped}`);
199
+ } catch {
200
+ return [];
201
+ }
202
+ });
203
+ } catch {}
204
+ hoisted.push([dir, names]);
205
+ if (dirname(dir) === dir) break;
206
+ }
207
+ while (queue.length) {
208
+ const dir = queue.shift();
209
+ const info = dir === void 0 ? void 0 : packages.get(dir);
210
+ if (dir !== void 0 && info) visit(dir, info.deps, false);
211
+ }
212
+ for (const [dir, names] of hoisted) visit(dir, names, false, false);
213
+ return packages;
214
+ }
215
+ function resolveRelative(from, spec) {
216
+ const base = resolve(dirname(from), spec);
217
+ for (const path of [base, ...[
218
+ ".ts",
219
+ ".js",
220
+ ".mjs",
221
+ ".cjs",
222
+ ".json",
223
+ "/index.ts",
224
+ "/index.js"
225
+ ].map((e) => base + e)]) if (statSync(path, { throwIfNoEntry: false })?.isFile()) return path;
226
+ return null;
227
+ }
228
+ const isDirectory = (path) => statSync(path, { throwIfNoEntry: false })?.isDirectory() ?? false;
229
+ const isFile = (path) => statSync(path, { throwIfNoEntry: false })?.isFile() ?? false;
230
+ /** A string a config names relative to itself: a module it loads, a file it reads at prebuild, or a directory. */
231
+ function addNamedPath(file, spec, scan, root) {
232
+ const found = resolveRelative(file, spec);
233
+ if (found && /\.[cm]?[jt]sx?$/.test(found)) scanConfigSource(found, scan, "plugin", root);
234
+ else if (found) scan.files.add(found);
235
+ else if (isDirectory(resolve(dirname(file), spec))) scan.dirs.add(resolve(dirname(file), spec));
236
+ }
237
+ /** Every `process.env` that is not followed by a literal name: the config passes the environment on as a value. */
238
+ const ENV_VALUE = /process\.env\b(?!\s*(?:\??\.\s*[A-Za-z_$]|\[\s*(['"])[^'"]+\1\s*\]))/;
239
+ /** Whether a plugin applies mods: it calls a `with...` helper it takes from the config-plugins package. */
240
+ function appliesMods(source) {
241
+ const pkg = String.raw`['"](?:@expo\/config-plugins|expo\/config-plugins)['"]`;
242
+ const named = [...source.matchAll(new RegExp(String.raw`\{([^}]*)\}\s*=\s*require\s*\(\s*${pkg}\s*\)`, "g")), ...source.matchAll(new RegExp(String.raw`import\s*\{([^}]*)\}\s*from\s*${pkg}`, "g"))].flatMap((match) => String(match[1]).split(",").flatMap((part) => {
243
+ const [imported = "", alias = imported] = part.split(/\s+as\s+|:/).map((name) => name.trim());
244
+ return /^with[A-Z]\w*$/.test(imported) && /^\w+$/.test(alias) ? [alias] : [];
245
+ }));
246
+ const spaces = [...source.matchAll(new RegExp(String.raw`(?:const|let|var)\s+(\w+)\s*=\s*require\s*\(\s*${pkg}\s*\)`, "g")), ...source.matchAll(new RegExp(String.raw`import\s+(?:\*\s+as\s+)?(\w+)\s+from\s*${pkg}`, "g"))].map((match) => String(match[1]));
247
+ return named.some((name) => new RegExp(String.raw`(?<![\w.])${name}\s*\(`).test(source.replace(new RegExp(String.raw`function\s+${name}\b`, "g"), ""))) || spaces.some((name) => new RegExp(String.raw`\b${name}\.with[A-Z]\w*\s*\(`).test(source));
248
+ }
249
+ /**
250
+ * What a config module evaluates, found by reading its source: the local modules, assets and directories it names,
251
+ * the packages it imports and the environment variables it reads. Anything the scan cannot follow throws `Uncertain`.
252
+ * A local plugin named by a path string also runs while the config is evaluated, but its file access normally sits in
253
+ * mods that run at prebuild: its file access counts only when it calls no `with...` mod helper, and its process and
254
+ * network access always counts.
255
+ */
256
+ function scanConfigSource(file, scan, mode, root) {
257
+ if (scan.files.has(file)) return;
258
+ scan.files.add(file);
259
+ if (file.endsWith(".json")) {
260
+ scanJsonConfig(file, scan, root);
261
+ return;
262
+ }
263
+ const source = readFileSync(file, "utf8");
264
+ const where = relative(root, file);
265
+ const uncertain = (reason) => {
266
+ throw new Uncertain(`${reason} in ${where}`);
267
+ };
268
+ const fileAccess = mode === "evaluated" || !appliesMods(source);
269
+ if (/\brequire\s*\(\s*[^'"\s)]/.test(source) || /\bimport\s*\(\s*[^'"\s)]/.test(source)) uncertain("a dynamic require or import");
270
+ if (ENV_VALUE.test(source) || /=\s*process\b(?!\s*\??\.)/.test(source) || /\(\s*process\s*[,)]/.test(source)) uncertain("process.env read without a literal name");
271
+ if (/\b(execSync|execFileSync|spawnSync|fetch)\s*\(/.test(source) || /(?<!\.)\b(exec|execFile|spawn)\s*\(/.test(source)) uncertain("process or network access");
272
+ if (fileAccess && /\b(readFileSync|readFile|readdirSync|readdir|existsSync|statSync|createReadStream)\s*\(/.test(source)) uncertain("file access");
273
+ if (/[`]\s*\.{1,2}\/[^`]*\$\{/.test(source) || /\$\{[^}]*\}[^`]*\.(?:png|jpe?g|webp|json|plist)`/.test(source)) uncertain("an asset path built at evaluation");
274
+ for (const match of source.matchAll(/process\.env\s*\??\.\s*([A-Za-z_$][\w$]*)/g)) scan.env.add(String(match[1]));
275
+ for (const match of source.matchAll(/process\.env\s*\[\s*['"]([^'"]+)['"]\s*\]/g)) scan.env.add(String(match[1]));
276
+ const imports = /* @__PURE__ */ new Set();
277
+ for (const pattern of [
278
+ /\bfrom\s*['"]([^'"]+)['"]/g,
279
+ /\brequire\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
280
+ /\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
281
+ /\bimport\s+['"]([^'"]+)['"]/g
282
+ ]) for (const match of source.matchAll(pattern)) imports.add(String(match[1]));
283
+ for (const spec of imports) {
284
+ const bare = spec.replace(/^node:/, "");
285
+ if (RISKY_IMPORTS.has(bare) || bare.startsWith("dotenv")) {
286
+ if (!FILE_IMPORTS.has(bare) || fileAccess) uncertain(`an import of ${spec}`);
287
+ } else if (spec.startsWith(".")) {
288
+ const found = resolveRelative(file, spec);
289
+ if (!found) throw new Uncertain(`an import of ${spec} that does not resolve in ${where}`);
290
+ scanConfigSource(found, scan, mode, root);
291
+ } else if (!spec.startsWith("node:") && !BUILTINS.has(bare)) scan.packages.add(spec.startsWith("@") ? spec.split("/").slice(0, 2).join("/") : spec.split("/")[0] ?? spec);
292
+ }
293
+ for (const match of source.matchAll(/['"](\.{1,2}\/[^'"\n]+)['"]/g)) {
294
+ const spec = String(match[1]);
295
+ if (!imports.has(spec)) addNamedPath(file, spec, scan, root);
296
+ }
297
+ for (const match of source.matchAll(/['"]((?:@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*(?:\/[\w.-]+)*)['"]/g)) {
298
+ const spec = String(match[1]);
299
+ scan.packages.add(spec.startsWith("@") ? spec.split("/").slice(0, 2).join("/") : spec.split("/")[0] ?? spec);
300
+ }
301
+ for (const match of source.matchAll(ASSET)) {
302
+ const path = resolve(dirname(file), String(match[1]));
303
+ if (isFile(path)) scan.files.add(path);
304
+ }
305
+ }
306
+ /**
307
+ * The local files a static config names: every string value that is a path to a file or, written relative, to a
308
+ * directory, such as an icon, a splash image, a google services file, a fonts directory or a local plugin, and the
309
+ * packages its plugins name.
310
+ */
311
+ function scanJsonConfig(file, scan, root) {
312
+ let json;
313
+ try {
314
+ json = readJson(file);
315
+ } catch {
316
+ throw new Uncertain(`${relative(root, file)} cannot be parsed`);
317
+ }
318
+ const visit = (value) => {
319
+ if (Array.isArray(value)) value.forEach(visit);
320
+ else if (value && typeof value === "object") Object.values(value).forEach(visit);
321
+ else if (typeof value === "string" && value && value.length < 512 && !value.includes("\n")) {
322
+ const plain = resolve(dirname(file), value);
323
+ if (value.startsWith(".")) addNamedPath(file, value, scan, root);
324
+ else if (isFile(plain)) scan.files.add(plain);
325
+ else if (/^(@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*(\/[\w.-]+)*$/.test(value)) scan.packages.add(value.startsWith("@") ? value.split("/").slice(0, 2).join("/") : value.split("/")[0] ?? value);
326
+ }
327
+ };
328
+ visit(json);
329
+ }
330
+ function lockfileDigest(root, top) {
331
+ for (let dir = root;; dir = dirname(dir)) {
332
+ const name = LOCKFILES.find((file) => existsSync(join(dir, file)));
333
+ if (name) {
334
+ const bytes = readFileSync(join(dir, name));
335
+ if (name.endsWith("b")) return [name, sha256(bytes)];
336
+ const lines = bytes.toString("utf8").split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).toSorted();
337
+ return [relative(root, join(dir, name)), sha256(lines.join("\n"))];
338
+ }
339
+ if (dir === top || dirname(dir) === dir) return null;
340
+ }
341
+ }
342
+ const toPosix = (path) => path.split(sep).join("/");
343
+ /** A variable's value, and the file it names when it names one: a config can take a file path from the environment. */
344
+ function envValueDigest(root, value) {
345
+ if (value === void 0) return null;
346
+ const named = value && value.length < 1024 ? resolve(root, value) : null;
347
+ const file = named && statSync(named, { throwIfNoEntry: false })?.isFile() ? contentId(named) : null;
348
+ return sha256(file ? `${value}\0${file}` : value);
349
+ }
350
+ function contentId(path) {
351
+ const stat = lstatSync(path, { throwIfNoEntry: false });
352
+ if (!stat) return "-";
353
+ if (stat.isSymbolicLink()) return blobId(Buffer.from(readlinkSync(path)));
354
+ return stat.isFile() ? blobId(readFileSync(path)) : "-";
355
+ }
356
+ /**
357
+ * A digest of the inputs @expo/fingerprint hashes for one platform, read without evaluating the app config or running
358
+ * autolinking: git blob ids of the tracked native inputs, content hashes of the untracked, modified and ignored ones,
359
+ * each installed package's version and native markers, the lockfile, and the config's sources and the environment
360
+ * variables they read. Equal digests mean equal fingerprints only as far as this model of the inputs reaches; it
361
+ * reports `uncertain` for whatever it cannot follow, which then takes the full fingerprint.
362
+ */
363
+ async function staticFingerprint(projectRoot, target, { env, signal }) {
364
+ try {
365
+ return {
366
+ ...await computeDigest(realpathSync.native(projectRoot), target, env, signal),
367
+ uncertain: null
368
+ };
369
+ } catch (error) {
370
+ if (error instanceof Uncertain) return {
371
+ digest: null,
372
+ uncertain: error.message
373
+ };
374
+ throw error;
375
+ }
376
+ }
377
+ async function computeDigest(root, target, env, signal) {
378
+ if (existsSync(join(root, ".pnp.cjs"))) throw new Uncertain("Yarn Plug-n-Play installs have no node_modules to read");
379
+ const normalized = await normalizeOptionsAsync(root, await fingerprintOptions(root, target));
380
+ if (typeof normalized.fileHookTransform === "function") throw new Uncertain("fingerprint.config.js sets fileHookTransform");
381
+ if (normalized.nativeModuleSourceType !== "package" || normalized.configPluginSourceType !== "package") throw new Uncertain("the fingerprint preset hashes package files");
382
+ let top;
383
+ try {
384
+ top = realpathSync.native(await gitAsync(root, ["rev-parse", "--show-toplevel"], { signal }));
385
+ } catch {
386
+ throw new Uncertain("the project is not in a git work tree");
387
+ }
388
+ let manifest;
389
+ try {
390
+ manifest = readJson(join(root, "package.json"));
391
+ } catch {
392
+ throw new Uncertain("package.json cannot be read");
393
+ }
394
+ const packages = packageGraph(root, manifest);
395
+ const scan = {
396
+ files: /* @__PURE__ */ new Set(),
397
+ dirs: /* @__PURE__ */ new Set(),
398
+ packages: /* @__PURE__ */ new Set(),
399
+ env: /* @__PURE__ */ new Set(["EXPO_NO_DOTENV"])
400
+ };
401
+ for (const name of [
402
+ ...CONFIG_FILES,
403
+ ...FINGERPRINT_CONFIGS,
404
+ ...RN_CONFIGS
405
+ ]) {
406
+ const path = join(root, name);
407
+ if (existsSync(path)) scanConfigSource(path, scan, "evaluated", root);
408
+ }
409
+ const specs = /* @__PURE__ */ new Set([...ROOT_FILES, ...ROOT_DIRS]);
410
+ const useCNG = normalized.useCNGForPlatforms ?? {};
411
+ if (!field(useCNG, target.platform)) specs.add(target.platform);
412
+ const autolinking = field(field(manifest, "expo"), "autolinking");
413
+ specs.add("modules");
414
+ for (const options of [
415
+ autolinking,
416
+ field(autolinking, "ios"),
417
+ field(autolinking, "android")
418
+ ]) {
419
+ const nativeModulesDir = field(options, "nativeModulesDir");
420
+ if (typeof nativeModulesDir === "string") specs.add(nativeModulesDir);
421
+ const searchPaths = field(options, "searchPaths");
422
+ if (Array.isArray(searchPaths)) {
423
+ for (const path of searchPaths) if (typeof path === "string") specs.add(path);
424
+ }
425
+ }
426
+ const extraContents = [];
427
+ for (const source of normalized.extraSources ?? []) if (source.type === "contents") extraContents.push([source.id, source.contents]);
428
+ else specs.add(source.filePath);
429
+ for (const [dir, info] of packages) if (local(dir) && info.native) specs.add(relative(root, dir));
430
+ for (const name of scan.packages) for (const [dir, info] of packages) if (info.name === name && local(dir)) specs.add(relative(root, dir));
431
+ const insideRepo = (spec) => {
432
+ const fromTop = relative(top, resolve(root, spec));
433
+ return !isAbsolute(fromTop) && fromTop !== ".." && !fromTop.startsWith(`..${sep}`);
434
+ };
435
+ const gitSpecs = [...specs].filter(insideRepo).map(toPosix).toSorted();
436
+ const outsideRepo = [...specs].filter((spec) => !insideRepo(spec)).toSorted();
437
+ const files = /* @__PURE__ */ new Map();
438
+ const list = async (args) => gitSpecs.length ? (await gitAsync(root, [
439
+ "--literal-pathspecs",
440
+ "ls-files",
441
+ "-z",
442
+ ...args,
443
+ "--",
444
+ ...gitSpecs
445
+ ], {
446
+ untrimmed: true,
447
+ timeoutMs: 6e4,
448
+ signal
449
+ })).split("\0").filter(Boolean) : [];
450
+ const [staged, changed, untracked, ignored] = await Promise.all([
451
+ list(["-s"]),
452
+ list(["-m"]),
453
+ list(["-o", "--exclude-standard"]),
454
+ list([
455
+ "-o",
456
+ "-i",
457
+ "--exclude-standard",
458
+ "--directory"
459
+ ])
460
+ ]);
461
+ for (const line of staged) {
462
+ const [meta, path] = line.split(" ");
463
+ const [mode, id] = (meta ?? "").split(" ");
464
+ if (mode === "160000") throw new Uncertain(`${path} is a git submodule`);
465
+ if (path && id) files.set(path, id);
466
+ }
467
+ const hashed = [...changed, ...untracked];
468
+ const isIgnored = (path, dir) => path.split("/").includes("node_modules") || isIgnoredPathWithMatchObjects(path, dir ? normalized.ignoreDirMatchObjects : normalized.ignorePathMatchObjects);
469
+ const walk = (path) => {
470
+ if (isIgnored(path, true)) return;
471
+ for (const entry of readdirSync(join(root, path), { withFileTypes: true })) {
472
+ const child = posix.join(path, entry.name);
473
+ if (entry.isDirectory()) walk(child);
474
+ else if (!isIgnored(child, false)) hashed.push(child);
475
+ if (hashed.length > UNTRACKED_FILE_CAP) throw new Uncertain("too many inputs outside git");
476
+ }
477
+ };
478
+ for (const entry of ignored) if (entry.endsWith("/")) walk(entry.slice(0, -1));
479
+ else if (!isIgnored(entry, false)) hashed.push(entry);
480
+ for (const dir of scan.dirs) walk(toPosix(relative(root, dir)));
481
+ for (const spec of outsideRepo) {
482
+ const stat = statSync(join(root, spec), { throwIfNoEntry: false });
483
+ if (stat?.isDirectory()) walk(toPosix(spec));
484
+ else if (stat) hashed.push(toPosix(spec));
485
+ }
486
+ if (hashed.length > UNTRACKED_FILE_CAP) throw new Uncertain("too many inputs outside git");
487
+ for (const name of expoEnvFiles(env)) {
488
+ hashed.push(name);
489
+ let text = "";
490
+ try {
491
+ text = readFileSync(join(root, name), "utf8");
492
+ } catch {}
493
+ for (const match of text.matchAll(/\$\{?([A-Za-z_][A-Za-z0-9_]*)/g)) scan.env.add(String(match[1]));
494
+ }
495
+ for (const file of scan.files) hashed.push(toPosix(relative(root, file)));
496
+ await Promise.all(hashed.map(async (path) => {
497
+ signal?.throwIfAborted();
498
+ const full = join(root, path);
499
+ const stat = lstatSync(full, { throwIfNoEntry: false });
500
+ files.set(path, stat?.isFile() ? blobId(await readFile(full)) : contentId(full));
501
+ }));
502
+ const packageRows = [...packages].map(([dir, info]) => [
503
+ toPosix(relative(root, dir)),
504
+ info.name,
505
+ info.version,
506
+ info.native,
507
+ info.platforms
508
+ ]).toSorted((a, b) => JSON.stringify(a) < JSON.stringify(b) ? -1 : 1);
509
+ const { ignorePathMatchObjects, ignoreDirMatchObjects: _dirs, fileHookTransform: _hook, ...options } = normalized;
510
+ const require = createRequire(import.meta.url);
511
+ const identity = {
512
+ schema: STATIC_SCHEMA,
513
+ stim: STIM_VERSION,
514
+ fingerprint: String(field(require("@expo/fingerprint/package.json"), "version")),
515
+ platform: target.platform,
516
+ root: toPosix(relative(top, root)),
517
+ options: {
518
+ ...options,
519
+ concurrentIoLimit: null,
520
+ silent: null,
521
+ debug: null,
522
+ ignorePaths: ignorePathMatchObjects.map((match) => match.pattern),
523
+ extraSources: (options.extraSources ?? []).map(({ reasons: _reasons, ...source }) => source)
524
+ }
525
+ };
526
+ const nativePackages = [...new Set([...packages.values()].filter((info) => linkedOn(info, target.platform)).map((info) => info.name))].toSorted();
527
+ return {
528
+ digest: sha256(JSON.stringify({
529
+ identity,
530
+ files: [...files].toSorted(([a], [b]) => a < b ? -1 : a > b ? 1 : 0),
531
+ extraContents,
532
+ packages: packageRows,
533
+ lockfile: lockfileDigest(root, top),
534
+ env: [...scan.env].toSorted().map((name) => [name, envValueDigest(root, env[name])])
535
+ })),
536
+ nativePackages
537
+ };
538
+ }
539
+ //#endregion
540
+ //#region src/cache/fingerprint-memo.ts
541
+ /** Records kept per platform; writing one more removes the least recently written. */
542
+ const FINGERPRINT_MEMO_CAP = 256;
543
+ /** How long the end of a run waits for a background check that is still running before it gives the check up. */
544
+ const FINGERPRINT_CHECK_GRACE_MS = 3e3;
545
+ const AUTOLINKED = /^(expo|rncore)Autolinking(Ios|Android)$/;
546
+ const errorMessage = (error) => error instanceof Error ? error.message : String(error);
547
+ const SOURCE_TYPES = /* @__PURE__ */ new Set([
548
+ "file",
549
+ "dir",
550
+ "contents",
551
+ "package"
552
+ ]);
553
+ function isSourceList(sources) {
554
+ return sources.every((source) => source !== null && typeof source === "object" && "type" in source && SOURCE_TYPES.has(String(source.type)));
555
+ }
556
+ function writeRecord(record, { onlyIfAbsent = false } = {}) {
557
+ const dir = fingerprintMemoDir(record.platform);
558
+ const file = fingerprintMemoFile(record.platform, record.digest);
559
+ withDirLock$1(fingerprintMemoLock(), () => {
560
+ if (onlyIfAbsent && existsSync(file)) return;
561
+ mkdirSync(dir, {
562
+ recursive: true,
563
+ mode: 448
564
+ });
565
+ const tmp = `${file}.tmp-${process.pid}`;
566
+ writeFileSync(tmp, JSON.stringify(record), { mode: 384 });
567
+ renameSync(tmp, file);
568
+ const records = readdirSync(dir).filter((name) => name.endsWith(".json"));
569
+ if (records.length <= FINGERPRINT_MEMO_CAP) return;
570
+ const byAge = records.map((name) => ({
571
+ name,
572
+ mtimeMs: statSync(join(dir, name), { throwIfNoEntry: false })?.mtimeMs ?? 0
573
+ })).toSorted((a, b) => a.mtimeMs - b.mtimeMs);
574
+ for (const { name } of byAge.slice(0, records.length - FINGERPRINT_MEMO_CAP)) rmSync(join(dir, name), { force: true });
575
+ });
576
+ try {
577
+ register({
578
+ dir: fingerprintMemoDir(),
579
+ name: "Fingerprint memo",
580
+ prune: "entries",
581
+ entriesDepth: 2,
582
+ note: "full native fingerprints keyed by a static digest of their inputs"
583
+ });
584
+ } catch {}
585
+ }
586
+ function dropRecord(platform, digest) {
587
+ const file = fingerprintMemoFile(platform, digest);
588
+ if (!existsSync(file)) return;
589
+ withDirLock$1(fingerprintMemoLock(), () => rmSync(file, { force: true }));
590
+ }
591
+ const runBackgroundFingerprint = (operation, projectRoot, target, signal) => readDoctorFingerprint({
592
+ ...operation,
593
+ signal
594
+ }, {
595
+ root: projectRoot,
596
+ ...target.platform ? { platform: target.platform } : {},
597
+ ...target.iosProjectPath ? { iosProjectPath: target.iosProjectPath } : {},
598
+ ...target.androidLayout ? { androidLayout: target.androidLayout } : {}
599
+ }, { lowPriority: true });
600
+ function startCheck(operation, projectRoot, target, stored, background) {
601
+ const controller = new AbortController();
602
+ const signal = AbortSignal.any([operation.signal, controller.signal]);
603
+ let done = false;
604
+ let abandoned = false;
605
+ const result = background(operation, projectRoot, target, signal).then((computed) => ({
606
+ hash: computed?.hash ?? null,
607
+ sources: computed?.sources ?? [],
608
+ failure: computed?.hash ? null : "no hash"
609
+ }), (error) => ({
610
+ hash: null,
611
+ sources: [],
612
+ failure: errorMessage(error)
613
+ })).then(({ hash, sources, failure }) => {
614
+ done = true;
615
+ const status = hash === stored.hash ? "confirmed" : hash === null ? "failed" : "stale";
616
+ if (status === "confirmed") try {
617
+ writeRecord(stored, { onlyIfAbsent: true });
618
+ } catch {}
619
+ if (!abandoned) debugLog.log("fingerprint_memo_check", {
620
+ platform: target.platform,
621
+ status,
622
+ ...failure && !controller.signal.aborted ? { failure } : {}
623
+ });
624
+ const modeled = new Set(stored.nativePackages);
625
+ const unmodeled = sources.flatMap((source) => source.type === "package" && source.reasons.some((reason) => AUTOLINKED.test(reason)) && !modeled.has(source.name) ? [source.name] : []);
626
+ return {
627
+ status,
628
+ recorded: stored.hash,
629
+ current: hash,
630
+ ...status === "stale" ? { unmodeled } : {}
631
+ };
632
+ });
633
+ return {
634
+ settle: async (graceMs = FINGERPRINT_CHECK_GRACE_MS) => {
635
+ if (done) return result;
636
+ let timer;
637
+ const late = new Promise((resolve) => {
638
+ timer = setTimeout(() => resolve(null), graceMs);
639
+ });
640
+ const settled = await Promise.race([result, late]);
641
+ clearTimeout(timer);
642
+ if (settled) return settled;
643
+ abandoned = true;
644
+ controller.abort();
645
+ debugLog.log("fingerprint_memo_check", {
646
+ platform: target.platform,
647
+ status: "abandoned"
648
+ });
649
+ return {
650
+ status: "abandoned",
651
+ recorded: stored.hash,
652
+ current: null
653
+ };
654
+ },
655
+ cancel: () => controller.abort()
656
+ };
657
+ }
658
+ /**
659
+ * The fingerprint for a pre-mutation cache lookup. With `consult`, a record of this machine's Stim home whose static
660
+ * digest matches the tree stands in for the full fingerprint, and with `record` a full fingerprint then runs in the
661
+ * background as `check`; the record is removed until that check agrees. With `record`, a full fingerprint is
662
+ * recorded under the tree's static digest, only when the digest did not change while it was computed. Any memo
663
+ * failure falls back to the full fingerprint; errors from the fingerprint itself propagate.
664
+ */
665
+ async function memoizedFingerprint(projectRoot, target, { consult, record, operation, fingerprint = fingerprintProject, background = runBackgroundFingerprint, staticDigest = staticFingerprint, now = Date.now }) {
666
+ const full = () => fingerprint(projectRoot, target);
667
+ const skip = (reason) => debugLog.log("fingerprint_memo_skip", {
668
+ platform: target.platform,
669
+ reason
670
+ });
671
+ if (!operation || !(consult || record)) return {
672
+ fingerprint: await full(),
673
+ memoHit: false,
674
+ check: null
675
+ };
676
+ const digestNow = async () => {
677
+ try {
678
+ const result = await staticDigest(projectRoot, target, {
679
+ env: operation.env,
680
+ signal: operation.signal
681
+ });
682
+ if (result.uncertain !== null) {
683
+ skip(result.uncertain);
684
+ return null;
685
+ }
686
+ return result;
687
+ } catch (error) {
688
+ operation.signal.throwIfAborted();
689
+ skip(errorMessage(error));
690
+ return null;
691
+ }
692
+ };
693
+ const before = await digestNow();
694
+ if (!before) return {
695
+ fingerprint: await full(),
696
+ memoHit: false,
697
+ check: null
698
+ };
699
+ if (consult) try {
700
+ const stored = readFingerprintMemo(target.platform, before.digest);
701
+ if (!stored) skip("no record");
702
+ else if (!isSourceList(stored.sources)) skip("the record has unknown sources");
703
+ else {
704
+ if (record) dropRecord(target.platform, before.digest);
705
+ debugLog.log("fingerprint_memo_hit", { platform: target.platform });
706
+ trace.mark("fingerprint memo hit", { platform: target.platform });
707
+ return {
708
+ fingerprint: {
709
+ hash: stored.hash,
710
+ sources: stored.sources
711
+ },
712
+ memoHit: true,
713
+ check: record ? startCheck(operation, projectRoot, target, stored, background) : null
714
+ };
715
+ }
716
+ } catch (error) {
717
+ skip(errorMessage(error));
718
+ }
719
+ const startedAt = now();
720
+ const computed = await full();
721
+ if (!record || !computed?.hash) return {
722
+ fingerprint: computed,
723
+ memoHit: false,
724
+ check: null
725
+ };
726
+ if ((await digestNow())?.digest !== before.digest) skip("an input changed while it was fingerprinted");
727
+ else try {
728
+ writeRecord({
729
+ schema: FINGERPRINT_MEMO_SCHEMA,
730
+ platform: target.platform,
731
+ digest: before.digest,
732
+ recordedAt: startedAt,
733
+ hash: computed.hash,
734
+ sources: computed.sources,
735
+ nativePackages: before.nativePackages
736
+ });
737
+ } catch (error) {
738
+ skip(errorMessage(error));
739
+ }
740
+ return {
741
+ fingerprint: computed,
742
+ memoHit: false,
743
+ check: null
744
+ };
745
+ }
746
+ /** Waits briefly for a memo hit's background check and reports a disagreement, which has already dropped the record. */
747
+ async function settleFingerprintCheck(check, platform, warn, log) {
748
+ if (!check) return;
749
+ const { status, recorded, current, unmodeled } = await check.settle();
750
+ if (status !== "stale" || !current) return;
751
+ const line = `the recorded fingerprint ${shortHash(recorded)} no longer matches the tree (${shortHash(current)}), so this run looked up the build cache with a stale key; run stim ${platform} again to use the current tree`;
752
+ warn(line);
753
+ log?.({
754
+ src: "build",
755
+ level: "warn",
756
+ event: "fingerprint_memo_stale",
757
+ msg: line,
758
+ recorded,
759
+ current,
760
+ ...unmodeled?.length ? { unmodeled } : {}
761
+ });
762
+ }
763
+ //#endregion
764
+ //#region src/cache/miss-reason.ts
765
+ const MISS_CHANGE_CAP = 20;
766
+ function recordedBuilds(state, platform) {
767
+ const builds = [];
768
+ for (const value of [state?.[LAST_BUILD_KEYS[platform]], state?.lastBuild]) {
769
+ const record = value;
770
+ if (record?.platform !== platform) continue;
771
+ const startedAt = typeof record.startedAt === "string" ? record.startedAt : "";
772
+ if (typeof record.fingerprint === "string" && typeof record.cacheKey === "string") builds.push({
773
+ fingerprint: record.fingerprint,
774
+ cacheKey: record.cacheKey,
775
+ startedAt
776
+ });
777
+ const carried = record.missReason?.baseline;
778
+ if (typeof carried?.fingerprint === "string" && typeof carried.cacheKey === "string") builds.push({
779
+ fingerprint: carried.fingerprint,
780
+ cacheKey: carried.cacheKey,
781
+ startedAt
782
+ });
783
+ }
784
+ return builds;
785
+ }
786
+ function projectIdentity(root) {
787
+ let real;
788
+ try {
789
+ real = realpathSync.native(root);
790
+ } catch {
791
+ return null;
792
+ }
793
+ for (let dir = real;; dir = dirname(dir)) {
794
+ if (existsSync(join(dir, ".git"))) {
795
+ const common = gitCommonDirOnDisk(dir);
796
+ return common ? `${common}\n${relative(dir, real)}` : null;
797
+ }
798
+ if (dirname(dir) === dir) return null;
799
+ }
800
+ }
801
+ function findMissBaseline(root, platform, { readState = readWorkspaceState, projectRoots = () => Object.keys(loadConfig()?.projects ?? {}), identity = projectIdentity, sourcesOf = (p, key) => storedSources(p, key, sharedBuildCache()) } = {}) {
802
+ for (const build of recordedBuilds(readState(root), platform)) {
803
+ const sources = sourcesOf(platform, build.cacheKey);
804
+ if (sources) return {
805
+ fingerprint: build.fingerprint,
806
+ cacheKey: build.cacheKey,
807
+ sources,
808
+ from: "workspace"
809
+ };
810
+ }
811
+ const project = identity(root);
812
+ if (!project) return null;
813
+ const others = [];
814
+ for (const other of projectRoots()) {
815
+ if (other === root || identity(other) !== project) continue;
816
+ others.push(...recordedBuilds(readState(other), platform));
817
+ }
818
+ for (const build of others.toSorted((a, b) => (Date.parse(b.startedAt) || 0) - (Date.parse(a.startedAt) || 0))) {
819
+ const sources = sourcesOf(platform, build.cacheKey);
820
+ if (sources) return {
821
+ fingerprint: build.fingerprint,
822
+ cacheKey: build.cacheKey,
823
+ sources,
824
+ from: "project"
825
+ };
826
+ }
827
+ return null;
828
+ }
829
+ const CATEGORY_RANK = {
830
+ "native-dependency": 0,
831
+ "app-config": 1,
832
+ "config-plugin": 2,
833
+ package: 3,
834
+ "native-dir": 4,
835
+ file: 5,
836
+ "app-asset": 6,
837
+ other: 7,
838
+ "package-scripts": 8,
839
+ autolinking: 9
840
+ };
841
+ function describeChange({ name, change, reasons }) {
842
+ const pkg = packageIn(name);
843
+ if (name === "expoConfig" || reasons.includes("expoConfig")) return {
844
+ category: "app-config",
845
+ label: "app config changed"
846
+ };
847
+ if (pkg && reasons.some((reason) => /^(expo|rncore)Autolinking(Ios|Android)?$/.test(reason))) return {
848
+ category: "native-dependency",
849
+ label: `native dependency ${change}: ${pkg}`
850
+ };
851
+ if (pkg && reasons.includes("expoConfigPlugins")) return {
852
+ category: "config-plugin",
853
+ label: `config plugin ${change}: ${pkg}`
854
+ };
855
+ if (/^(expoAutolinkingConfig|rncoreAutolinkingConfig)(:|$)/.test(name)) return {
856
+ category: "autolinking",
857
+ label: "autolinking config changed"
858
+ };
859
+ if (name.startsWith("package:")) return {
860
+ category: "package",
861
+ label: `${name.slice(8)} ${change}`
862
+ };
863
+ if (name === "packageJson:scripts") return {
864
+ category: "package-scripts",
865
+ label: "package.json scripts changed"
866
+ };
867
+ if (reasons.includes("expoConfigExternalFile")) return {
868
+ category: "app-asset",
869
+ label: `app asset ${change}: ${name}`
870
+ };
871
+ if (reasons.includes("bareNativeDir")) return {
872
+ category: "native-dir",
873
+ label: `${name}/ ${change}`
874
+ };
875
+ if (reasons.length || name.includes("/") || name.includes(".")) return {
876
+ category: "file",
877
+ label: `${name} ${change}`
878
+ };
879
+ return {
880
+ category: "other",
881
+ label: `${name} ${change}`
882
+ };
883
+ }
884
+ function rekeyPrefix(rekeyedBy) {
885
+ return rekeyedBy.length ? `${rekeyedBy.join(" and ")} changed native inputs; ` : "";
886
+ }
887
+ function missReasonFromChanges({ changes, baseline, rekeyedBy = [] }) {
888
+ const prefix = rekeyPrefix(rekeyedBy);
889
+ if (!baseline) return {
890
+ kind: "no-baseline",
891
+ summary: `${prefix}no earlier build of this project in the cache to compare with`,
892
+ changes: [],
893
+ changeCount: 0,
894
+ baseline: null,
895
+ rekeyedBy
896
+ };
897
+ if (!changes.length) return {
898
+ kind: "same-sources",
899
+ summary: `${prefix}native inputs match the last build; no cached app for this configuration or target`,
900
+ changes: [],
901
+ changeCount: 0,
902
+ baseline,
903
+ rekeyedBy
904
+ };
905
+ const described = changes.map((change, index) => ({
906
+ change,
907
+ index,
908
+ ...describeChange(change)
909
+ })).toSorted((a, b) => CATEGORY_RANK[a.category] - CATEGORY_RANK[b.category] || a.index - b.index);
910
+ const dependencyMoved = described.some((entry) => entry.category === "native-dependency");
911
+ const labels = [...new Set(described.filter((entry) => !(dependencyMoved && entry.category === "autolinking")).map((entry) => entry.label))];
912
+ const more = labels.length > 1 ? ` (+${labels.length - 1} more)` : "";
913
+ return {
914
+ kind: "changed",
915
+ summary: `${prefix}${labels[0]}${more}`,
916
+ changes: described.slice(0, MISS_CHANGE_CAP).map(({ change, category }) => ({
917
+ source: change.name,
918
+ change: change.change,
919
+ category
920
+ })),
921
+ changeCount: changes.length,
922
+ baseline,
923
+ rekeyedBy
924
+ };
925
+ }
926
+ function explainBuildMiss({ root, platform, current, rekeyedBy = [], baselineDeps, differ = expoFingerprint.diffFingerprints }) {
927
+ const baseline = findMissBaseline(root, platform, baselineDeps);
928
+ const changes = baseline ? diffFingerprintSources({
929
+ previous: baseline.sources,
930
+ previousHash: baseline.fingerprint,
931
+ current,
932
+ differ
933
+ }) : [];
934
+ return {
935
+ reason: missReasonFromChanges({
936
+ changes,
937
+ baseline: baseline ? {
938
+ fingerprint: baseline.fingerprint,
939
+ cacheKey: baseline.cacheKey,
940
+ from: baseline.from
941
+ } : null,
942
+ rekeyedBy
943
+ }),
944
+ changedNames: changes.map((change) => change.name),
945
+ previousHash: baseline?.fingerprint ?? null
946
+ };
947
+ }
948
+ function predictBuildMiss({ root, platform, current, prebuild }) {
949
+ const { reason } = explainBuildMiss({
950
+ root,
951
+ platform,
952
+ current
953
+ });
954
+ const baseline = reason.baseline && {
955
+ fingerprint: reason.baseline.fingerprint,
956
+ from: reason.baseline.from
957
+ };
958
+ if (!prebuild || !baseline) return {
959
+ ...reason,
960
+ baseline
961
+ };
962
+ const dir = platform === "ios" ? "ios/" : "android/";
963
+ return {
964
+ ...reason,
965
+ kind: "prebuild-pending",
966
+ summary: `${reason.summary} (before prebuild ${prebuild}s ${dir})`,
967
+ baseline
968
+ };
969
+ }
970
+ function skippedMissReason(summary) {
971
+ return {
972
+ kind: "cache-skipped",
973
+ summary,
974
+ changes: [],
975
+ changeCount: 0,
976
+ baseline: null,
977
+ rekeyedBy: []
978
+ };
979
+ }
980
+ function fingerprintErrorMissReason(message) {
981
+ return {
982
+ kind: "fingerprint-error",
983
+ summary: `fingerprinting failed: ${message}`,
984
+ changes: [],
985
+ changeCount: 0,
986
+ baseline: null,
987
+ rekeyedBy: []
988
+ };
989
+ }
990
+ //#endregion
991
+ //#region src/engine/prebuild.ts
992
+ const PREBUILD_ERROR = "STIM_PREBUILD_FAILED";
993
+ const LAST_LINES = 20;
994
+ function nativeDirName(platform) {
995
+ return platform === "android" ? "android" : "ios";
996
+ }
997
+ function nativeDir(root, platform) {
998
+ return join(root, nativeDirName(platform));
999
+ }
1000
+ function nativeDirInFingerprint(sources, platform) {
1001
+ const dir = nativeDirName(platform);
1002
+ return sources.some((source) => source.type === "dir" && source.filePath === dir && source.hash != null);
1003
+ }
1004
+ /**
1005
+ * @expo/fingerprint leaves a native directory out of the hash when git ignores it (CNG), so the cache
1006
+ * key only covers that directory when this workspace's recorded prebuild ran on the same fingerprint.
1007
+ */
1008
+ function prebuildAction({ isExpo, nativeDirExists, nativeDirFingerprinted, nativeDirTracked, generatedFrom, fingerprint }) {
1009
+ if (!isExpo) return "none";
1010
+ if (!nativeDirExists) return "generate";
1011
+ if (nativeDirFingerprinted || generatedFrom === fingerprint) return "none";
1012
+ return nativeDirTracked ? "refuse" : "regenerate";
1013
+ }
1014
+ function prebuildRecord(state) {
1015
+ const record = state?.prebuild;
1016
+ return record && typeof record === "object" ? record : {};
1017
+ }
1018
+ function unreadableGitTracksNativeDir(error) {
1019
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
1020
+ return !/not a git repository \(or any (of the parent directories|parent up to mount point)/.test(error instanceof Error ? error.message : String(error));
1021
+ }
1022
+ function gitTracksNativeDir(root, platform) {
1023
+ try {
1024
+ return git(root, [
1025
+ "ls-files",
1026
+ "--",
1027
+ nativeDirName(platform)
1028
+ ], { env: {
1029
+ LC_ALL: "C",
1030
+ LANGUAGE: "C"
1031
+ } }).trim() !== "";
1032
+ } catch (error) {
1033
+ return unreadableGitTracksNativeDir(error);
1034
+ }
1035
+ }
1036
+ function prebuildInputs(root, platform, { isExpo, fingerprint, sources }) {
1037
+ const nativeDirExists = existsSync(nativeDir(root, platform));
1038
+ const fingerprinted = nativeDirInFingerprint(sources, platform);
1039
+ const generatedFrom = prebuildRecord(readWorkspaceState$1(root))[platform];
1040
+ return {
1041
+ isExpo,
1042
+ nativeDirExists,
1043
+ nativeDirFingerprinted: fingerprinted,
1044
+ generatedFrom: typeof generatedFrom === "string" ? generatedFrom : null,
1045
+ fingerprint
1046
+ };
1047
+ }
1048
+ function planPrebuild(root, platform, options) {
1049
+ const inputs = prebuildInputs(root, platform, options);
1050
+ return prebuildAction({
1051
+ ...inputs,
1052
+ nativeDirTracked: inputs.nativeDirExists && !inputs.nativeDirFingerprinted && gitTracksNativeDir(root, platform)
1053
+ });
1054
+ }
1055
+ async function planPrebuildAsync(root, platform, options, signal) {
1056
+ signal?.throwIfAborted();
1057
+ const untracked = prebuildAction({
1058
+ ...prebuildInputs(root, platform, options),
1059
+ nativeDirTracked: false
1060
+ });
1061
+ if (untracked !== "regenerate") return untracked;
1062
+ let tracked;
1063
+ try {
1064
+ tracked = (await gitAsync(root, [
1065
+ "ls-files",
1066
+ "--",
1067
+ nativeDirName(platform)
1068
+ ], {
1069
+ env: {
1070
+ LC_ALL: "C",
1071
+ LANGUAGE: "C"
1072
+ },
1073
+ signal
1074
+ })).trim() !== "";
1075
+ } catch (error) {
1076
+ signal?.throwIfAborted();
1077
+ tracked = unreadableGitTracksNativeDir(error);
1078
+ }
1079
+ return tracked ? "refuse" : "regenerate";
1080
+ }
1081
+ function recordPrebuild(root, platform, fingerprint) {
1082
+ updateWorkspaceState(root, (state) => ({
1083
+ ...state,
1084
+ prebuild: {
1085
+ ...prebuildRecord(state),
1086
+ [platform]: fingerprint
1087
+ }
1088
+ }));
1089
+ }
1090
+ /** Records the prebuild fingerprint after waiting for the workspace state lock without blocking the event loop. */
1091
+ function recordPrebuildAsync(root, platform, fingerprint) {
1092
+ return withWorkspaceStateLockAsync(root, () => recordPrebuild(root, platform, fingerprint));
1093
+ }
1094
+ /**
1095
+ * @expo/cli 57 made `expo prebuild` delete existing native folders by default and added `--no-clean`
1096
+ * to keep them (expo/expo#47209); earlier CLIs keep them by default and reject `--no-clean`.
1097
+ */
1098
+ function keepNativeDirArgs(root) {
1099
+ return (expoSdkMajor(root) ?? 0) >= 57 ? ["--no-clean"] : [];
1100
+ }
1101
+ function staleNativeDirRemedy(platform) {
1102
+ const dir = nativeDirName(platform);
1103
+ return `Fix what the output above reports. Stim ran \`expo prebuild\` over the existing ${dir}/ without deleting it, which keeps what earlier config plugins wrote there. If ${dir}/ predates a removed config plugin or an Expo SDK upgrade, delete ${dir}/ and run again so Stim generates it fresh.`;
1104
+ }
1105
+ function staleNativeDirRefusal(platform) {
1106
+ const dir = nativeDirName(platform);
1107
+ return {
1108
+ code: PREBUILD_ERROR,
1109
+ message: `The fingerprint leaves ${dir}/ out of the cache key, and Stim cannot show that ${dir}/ was generated from the current app config. Git tracks files in it, or could not say whether it does, so Stim will not run \`expo prebuild\` over it.`,
1110
+ remedy: `Stop excluding ${dir}/ from the fingerprint (check .fingerprintignore) so the cache key covers its files, or add ${dir}/ to .gitignore and untrack it so Stim regenerates it from the app config.`
1111
+ };
1112
+ }
1113
+ function prebuildRefusal({ isExpo, platform, nativeDirExists }) {
1114
+ if (nativeDirExists || isExpo) return null;
1115
+ const dir = nativeDirName(platform);
1116
+ return {
1117
+ code: PREBUILD_ERROR,
1118
+ message: `This project has no ${dir}/ directory and is not an Expo (CNG) project, so Stim cannot generate one.`,
1119
+ remedy: `Check out or generate the native project (\`npx @react-native-community/cli init\` produced one originally), or add \`expo\` to the project so \`expo prebuild\` can create ${dir}/.`
1120
+ };
1121
+ }
1122
+ function prebuildFailure(logWriter, result) {
1123
+ logWriter?.write?.({
1124
+ src: "build",
1125
+ level: "error",
1126
+ msg: result.reason || "Expo prebuild failed.",
1127
+ event: "prebuild_failed"
1128
+ });
1129
+ return result;
1130
+ }
1131
+ async function runPrebuild(root, platform, logWriter, { spawnFn = null, now = Date.now, isExpo = null, clean = false } = {}) {
1132
+ const dirExists = existsSync(nativeDir(root, platform));
1133
+ const refusal = prebuildRefusal({
1134
+ isExpo: isExpo === null ? detectIsExpo(root) : isExpo,
1135
+ platform,
1136
+ nativeDirExists: dirExists
1137
+ });
1138
+ if (refusal) return prebuildFailure(logWriter, {
1139
+ failed: true,
1140
+ code: refusal.code,
1141
+ reason: refusal.message,
1142
+ remedy: refusal.remedy,
1143
+ error: refusal,
1144
+ lastLines: []
1145
+ });
1146
+ const bin = expoBinPath(root);
1147
+ if (!bin) {
1148
+ const binRefusal = expoBinRefusal(root, "prebuild");
1149
+ return prebuildFailure(logWriter, {
1150
+ failed: true,
1151
+ code: PREBUILD_ERROR,
1152
+ reason: binRefusal.message,
1153
+ remedy: binRefusal.remedy,
1154
+ lastLines: []
1155
+ });
1156
+ }
1157
+ const spawn = spawnFn || ((cmd, args, opts) => getExecutor().spawn(cmd, args, opts));
1158
+ const startedAt = now();
1159
+ const tail = [];
1160
+ const push = (line) => {
1161
+ const msg = stripAnsi(String(line)).trimEnd();
1162
+ if (!msg.trim()) return;
1163
+ tail.push(msg);
1164
+ if (tail.length > LAST_LINES) tail.shift();
1165
+ logWriter?.write?.({
1166
+ src: "build",
1167
+ level: "debug",
1168
+ msg,
1169
+ raw: true,
1170
+ event: "prebuild"
1171
+ });
1172
+ };
1173
+ let child;
1174
+ try {
1175
+ const invocation = remoteCliInvocation(bin, [
1176
+ "prebuild",
1177
+ "-p",
1178
+ platform,
1179
+ "--no-install",
1180
+ ...clean ? ["--clean"] : dirExists ? keepNativeDirArgs(root) : []
1181
+ ], "expo");
1182
+ child = spawnDeclared(ambientContext(), () => spawn(invocation.file, invocation.args, {
1183
+ cwd: root,
1184
+ stdio: [
1185
+ "ignore",
1186
+ "pipe",
1187
+ "pipe"
1188
+ ],
1189
+ env: {
1190
+ ...ambientContext()?.env ?? process.env,
1191
+ ...expoTelemetryEnv(ambientContext()?.env ?? process.env),
1192
+ FORCE_COLOR: "0",
1193
+ CI: "1"
1194
+ }
1195
+ }));
1196
+ } catch (err) {
1197
+ return prebuildFailure(logWriter, {
1198
+ failed: true,
1199
+ code: PREBUILD_ERROR,
1200
+ reason: `Could not run \`expo prebuild\`: ${err?.message || err}`,
1201
+ lastLines: []
1202
+ });
1203
+ }
1204
+ const outReader = createLineReader(push);
1205
+ const errReader = createLineReader(push);
1206
+ child.stdout?.setEncoding?.("utf-8");
1207
+ child.stderr?.setEncoding?.("utf-8");
1208
+ child.stdout?.on("data", (chunk) => outReader.push(chunk));
1209
+ child.stderr?.on("data", (chunk) => errReader.push(chunk));
1210
+ const result = await waitForChild(child);
1211
+ outReader.flush();
1212
+ errReader.flush();
1213
+ const durationMs = now() - startedAt;
1214
+ if (result.error) return prebuildFailure(logWriter, {
1215
+ failed: true,
1216
+ code: PREBUILD_ERROR,
1217
+ reason: `Could not run \`expo prebuild\`: ${result.error?.message || result.error}`,
1218
+ lastLines: tail.slice(),
1219
+ durationMs
1220
+ });
1221
+ if (result.code !== 0) {
1222
+ const how = result.signal ? `signal ${result.signal}` : `exit code ${result.code}`;
1223
+ return prebuildFailure(logWriter, {
1224
+ failed: true,
1225
+ code: PREBUILD_ERROR,
1226
+ reason: `\`expo prebuild -p ${platform}\` failed (${how}).`,
1227
+ ...dirExists && !clean ? { remedy: staleNativeDirRemedy(platform) } : {},
1228
+ lastLines: tail.slice(),
1229
+ durationMs
1230
+ });
1231
+ }
1232
+ if (!existsSync(nativeDir(root, platform))) return prebuildFailure(logWriter, {
1233
+ failed: true,
1234
+ code: PREBUILD_ERROR,
1235
+ reason: `\`expo prebuild -p ${platform}\` succeeded but did not create ${nativeDirName(platform)}/.`,
1236
+ lastLines: tail.slice(),
1237
+ durationMs
1238
+ });
1239
+ return {
1240
+ ok: true,
1241
+ durationMs,
1242
+ nativeDir: nativeDir(root, platform)
1243
+ };
1244
+ }
1245
+ //#endregion
1246
+ export { runPrebuild as a, fingerprintErrorMissReason as c, memoizedFingerprint as d, settleFingerprintCheck as f, recordPrebuildAsync as i, predictBuildMiss as l, planPrebuildAsync as n, staleNativeDirRefusal as o, recordPrebuild as r, explainBuildMiss as s, planPrebuild as t, skippedMissReason as u };