stim 1.19.0 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/README.md +20 -3
  2. package/dist/{activity-BSw0x84R.mjs → activity-BrGh9EAI.mjs} +3 -3
  3. package/dist/{agent-device-usage-output-Bjxu_VmX.mjs → agent-device-usage-output-DVyv_hWj.mjs} +7 -7
  4. package/dist/android-0vv-OruW.mjs +6 -0
  5. package/dist/android-Brx6EKvJ.mjs +1258 -0
  6. package/dist/{android-BdQC6zs1.mjs → android-BsYNR81W.mjs} +103 -45
  7. package/dist/{android-DE5g_5gv.mjs → android-CK3fpEyZ.mjs} +2 -2
  8. package/dist/{android-cas-C87geAg-.mjs → android-cas-DCAuXryb.mjs} +9 -5
  9. package/dist/android-cas-compiler.mjs +1 -1
  10. package/dist/api-run.mjs +159 -8
  11. package/dist/{api-DIf_s86s.mjs → api-zuX3ynYN.mjs} +5 -1
  12. package/dist/api.d.mts +98 -4
  13. package/dist/api.mjs +1 -1
  14. package/dist/{app-install-CFI4ptUL.mjs → app-install-0LgaQRT7.mjs} +9 -8
  15. package/dist/artifact-CnC0mfov.mjs +807 -0
  16. package/dist/artifact-DClXJNWx.mjs +772 -0
  17. package/dist/artifact-lifecycle-DzPpBYvK.mjs +30 -0
  18. package/dist/{browser-web-CDhqQxhv.mjs → browser-web-CzBmhmgs.mjs} +19 -19
  19. package/dist/{budget-Dz-kyXLv.mjs → budget-BONq9vXY.mjs} +3101 -3062
  20. package/dist/build-BixRI6sB.mjs +462 -0
  21. package/dist/{build-facts-B87vo-Im.d.mts → build-facts-DcLv2HSd.d.mts} +1 -32
  22. package/dist/build-plan-CP0tckNU.mjs +553 -0
  23. package/dist/{cache-manifest-DiwKSyv9.mjs → cache-manifest-qA3dT2x2.mjs} +1 -1
  24. package/dist/cache-manifest.mjs +1 -1
  25. package/dist/{manifest-CX0d1IkZ.mjs → ccache-CjHQ5F8g.mjs} +13 -19
  26. package/dist/{chrome-BTUOo8p2.mjs → chrome-Dp2lMLBe.mjs} +1 -1
  27. package/dist/{cli-C9Zvgbdc.mjs → cli-B2R8wH1F.mjs} +20 -20
  28. package/dist/cli.mjs +1 -1
  29. package/dist/{client-CuUo8Nvg.mjs → client-BLy1Pw2V.mjs} +89 -307
  30. package/dist/collector-run.d.mts +4 -0
  31. package/dist/collector-run.mjs +7 -7
  32. package/dist/{command-output-Bt8ygHE7.mjs → command-output-BjPN99ZB.mjs} +2 -2
  33. package/dist/{config-DC5jEtd1.mjs → config-D0LhTY3S.mjs} +8 -12
  34. package/dist/{created-devices-D0s26Hx5.mjs → created-devices-HU_DAb_Q.mjs} +7 -6
  35. package/dist/{dependency-state-DKaS1OM9.mjs → dependency-state-B2m8eqsS.mjs} +2 -2
  36. package/dist/{detached-entry-pB_IPfV4.mjs → detached-entry-BXq4fIl-.mjs} +1 -1
  37. package/dist/{dev-client-CDY_zAmD.mjs → dev-client-ClATaJoH.mjs} +2 -2
  38. package/dist/{device-BHw0WlHl.mjs → device-B7lsC0K1.mjs} +8 -8
  39. package/dist/device-DVCwAFlX.mjs +727 -0
  40. package/dist/{device-capacity-BoN1jQkO.mjs → device-capacity-Ca_ke1LE.mjs} +13 -14
  41. package/dist/device-host-worker.mjs +38 -18
  42. package/dist/device-ios-CRUqPxKw.mjs +413 -0
  43. package/dist/{device-lease-Cu7PtQpZ.mjs → device-lease-DHOBXGgS.mjs} +135 -22
  44. package/dist/{device-lease-run-D38SnZtt.mjs → device-lease-run-CvDeHTkG.mjs} +4 -4
  45. package/dist/{device-pool-CP4rAsHx.mjs → device-pool-L2-5GxMy.mjs} +3 -3
  46. package/dist/{device-remote-B5GiUifk.mjs → device-remote-Bmg42063.mjs} +17 -68
  47. package/dist/doctor-C2MSuMMg.mjs +515 -0
  48. package/dist/{doctor-PRJH94v6.mjs → doctor-DPAqhVCK.mjs} +333 -744
  49. package/dist/{doctor-watchman-wZhyTdp4.mjs → doctor-watchman-CB4NnVZm.mjs} +13 -12
  50. package/dist/{simslim-D9LkJxIO.mjs → eas-build-Dw_ec2Km.mjs} +6 -152
  51. package/dist/{stim-installations-DbmUjsLv.mjs → eas-session-ledger-DZlC-Fq3.mjs} +57 -4
  52. package/dist/{error-diagnostics--v5HGOU0.mjs → error-diagnostics-Du7I_GuY.mjs} +17 -12
  53. package/dist/errors-DzkMYh8Q.mjs +205 -0
  54. package/dist/{exec-i8GDPwq9.mjs → exec-B81Qodjn.mjs} +40 -10
  55. package/dist/{gc-CyHMcKcn.mjs → gc-PWp34zGb.mjs} +21 -20
  56. package/dist/{device-ios-B2ruohE-.mjs → gradle-C2OEKCCI.mjs} +21 -650
  57. package/dist/{guide-dRaQJJk9.mjs → guide-Dai9lphv.mjs} +504 -117
  58. package/dist/{hosted-android-Bs7fytj7.mjs → hosted-android-DQ-Agsd8.mjs} +5 -5
  59. package/dist/{hosted-client-C5G4h_OJ.mjs → hosted-client-Io01s5By.mjs} +9 -3
  60. package/dist/{hosted-ios-Bik035eL.mjs → hosted-ios-DIxFnYp-.mjs} +3 -3
  61. package/dist/{hosted-logs-CI9Az9a4.mjs → hosted-logs-DP-ABU_M.mjs} +8 -8
  62. package/dist/{hosted-macos-DABm3ScO.mjs → hosted-macos-Aziw9T_9.mjs} +7 -6
  63. package/dist/{hosted-native-DbV2aUVw.mjs → hosted-native-BdAQi21M.mjs} +17 -14
  64. package/dist/{idle-shutdown-DYiMBn34.mjs → idle-shutdown-DdMqoSUB.mjs} +7 -7
  65. package/dist/{ios-DYUr9LGc.mjs → ios-BZ9dg2Fr.mjs} +8 -7
  66. package/dist/{ios-BWbHFZVz.mjs → ios-CXLIUxXJ.mjs} +180 -24
  67. package/dist/ios-CZuv6gRv.mjs +1112 -0
  68. package/dist/{ios-DCrkgXEM.mjs → ios-VGvNdsv2.mjs} +1 -1
  69. package/dist/{ios-device-CrW8aMb5.mjs → ios-device-B1B14xND.mjs} +1 -1
  70. package/dist/{ios-device-CJ_ZOGky.mjs → ios-device-BreZOpM0.mjs} +3 -3
  71. package/dist/ios-project-B9drt_aY.mjs +197 -0
  72. package/dist/{ios-state-By0XuLTQ.mjs → ios-state-CjrL2qv-.mjs} +1 -1
  73. package/dist/js-swap-C8icNYIY.mjs +432 -0
  74. package/dist/launch-DxH0THLP.mjs +745 -0
  75. package/dist/{react-native-ios-CSpIxexZ.mjs → launch-YrbqaIFj.mjs} +59 -612
  76. package/dist/{launch-verify-DCveSE6d.mjs → launch-verify-BzRdbuO1.mjs} +26 -7
  77. package/dist/{logs-Bao1HOMy.mjs → logs-DHaRNfg7.mjs} +11 -11
  78. package/dist/macos-C0iljhsN.mjs +2 -0
  79. package/dist/{macos-ciyBdTY5.mjs → macos-V_3Ui7g4.mjs} +42 -50
  80. package/dist/macos-run.mjs +1 -1
  81. package/dist/maintenance-run.mjs +13 -10
  82. package/dist/{metro-DI2AjPF_.mjs → metro-D_j9sN-B.mjs} +18 -5
  83. package/dist/{metro-gateway-BxpJLIwh.mjs → metro-gateway-BBJldOdi.mjs} +2 -2
  84. package/dist/miss-reason-BPAzZS0r.mjs +238 -0
  85. package/dist/{named-ports-B4iWH4E-.mjs → named-ports-C2lvoPsb.mjs} +3 -3
  86. package/dist/native-android-lHzaew7r.mjs +226 -0
  87. package/dist/native-gradle-inputs-BQnIdAlL.mjs +356 -0
  88. package/dist/{native-runtime-BGxsbfny.mjs → native-runtime-BRmWL7ru.mjs} +9 -8
  89. package/dist/native-xcode-inputs-eutS19jU.mjs +307 -0
  90. package/dist/native-xcode-ios-DP3cHLwu.mjs +434 -0
  91. package/dist/{ndjson-LGZAMsqa.mjs → ndjson-GOnbyvso.mjs} +6 -6
  92. package/dist/offload-worker.d.mts +41 -7
  93. package/dist/offload-worker.mjs +409 -74
  94. package/dist/{ownership-EB0ySdGK.mjs → ownership-DXzMMSx6.mjs} +2 -2
  95. package/dist/{placement-log-Dxe34rJm.mjs → placement-log-reFbjqEr.mjs} +1 -1
  96. package/dist/plan-BDk2AeY_.mjs +260 -0
  97. package/dist/plan-placement-D_YYlE6N.mjs +73 -0
  98. package/dist/{ports-ByPc0_97.mjs → ports-jzvAJXXL.mjs} +3 -3
  99. package/dist/{prebuild-BKJ3wJI1.mjs → prebuild-Bw44QxVh.mjs} +4 -4
  100. package/dist/{preview-BbSottmO.mjs → preview-BaBJ72Al.mjs} +50 -32
  101. package/dist/project-B8_duAzW.mjs +1067 -0
  102. package/dist/project-plan-BPwbLwQu.mjs +10 -0
  103. package/dist/{pull-request-IHNdMnG8.mjs → pull-request-vOhLNmIb.mjs} +1 -1
  104. package/dist/pull-requests.mjs +1 -1
  105. package/dist/react-native-android-chm1DMlA.mjs +921 -0
  106. package/dist/react-native-build-oAw6gQeW.mjs +239 -0
  107. package/dist/react-native-doctor-DYXoh-6I.mjs +78 -0
  108. package/dist/react-native-ios-SCTK_CT5.mjs +595 -0
  109. package/dist/{recordings-Bv8McZN4.mjs → recordings-Bk-VivUV.mjs} +1 -1
  110. package/dist/{reload-rAPdqeRR.mjs → reload-D34GkY78.mjs} +17 -13
  111. package/dist/remote-BU9UyhCJ.mjs +192 -0
  112. package/dist/{remote-cache-N3hAEaAL.mjs → remote-cache-Bsts6f6Y.mjs} +5 -5
  113. package/dist/result-BDoRk1k3.mjs +186 -0
  114. package/dist/{run-lAkle1-c.mjs → run-qvff78hv.mjs} +3 -3
  115. package/dist/{server-bare-CkIjnegq.mjs → server-bare-dWjAarki.mjs} +5 -5
  116. package/dist/server-command-7XIKlWtO.mjs +69 -0
  117. package/dist/{server-expo-n3tUi3Em.mjs → server-expo-CsYu2aKL.mjs} +107 -82
  118. package/dist/server-expo-DV43vep4.mjs +2 -0
  119. package/dist/{settings-CxgngUZk.mjs → settings-Bbztm4oV.mjs} +179 -129
  120. package/dist/{settings-2am7TSxp.mjs → settings-CT3E71-i.mjs} +6 -6
  121. package/dist/{state-lKh8shBp.d.mts → settings-vk3mJGSm.d.mts} +4 -2
  122. package/dist/settings.schema.json +84 -0
  123. package/dist/simslim-ChMfe8rg.mjs +149 -0
  124. package/dist/{slot-launch-HV_KtPIw.mjs → slot-launch-B9hn96wd.mjs} +6 -6
  125. package/dist/{stage-BgaZJVZY.mjs → stage-BNKKdDNc.mjs} +6 -3
  126. package/dist/start-CbQCSnXQ.mjs +2 -0
  127. package/dist/{start-DtxthMfI.mjs → start-CdvcTaXr.mjs} +97 -68
  128. package/dist/{state-Tp4uImDS.mjs → state-Boo0IMJq.mjs} +1 -1
  129. package/dist/{state-D2KtnALG.mjs → state-DI84A-uu.mjs} +29 -10
  130. package/dist/{stats-8EJ5ivFT.mjs → stats-BIIDrjA8.mjs} +5 -5
  131. package/dist/{status-BEeL6IKz.mjs → status-BDGHwkCs.mjs} +3 -1
  132. package/dist/{status-BOyp0CUl.mjs → status-DARMkc9q.mjs} +81 -293
  133. package/dist/status-watch-DvmI3SmN.mjs +296 -0
  134. package/dist/{stim-desktop-C65jk_q2.mjs → stim-desktop-8GQX2OV8.mjs} +1 -1
  135. package/dist/{stop-DRipAg-e.mjs → stop-CCZS-C9m.mjs} +2 -2
  136. package/dist/{stop-D0UIpZuh.mjs → stop-WQPQHLuy.mjs} +5 -5
  137. package/dist/{stop-zDlknFoz.mjs → stop-iNemHsJC.mjs} +21 -20
  138. package/dist/supervisor-run.d.mts +7 -2
  139. package/dist/supervisor-run.mjs +32 -20
  140. package/dist/{support-DsNaFg6D.mjs → support-CnE_SIjt.mjs} +17 -7
  141. package/dist/{support-CNqYkV0y.mjs → support-DCco5fut.mjs} +61 -8
  142. package/dist/{swiftpm-macos-aJzPAj1t.mjs → swiftpm-macos-afeNUExW.mjs} +5 -5
  143. package/dist/tailnet-CoJUb2tB.mjs +265 -0
  144. package/dist/toolchain-BIupIro1.mjs +2 -0
  145. package/dist/{toolchain-C3xSoyUl.mjs → toolchain-DqyDXs30.mjs} +206 -53
  146. package/dist/{trigger-CAhmG-4s.mjs → trigger-C0MWBi8O.mjs} +2 -2
  147. package/dist/trigger-SHLOWJ4O.mjs +2 -0
  148. package/dist/{warm-progress-pFLB2e6P.mjs → warm-progress-C3ASD6z5.mjs} +6 -6
  149. package/dist/{watchman-BL1IdOtq.mjs → watchman-CQl51YZu.mjs} +6 -6
  150. package/dist/{web-DbbrPUn0.mjs → web-B3eldHFH.mjs} +9 -13
  151. package/dist/web-BLXpOe6b.mjs +2 -0
  152. package/dist/web-run.mjs +10 -10
  153. package/dist/{workspace-state-CR9X86xJ.mjs → workspace-state-CSIFuuDa.mjs} +33 -33
  154. package/dist/{workspaces-Djb2gfi9.mjs → workspaces-D2xSHBDY.mjs} +1260 -1258
  155. package/dist/{worktree-BL4M1rhB.mjs → worktree-BxDqaTev.mjs} +84 -48
  156. package/dist/worktree-q7_OZNY0.mjs +2 -0
  157. package/package.json +8 -6
  158. package/shim/native-android.gradle +101 -0
  159. package/dist/android-BXc6BmEi.mjs +0 -3732
  160. package/dist/android-BvV1gGdN.mjs +0 -4
  161. package/dist/ios-ITYno9rO.mjs +0 -2241
  162. package/dist/macos-nIOATJmR.mjs +0 -2
  163. package/dist/metro-store-BAHFcJm6.mjs +0 -106
  164. package/dist/miss-reason-CjQCOKqq.mjs +0 -1743
  165. package/dist/plan-placement-BVY59qa8.mjs +0 -290
  166. package/dist/project-B5eQ1USs.mjs +0 -612
  167. package/dist/react-native-ios-BMh2i25I.mjs +0 -2
  168. package/dist/server-expo-DGsdUGX2.mjs +0 -2
  169. package/dist/start-D8NbxNBb.mjs +0 -2
  170. package/dist/trigger-CoalsMTV.mjs +0 -2
  171. package/dist/web-DFpFeZu5.mjs +0 -2
  172. package/dist/worktree-DV-A0LNL.mjs +0 -2
@@ -1,246 +1,143 @@
1
- import { t as getExecutor } from "./exec-i8GDPwq9.mjs";
2
- import { d as quotedPath, f as refuseNoProject, l as phaseLine, u as plural } from "./command-output-Bt8ygHE7.mjs";
3
- import { _ as loadConfig$1, p as getProject, s as configCorruptRepair, u as getConcurrencyLimits } from "./config-DC5jEtd1.mjs";
4
- import { _ as workspaceDerivedData, d as sharedBuildCache } from "./workspace-state-CR9X86xJ.mjs";
5
- import { f as appProjectProblem, h as isPackageResolvable, p as detectIsExpo, r as findProjectRoot } from "./project-B5eQ1USs.mjs";
6
- import { B as settingShapeErrors, Et as compilerCacheFallbackMessage, G as webSettings, H as settingsLayers, I as resolveSettings, M as remoteAndroidSetting, Ot as resolveOptimizations, P as remoteIosSetting, R as settingOrigin, St as makeTemporaryDirectory, W as unknownSettingKeys, et as dirtyFingerprintFiles, gt as resolveSourceCheckout, i as SETTING_SHAPE_REMEDY, k as projectOptimizations, lt as listWorktrees, mt as repoRoot, n as PATH_SETTINGS, nt as gitCommonDir, r as SETTINGS_WITH_RESOLVE_TIME_FALLBACK, ut as locallyKnownUpstream, v as iosSimSlimProfileSetting, wt as temporaryRoot, xt as filesystemDevice, y as mergeSettingsLayers } from "./settings-CxgngUZk.mjs";
7
- import { St as inspectWatchmanMemory, a as inspectBudget, n as budgetLine } from "./budget-Dz-kyXLv.mjs";
8
- import { N as listBuildSlots } from "./workspaces-Djb2gfi9.mjs";
9
- import { C as diffFingerprintSources, D as fingerprintProject, h as listBuildLocks } from "./device-lease-Cu7PtQpZ.mjs";
10
- import { D as hostSystemImageArch, ft as parkedMaxSetting, o as androidHome, ot as POOL_SETTING_REMEDY } from "./android-BdQC6zs1.mjs";
11
- import { M as readHostMemoryPressure, b as pickDefaultIosCreation, j as hostMemoryPressureAdvice, l as iosRuntimeMatches, m as listIosRuntimes } from "./ios-BWbHFZVz.mjs";
12
- import { L as realIo, P as inspectDeviceHostMachines, j as inspectBuildMachines, o as offloadCheck, u as simulatorRuntime } from "./client-CuUo8Nvg.mjs";
13
- import { i as countLiveOwnedDevices } from "./device-capacity-BoN1jQkO.mjs";
14
- import { a as projectCmakeLauncher } from "./manifest-CX0d1IkZ.mjs";
15
- import { A as bundlerPin, i as macosToolchain, j as podInstallCommand, n as androidToolchain, r as iosToolchain, t as androidRequirements, x as detectXcodeMajor } from "./toolchain-C3xSoyUl.mjs";
16
- import { f as resolveEasCliBin, i as checkEasAuth$1, l as ownerFromConfig, u as providerFromConfig } from "./remote-cache-N3hAEaAL.mjs";
17
- import { _ as androidPathRoomMessage, a as apkOutputsDir, c as ccacheEnabled, g as androidPathRoom, p as androidSdkRefusal, v as androidPathRoomRemedy } from "./device-ios-B2ruohE-.mjs";
18
- import { _ as MIN_EAS_CLI_SIMULATOR_VERSION, s as readInstalledEasCliVersion, v as easCliSupport, y as easCliUpgradeRemedy } from "./device-remote-B5GiUifk.mjs";
19
- import { n as inspectStimVersions, t as compareStimVersions } from "./stim-installations-DbmUjsLv.mjs";
20
- import { n as simslimIsOnPath, r as MIN_EAS_CLI_BUILD_DOWNLOAD_VERSION } from "./simslim-D9LkJxIO.mjs";
21
- import { r as resolveAndroidCompilerCache, t as readAndroidCasToolchain } from "./android-cas-C87geAg-.mjs";
22
- import { g as resolveRuntime, m as resolveDeviceType } from "./support-CNqYkV0y.mjs";
23
- import { n as hasInstalledDependencies, r as installedNpmTreeIsValid, t as dependencyState } from "./dependency-state-DKaS1OM9.mjs";
24
- import { a as findChrome, t as CHROME_INSTALL_REMEDY } from "./chrome-BTUOo8p2.mjs";
25
- import { r as maintenanceStatus, t as maintenanceLine } from "./status-BEeL6IKz.mjs";
26
- import { a as writeIgnoreDirs, i as watchedCheckouts, n as inspectNestedWorktrees, r as nestedWorktreeFinding, s as recordDoctorRun } from "./doctor-watchman-wZhyTdp4.mjs";
1
+ import { t as getExecutor } from "./exec-B81Qodjn.mjs";
2
+ import { _ as loadConfig$1, s as configCorruptRepair, u as getConcurrencyLimits } from "./config-D0LhTY3S.mjs";
3
+ import { B as remoteAndroidSetting, D as mergeSettingsLayers, Et as repoRoot, G as resolveIosProjectDir, H as remoteIosSetting, I as projectSettingsContext, It as temporaryRoot, Nt as filesystemDevice, Ot as resolveSourceCheckout, Pt as makeTemporaryDirectory, Q as settingShapeErrors, St as locallyKnownUpstream, T as iosSimSlimProfileSetting, U as resolveAndroidLayout, X as settingOrigin, a as SETTING_SHAPE_REMEDY, et as settingsLayers, ft as dirtyFingerprintFiles, i as SETTINGS_WITH_RESOLVE_TIME_FALLBACK, m as defaultAndroidLayout, mt as gitCommonDir, nt as unknownSettingKeys, q as resolveSettings, r as PATH_SETTINGS, rt as webSettings, xt as listWorktrees } from "./settings-Bbztm4oV.mjs";
4
+ import { C as diffFingerprintSources, D as fingerprintProject, h as listBuildLocks } from "./device-lease-DHOBXGgS.mjs";
5
+ import { b as workspaceDerivedData, m as sharedBuildCache } from "./workspace-state-CSIFuuDa.mjs";
6
+ import { d as quotedPath, u as plural } from "./command-output-BjPN99ZB.mjs";
7
+ import { S as resolvePackageJson, _ as detectIsExpo, g as appProjectProblem, y as isPackageResolvable } from "./project-B8_duAzW.mjs";
8
+ import { D as hostSystemImageArch, at as POOL_SETTING_REMEDY, dt as parkedMaxSetting, o as androidHome } from "./android-BsYNR81W.mjs";
9
+ import { I as listBuildSlots } from "./workspaces-D2xSHBDY.mjs";
10
+ import { l as realIo } from "./tailnet-CoJUb2tB.mjs";
11
+ import { p as resolveOptimizations, u as compilerCacheFallbackMessage } from "./errors-DzkMYh8Q.mjs";
12
+ import { f as resolveEasCliBin, i as checkEasAuth$1, l as ownerFromConfig, u as providerFromConfig } from "./remote-cache-Bsts6f6Y.mjs";
13
+ import { M as hostMemoryPressureAdvice, P as readHostMemoryPressure } from "./ios-CXLIUxXJ.mjs";
14
+ import { i as projectCmakeLauncher } from "./ccache-CjHQ5F8g.mjs";
15
+ import { r as androidSdkRefusal } from "./gradle-C2OEKCCI.mjs";
16
+ import { L as podInstallCommand } from "./toolchain-DqyDXs30.mjs";
17
+ import { i as countLiveOwnedDevices } from "./device-capacity-Ca_ke1LE.mjs";
18
+ import { d as androidPathRoomRemedy, i as ccacheEnabled, l as androidPathRoom, t as apkOutputsDir, u as androidPathRoomMessage } from "./react-native-build-oAw6gQeW.mjs";
19
+ import { r as resolveAndroidCompilerCache, t as readAndroidCasToolchain } from "./android-cas-DCAuXryb.mjs";
20
+ import { g as easCliUpgradeRemedy, h as easCliSupport, m as MIN_EAS_CLI_SIMULATOR_VERSION, s as readInstalledEasCliVersion } from "./device-remote-Bmg42063.mjs";
21
+ import { t as MIN_EAS_CLI_BUILD_DOWNLOAD_VERSION } from "./eas-build-Dw_ec2Km.mjs";
22
+ import { n as simslimIsOnPath } from "./simslim-ChMfe8rg.mjs";
23
+ import { n as hasInstalledDependencies, r as installedNpmTreeIsValid, t as dependencyState } from "./dependency-state-B2m8eqsS.mjs";
24
+ import { a as findChrome, t as CHROME_INSTALL_REMEDY } from "./chrome-Dp2lMLBe.mjs";
27
25
  import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync, rmSync } from "node:fs";
28
26
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
29
- import { dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1, relative as relative$1, resolve as resolve$1, sep as sep$1 } from "path";
30
- import { isJsonObject, maintenanceNdjsonFile, readHostedAppMetadata, readHostedSessions, readJsonObject, readStats, settingDefinition } from "@stim-cli/core/state";
31
- import chalk from "chalk";
32
- import { existsSync as existsSync$1, lstatSync as lstatSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, realpathSync as realpathSync$1, renameSync as renameSync$1, rmSync as rmSync$1, writeFileSync as writeFileSync$1 } from "fs";
33
- import { InvalidArgumentError } from "commander";
34
- import { homedir } from "os";
27
+ import { basename as basename$1, dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1, relative as relative$1, resolve as resolve$1, sep as sep$1 } from "path";
28
+ import { isJsonObject, readJsonObject, readStats, settingDefinition } from "@stim-cli/core/state";
29
+ import { existsSync as existsSync$1, lstatSync as lstatSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, realpathSync as realpathSync$1, rmSync as rmSync$1 } from "fs";
35
30
  import * as expoFingerprint from "@expo/fingerprint";
36
- //#region src/diagnostics/sandbox.ts
37
- /**
38
- * What a sandboxed harness has to permit for Stim to work: its state
39
- * directory, the XPC service simctl talks to, and the port adb's server binds.
40
- */
41
- function sandboxAllowance(stimHome = "~/.stim") {
42
- return {
43
- allowWrite: [stimHome],
44
- allowMachLookup: ["com.apple.coresimulator.*"],
45
- allowLocalBinding: true
46
- };
47
- }
48
- function detectHarness(env = process.env) {
49
- if (env.CLAUDECODE || env.CLAUDE_CODE_ENTRYPOINT) return "claude-code";
50
- if (Object.keys(env).some((k) => k.startsWith("CODEX_"))) return "codex";
51
- return null;
52
- }
53
- function expandHome(value, home) {
54
- const v = value.trim();
55
- if (v === "~" || v === "$HOME") return home;
56
- if (v.startsWith("~/")) return join$1(home, v.slice(2));
57
- if (v.startsWith("$HOME/")) return join$1(home, v.slice(6));
58
- return v;
59
- }
60
- /**
61
- * Whether the sandbox actually stops Stim, rather than whether a harness that
62
- * can sandbox is present: a session with its sandbox off exports the same
63
- * variables as one with it on, so only a write says which this is.
64
- */
65
- function sandboxBlocksStimHome(stimHome, home = homedir()) {
66
- const dir = expandHome(stimHome, home);
67
- const probe = join$1(dir, `.probe-${process.pid}-${Math.random().toString(36).slice(2)}`);
31
+ //#region src/diagnostics/doctor-config.ts
32
+ function readMachineSettings(context) {
68
33
  try {
69
- mkdirSync$1(dir, { recursive: true });
70
- writeFileSync$1(probe, "");
71
- rmSync$1(probe, { force: true });
72
- return false;
73
- } catch (err) {
74
- const code = err.code;
75
- return code === "EPERM" || code === "EACCES";
34
+ const layers = settingsLayers(context);
35
+ return {
36
+ settings: mergeSettingsLayers(layers.map((layer) => layer.settings)),
37
+ layers,
38
+ corrupt: null
39
+ };
40
+ } catch (error) {
41
+ if (error.code !== "STIM_CONFIG_CORRUPT") throw error;
42
+ return {
43
+ settings: {},
44
+ layers: [],
45
+ corrupt: {
46
+ level: "note",
47
+ title: "The Stim config is not a valid JSON object",
48
+ detail: `${String(error.message).split("\n")[0]}. Every setting and device record lives in that file, so no other machine-level check can run until it parses.`,
49
+ fix: configCorruptRepair()
50
+ }
51
+ };
76
52
  }
77
53
  }
78
- /** Where a per-user, gitignored Claude Code settings file belongs for this project. */
79
- function claudeLocalSettingsPath(root) {
80
- return join$1(root, ".claude", "settings.local.json");
81
- }
82
- /** Every settings file that may already carry the allowance, most local first. */
83
- function allowanceSearchPaths(settingsRoot, home = homedir()) {
84
- return [
85
- claudeLocalSettingsPath(settingsRoot),
86
- join$1(settingsRoot, ".claude", "settings.json"),
87
- join$1(home, ".claude", "settings.json")
88
- ];
89
- }
90
- function isPlainObject(value) {
91
- return !!value && typeof value === "object" && !Array.isArray(value);
92
- }
93
- /**
94
- * Absent, unreadable and present-but-not-an-object are three different states.
95
- * Collapsing them loses the one distinction that matters before a write: a
96
- * file whose contents did not parse still holds settings we must not destroy.
97
- */
98
- function readSettings(path) {
99
- let raw;
100
- try {
101
- raw = readFileSync$1(path, "utf-8");
102
- } catch (err) {
103
- return err.code === "ENOENT" ? { kind: "absent" } : { kind: "invalid" };
104
- }
105
- try {
106
- const parsed = JSON.parse(raw);
107
- return isPlainObject(parsed) ? {
108
- kind: "object",
109
- value: parsed
110
- } : { kind: "invalid" };
111
- } catch {
112
- return { kind: "invalid" };
54
+ const PATH_SETTINGS_TRIMMED_BY_THEIR_CONSUMER = /* @__PURE__ */ new Set(["android.keystore"]);
55
+ function checkMachineSettings({ settings, layers, projectRoot, optimizations = null, reportedElsewhere = [], exists = existsSync, env = process.env }) {
56
+ const findings = [];
57
+ const fallback = optimizations?.android.compilerCacheFallback ?? null;
58
+ const fallbackEntry = fallback ? settingOrigin(layers, fallback.key) : null;
59
+ const attributed = fallback && (fallback.fromEnvironment || fallbackEntry) ? fallback : null;
60
+ const reported = attributed?.manifest && !exists(attributed.manifest) ? null : attributed;
61
+ const skip = new Set(reportedElsewhere);
62
+ if (reported) skip.add(reported.key);
63
+ if (!optimizations) for (const key of SETTINGS_WITH_RESOLVE_TIME_FALLBACK) skip.add(key);
64
+ for (const key of PATH_SETTINGS) {
65
+ const variable = settingDefinition(key)?.env;
66
+ if (skip.has(key) || variable && skip.has(variable)) continue;
67
+ const override = variable ? env[variable] : void 0;
68
+ if (variable && override) {
69
+ const named = missingPath(override, projectRoot, exists);
70
+ if (named) findings.push({
71
+ level: "note",
72
+ title: "An environment variable points at a path that is not there",
73
+ detail: `${variable} in the environment names ${named}, which does not exist.`,
74
+ fix: `Point ${variable} at the path it should name, or unset it.`
75
+ });
76
+ continue;
77
+ }
78
+ const entry = settingOrigin(layers, key);
79
+ if (!entry || typeof entry.value !== "string") continue;
80
+ const named = missingPath(entry.value, projectRoot, exists, PATH_SETTINGS_TRIMMED_BY_THEIR_CONSUMER.has(key));
81
+ if (!named) continue;
82
+ findings.push({
83
+ level: "note",
84
+ title: "A setting points at a path that is not there",
85
+ detail: `${key} in ${entry.file} names ${named}, which does not exist.`,
86
+ fix: `Point ${key} at the path it should name, or remove it from ${entry.file}.`
87
+ });
113
88
  }
114
- }
115
- function nested(source, ...keys) {
116
- let cursor = source;
117
- for (const key of keys) {
118
- if (!isPlainObject(cursor)) return void 0;
119
- cursor = cursor[key];
89
+ if (reported && optimizations) findings.push(compilerCacheFinding({
90
+ fallback: reported,
91
+ optimizations,
92
+ file: fallbackEntry?.file ?? null
93
+ }));
94
+ for (const key of unknownSettingKeys(settings)) {
95
+ const file = settingOrigin(layers, key)?.file ?? null;
96
+ findings.push({
97
+ level: "note",
98
+ title: "A key in the config is inert",
99
+ detail: `${key}${file ? ` in ${file}` : ""} is not read by Stim, so its value changes nothing.`,
100
+ fix: `Remove ${key}${file ? ` from ${file}` : ""}, or run \`stim guide settings\` for the name that replaced it.`
101
+ });
120
102
  }
121
- return cursor;
122
- }
123
- /** Whether an allowWrite entry covers the Stim home, including as a parent. */
124
- function covers(entry, target, home) {
125
- const posix = (value) => {
126
- const expanded = expandHome(value, home);
127
- return sep$1 === "/" ? expanded : expanded.replaceAll(sep$1, "/");
128
- };
129
- const e = posix(entry).replace(/\/+\*+$/, "").replace(/\/+$/, "");
130
- const t = posix(target).replace(/\/+$/, "");
131
- return e === t || t.startsWith(`${e}/`);
103
+ return findings;
132
104
  }
133
- /**
134
- * Which parts of the allowance the settings files already carry. Any file may
135
- * supply any part, so a project that solved this in one place is not asked to
136
- * repeat it in another.
137
- */
138
- function missingAllowance(paths, stimHome = "~/.stim", home = homedir()) {
139
- const files = paths.map((p) => {
140
- const read = readSettings(p);
141
- return read.kind === "object" ? read.value : null;
142
- });
143
- const list = (...keys) => files.flatMap((f) => {
144
- const value = nested(f, ...keys);
145
- return Array.isArray(value) ? value.map(String) : [];
105
+ function compilerCacheFinding({ fallback, optimizations, file }) {
106
+ const detail = compilerCacheFallbackMessage({
107
+ fallback,
108
+ compilerCache: optimizations.android.compilerCache === "none" ? "none" : "ccache",
109
+ file
146
110
  });
147
- const missing = [];
148
- if (!list("sandbox", "filesystem", "allowWrite").some((w) => covers(w, stimHome, home))) missing.push("sandbox.filesystem.allowWrite");
149
- if (!list("sandbox", "network", "allowMachLookup").some((l) => l.toLowerCase().startsWith("com.apple.coresimulator"))) missing.push("sandbox.network.allowMachLookup");
150
- if (!files.some((f) => nested(f, "sandbox", "network", "allowLocalBinding") === true)) missing.push("sandbox.network.allowLocalBinding");
151
- return missing;
152
- }
153
- /**
154
- * The key that stops a merge, or null when the settings can take the
155
- * allowance. A value of an unexpected shape is someone else's, and rewriting
156
- * it would lose whatever it means.
157
- */
158
- function unmergeableKey(settings) {
159
- if (!("sandbox" in settings)) return null;
160
- if (!isPlainObject(settings.sandbox)) return "sandbox";
161
- const sandbox = settings.sandbox;
162
- if ("filesystem" in sandbox && !isPlainObject(sandbox.filesystem)) return "sandbox.filesystem";
163
- if ("network" in sandbox && !isPlainObject(sandbox.network)) return "sandbox.network";
164
- return null;
165
- }
166
- /** Merge the allowance into a settings object, leaving everything else as it is. */
167
- function withAllowance(settings, allowance) {
168
- const next = { ...settings };
169
- const sandbox = isPlainObject(next.sandbox) ? { ...next.sandbox } : {};
170
- const filesystem = isPlainObject(sandbox.filesystem) ? { ...sandbox.filesystem } : {};
171
- const network = isPlainObject(sandbox.network) ? { ...sandbox.network } : {};
172
- const writes = Array.isArray(filesystem.allowWrite) ? filesystem.allowWrite.map(String) : [];
173
- const lookups = Array.isArray(network.allowMachLookup) ? network.allowMachLookup.map(String) : [];
174
- filesystem.allowWrite = [.../* @__PURE__ */ new Set([...writes, ...allowance.allowWrite])];
175
- network.allowMachLookup = [.../* @__PURE__ */ new Set([...lookups, ...allowance.allowMachLookup])];
176
- network.allowLocalBinding = true;
177
- sandbox.filesystem = filesystem;
178
- sandbox.network = network;
179
- next.sandbox = sandbox;
180
- return next;
181
- }
182
- /**
183
- * Write the allowance into a Claude Code local settings file, creating it if
184
- * needed. Refuses whenever the current contents cannot be read back, because
185
- * the merge would replace settings rather than extend them.
186
- */
187
- function applyClaudeAllowance(path, allowance) {
188
- const read = readSettings(path);
189
- if (read.kind === "invalid") return {
190
- status: "refused",
191
- reason: `${path} is not a plain JSON object, so Stim cannot merge into it without losing what is there. Comments make a settings file unparseable here even though Claude Code accepts them.`
192
- };
193
- const existing = read.kind === "object" ? read.value : {};
194
- const blocker = unmergeableKey(existing);
195
- if (blocker) return {
196
- status: "refused",
197
- reason: `${path} holds a "${blocker}" that is not an object, so Stim cannot merge into it without discarding that value.`
111
+ if (fallback.key === "optimizations.android.compilerCache") return {
112
+ level: "note",
113
+ title: "A setting needs a companion this config does not supply",
114
+ detail,
115
+ fix: "Set optimizations.android.casToolchain to the toolchain JSON manifest, or set optimizations.android.compilerCache to ccache."
198
116
  };
199
- mkdirSync$1(dirname$1(path), { recursive: true });
200
- const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
201
- writeFileSync$1(tmp, `${JSON.stringify(withAllowance(existing, allowance), null, 2)}\n`);
202
- try {
203
- renameSync$1(tmp, path);
204
- } catch (err) {
205
- rmSync$1(tmp, { force: true });
206
- throw err;
207
- }
208
- return { status: read.kind === "absent" ? "created" : "updated" };
209
- }
210
- /**
211
- * The finding a sandboxed session should see: what fails, and the one command
212
- * that fixes it where the harness has a per-path allowance.
213
- */
214
- function sandboxFinding(settingsRoot, { env = process.env, home = homedir(), stimHome = process.env.STIM_HOME || "~/.stim", blocked } = {}) {
215
- const harness = detectHarness(env);
216
- if (!harness) return null;
217
- if (!(blocked ?? sandboxBlocksStimHome(stimHome, home))) return null;
218
- const symptoms = `Writes to ${stimHome} raise EPERM on a directory you can write, the simulator service looks dead, and the adb server looks unreachable.`;
219
- const title = "This session sandboxes shell commands, and Stim is not allowed through";
220
- if (harness === "codex") return {
221
- level: "cost",
117
+ const title = fallback.fromEnvironment ? "An environment variable holds a value Stim cannot use" : "A setting holds a value Stim cannot use";
118
+ if (fallback.manifest) return {
119
+ level: "note",
222
120
  title,
223
- detail: `${symptoms} Codex has one sandbox setting and no per-path allowance, so there is nothing to add.`,
224
- fix: "Run Stim with the sandbox off, or start Codex with `codex -s danger-full-access`, which turns the whole sandbox off rather than allowing these three. See `stim guide errors sandbox`."
225
- };
226
- const paths = allowanceSearchPaths(settingsRoot, home);
227
- const missing = missingAllowance(paths, stimHome, home);
228
- if (missing.length === 0) return {
229
- level: "cost",
230
- title: "Stim is allowed through this sandbox, but the session has not picked it up",
231
- detail: `${symptoms} ${paths[0]} already grants all three, so the settings were read before they were written.`,
232
- fix: "Restart the session for the allowance to take effect."
121
+ detail,
122
+ fix: `Repair ${fallback.manifest}, or ${fallback.fromEnvironment ? `unset ${fallback.key}` : `remove ${fallback.key}${file ? ` from ${file}` : ""}`}.`
233
123
  };
234
124
  return {
235
- level: "cost",
125
+ level: "note",
236
126
  title,
237
- detail: `${symptoms} Missing: ${missing.join(", ")}.`,
238
- fix: `Run \`stim doctor --fix\` to write sandbox.filesystem.allowWrite, sandbox.network.allowMachLookup, and sandbox.network.allowLocalBinding into ${paths[0]}, merged with what is there, or add the missing keys by hand. See \`stim guide errors sandbox\`.`
127
+ detail,
128
+ fix: fallback.fromEnvironment ? "Point STIM_ANDROID_CAS_TOOLCHAIN at an absolute path to the toolchain JSON manifest, or unset it." : "Set optimizations.android.casToolchain to an absolute path to the toolchain JSON manifest, or remove it."
239
129
  };
240
130
  }
131
+ function missingPath(value, projectRoot, exists, trim = false) {
132
+ const opened = trim ? value.trim() : value;
133
+ if (opened.trim() === "") return null;
134
+ const path = isAbsolute(opened) ? opened : resolve(projectRoot, opened);
135
+ if (exists(path)) return null;
136
+ return opened === opened.trim() ? path : JSON.stringify(path);
137
+ }
241
138
  //#endregion
242
139
  //#region src/diagnostics/doctor-storage.ts
243
- function checkStorageLayout(projectRoot, { platform, host = process.platform, device = filesystemDevice, stagingRoot = temporaryRoot } = {}) {
140
+ function checkStorageLayout(projectRoot, { platform, iosProjectPath = "ios", androidLayout = defaultAndroidLayout(projectRoot), host = process.platform, device = filesystemDevice, stagingRoot = temporaryRoot, scope } = {}) {
244
141
  const findings = [];
245
142
  const temporaryFix = "Unset STIM_TMPDIR / machine tempDir to select same-volume staging automatically, or point the override at a writable directory on the relevant volume outside Git working trees.";
246
143
  const check = (operation, paths, fix) => {
@@ -253,18 +150,21 @@ function checkStorageLayout(projectRoot, { platform, host = process.platform, de
253
150
  });
254
151
  };
255
152
  try {
256
- const target = repoRoot(projectRoot) ?? projectRoot;
257
- const source = resolveSourceCheckout(target);
258
- check("Worktree copy", ["path" in source ? source.path : target, target], "Keep the source checkout and linked worktree on the same volume to share file blocks when warming.");
259
153
  const cache = sharedBuildCache();
260
- check("Cached app/APK staging", [cache, stagingRoot(join(cache, "artifact.app"))], temporaryFix);
154
+ if (scope !== "native") {
155
+ const target = repoRoot(projectRoot) ?? projectRoot;
156
+ const source = resolveSourceCheckout(target);
157
+ check("Worktree copy", ["path" in source ? source.path : target, target], "Keep the source checkout and linked worktree on the same volume to share file blocks when warming.");
158
+ check("Cached app/APK staging", [cache, stagingRoot(join(cache, "artifact.app"))], temporaryFix);
159
+ }
261
160
  const cacheFix = "Place STIM_BUILD_CACHE / machine caches.buildCache on the build-output volume, or accept the full-copy cost of keeping the cache on a separate volume.";
262
- if ((platform === "ios" || platform !== "android" && existsSync(join(projectRoot, "ios"))) && host === "darwin") {
161
+ const localIos = platform === "ios" || platform !== "android" && existsSync(join(projectRoot, iosProjectPath));
162
+ if (scope !== "shared" && localIos && host === "darwin") {
263
163
  const output = join(workspaceDerivedData(projectRoot), "Build", "Products");
264
164
  check("iOS build-cache storage", [output, cache], cacheFix);
265
165
  check("iOS device app staging", [output, stagingRoot(join(output, "artifact.app"))], temporaryFix);
266
166
  }
267
- if (platform === "android" || platform !== "ios" && existsSync(join(projectRoot, "android"))) check("Android build-cache storage", [apkOutputsDir(projectRoot), cache], cacheFix);
167
+ if (scope !== "shared" && (platform === "android" || platform !== "ios" && existsSync(androidLayout.gradleRoot))) check("Android build-cache storage", [apkOutputsDir(androidLayout), cache], cacheFix);
268
168
  } catch (error) {
269
169
  findings.push({
270
170
  level: "cost",
@@ -347,8 +247,8 @@ function parseIosDebugArchitectures(output) {
347
247
  unknown: report.unknown || !inspected
348
248
  };
349
249
  }
350
- function inspectIosDebugArchitectures(projectRoot) {
351
- const iosRoot = join(projectRoot, "ios");
250
+ function inspectIosDebugArchitectures(projectRoot, iosProjectPath = "ios") {
251
+ const iosRoot = join(projectRoot, iosProjectPath);
352
252
  if (!existsSync(iosRoot)) return [];
353
253
  const findings = [];
354
254
  const unknown = [];
@@ -425,8 +325,8 @@ function children(path) {
425
325
  throw error;
426
326
  }
427
327
  }
428
- function moduleRoots(root) {
429
- const modules = [join(root, "android", "app")];
328
+ function moduleRoots(root, layout) {
329
+ const modules = [layout.moduleDir];
430
330
  const dependencies = join(root, "node_modules");
431
331
  for (const name of children(dependencies)) {
432
332
  if (name.startsWith(".")) continue;
@@ -448,8 +348,8 @@ function cacheFiles(base, depth = 0) {
448
348
  }
449
349
  return files;
450
350
  }
451
- function readCxxLauncherStates(root) {
452
- return moduleRoots(root).flatMap((module) => cacheFiles(join(module, ".cxx")).map((path) => ({
351
+ function readCxxLauncherStates(root, layout = defaultAndroidLayout(root)) {
352
+ return moduleRoots(root, layout).flatMap((module) => cacheFiles(join(module, ".cxx")).map((path) => ({
453
353
  path: relative(root, path),
454
354
  launcher: parseCmakeCacheLauncher(readFileSync(path, "utf8"))
455
355
  })));
@@ -468,10 +368,11 @@ function contained(root, path) {
468
368
  const rel = relative(root, path);
469
369
  return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
470
370
  }
471
- function selectedCompilerCache(root) {
371
+ function selectedCompilerCache(settings) {
372
+ if (!settings) return null;
472
373
  try {
473
374
  return resolveAndroidCompilerCache({
474
- optimizations: projectOptimizations(root),
375
+ optimizations: resolveOptimizations(settings),
475
376
  use: readAndroidCasToolchain
476
377
  }).optimizations.android.compilerCache;
477
378
  } catch {
@@ -482,16 +383,16 @@ function selectedCompilerCache(root) {
482
383
  * Keep every native build stopped until repair finishes. The supplemental
483
384
  * cache-lock check cannot detect uncached builds, release-swap fallback, or direct Gradle.
484
385
  */
485
- function repairCxxLauncherState(root) {
386
+ function repairCxxLauncherState(root, settings, layout = defaultAndroidLayout(root)) {
486
387
  const canonicalRoot = realpathSync(root);
487
388
  const result = {
488
389
  removed: [],
489
390
  refused: []
490
391
  };
491
- if (selectedCompilerCache(root) !== "ccache") return result;
392
+ if (selectedCompilerCache(settings) !== "ccache") return result;
492
393
  if (!getExecutor().findExecutable("ccache")) return result;
493
- const appOverrides = declaredLauncher(join(root, "android", "app"));
494
- const targets = readCxxLauncherStates(root).filter(({ launcher }) => {
394
+ const appOverrides = declaredLauncher(layout.moduleDir);
395
+ const targets = readCxxLauncherStates(root, layout).filter(({ launcher }) => {
495
396
  if (!launcher) return true;
496
397
  const command = launcher.split(";")[0]?.trim();
497
398
  return Boolean(command && isAbsolute(command) && basename(command) === "ccache" && !existsSync(command));
@@ -545,114 +446,6 @@ function repairCxxLauncherState(root) {
545
446
  return result;
546
447
  }
547
448
  //#endregion
548
- //#region src/diagnostics/doctor-config.ts
549
- function readMachineSettings(context) {
550
- try {
551
- const layers = settingsLayers(context);
552
- return {
553
- settings: mergeSettingsLayers(layers.map((layer) => layer.settings)),
554
- layers,
555
- corrupt: null
556
- };
557
- } catch (error) {
558
- if (error.code !== "STIM_CONFIG_CORRUPT") throw error;
559
- return {
560
- settings: {},
561
- layers: [],
562
- corrupt: {
563
- level: "note",
564
- title: "The Stim config is not a valid JSON object",
565
- detail: `${String(error.message).split("\n")[0]}. Every setting and device record lives in that file, so no other machine-level check can run until it parses.`,
566
- fix: configCorruptRepair()
567
- }
568
- };
569
- }
570
- }
571
- const PATH_SETTINGS_TRIMMED_BY_THEIR_CONSUMER = /* @__PURE__ */ new Set(["android.keystore"]);
572
- function checkMachineSettings({ settings, layers, projectRoot, optimizations = null, reportedElsewhere = [], exists = existsSync, env = process.env }) {
573
- const findings = [];
574
- const fallback = optimizations?.android.compilerCacheFallback ?? null;
575
- const fallbackEntry = fallback ? settingOrigin(layers, fallback.key) : null;
576
- const attributed = fallback && (fallback.fromEnvironment || fallbackEntry) ? fallback : null;
577
- const reported = attributed?.manifest && !exists(attributed.manifest) ? null : attributed;
578
- const skip = new Set(reportedElsewhere);
579
- if (reported) skip.add(reported.key);
580
- if (!optimizations) for (const key of SETTINGS_WITH_RESOLVE_TIME_FALLBACK) skip.add(key);
581
- for (const key of PATH_SETTINGS) {
582
- const variable = settingDefinition(key)?.env;
583
- if (skip.has(key) || variable && skip.has(variable)) continue;
584
- const override = variable ? env[variable] : void 0;
585
- if (variable && override) {
586
- const named = missingPath(override, projectRoot, exists);
587
- if (named) findings.push({
588
- level: "note",
589
- title: "An environment variable points at a path that is not there",
590
- detail: `${variable} in the environment names ${named}, which does not exist.`,
591
- fix: `Point ${variable} at the path it should name, or unset it.`
592
- });
593
- continue;
594
- }
595
- const entry = settingOrigin(layers, key);
596
- if (!entry || typeof entry.value !== "string") continue;
597
- const named = missingPath(entry.value, projectRoot, exists, PATH_SETTINGS_TRIMMED_BY_THEIR_CONSUMER.has(key));
598
- if (!named) continue;
599
- findings.push({
600
- level: "note",
601
- title: "A setting points at a path that is not there",
602
- detail: `${key} in ${entry.file} names ${named}, which does not exist.`,
603
- fix: `Point ${key} at the path it should name, or remove it from ${entry.file}.`
604
- });
605
- }
606
- if (reported && optimizations) findings.push(compilerCacheFinding({
607
- fallback: reported,
608
- optimizations,
609
- file: fallbackEntry?.file ?? null
610
- }));
611
- for (const key of unknownSettingKeys(settings)) {
612
- const file = settingOrigin(layers, key)?.file ?? null;
613
- findings.push({
614
- level: "note",
615
- title: "A key in the config is inert",
616
- detail: `${key}${file ? ` in ${file}` : ""} is not read by Stim, so its value changes nothing.`,
617
- fix: `Remove ${key}${file ? ` from ${file}` : ""}, or run \`stim guide settings\` for the name that replaced it.`
618
- });
619
- }
620
- return findings;
621
- }
622
- function compilerCacheFinding({ fallback, optimizations, file }) {
623
- const detail = compilerCacheFallbackMessage({
624
- fallback,
625
- compilerCache: optimizations.android.compilerCache === "none" ? "none" : "ccache",
626
- file
627
- });
628
- if (fallback.key === "optimizations.android.compilerCache") return {
629
- level: "note",
630
- title: "A setting needs a companion this config does not supply",
631
- detail,
632
- fix: "Set optimizations.android.casToolchain to the toolchain JSON manifest, or set optimizations.android.compilerCache to ccache."
633
- };
634
- const title = fallback.fromEnvironment ? "An environment variable holds a value Stim cannot use" : "A setting holds a value Stim cannot use";
635
- if (fallback.manifest) return {
636
- level: "note",
637
- title,
638
- detail,
639
- fix: `Repair ${fallback.manifest}, or ${fallback.fromEnvironment ? `unset ${fallback.key}` : `remove ${fallback.key}${file ? ` from ${file}` : ""}`}.`
640
- };
641
- return {
642
- level: "note",
643
- title,
644
- detail,
645
- fix: fallback.fromEnvironment ? "Point STIM_ANDROID_CAS_TOOLCHAIN at an absolute path to the toolchain JSON manifest, or unset it." : "Set optimizations.android.casToolchain to an absolute path to the toolchain JSON manifest, or remove it."
646
- };
647
- }
648
- function missingPath(value, projectRoot, exists, trim = false) {
649
- const opened = trim ? value.trim() : value;
650
- if (opened.trim() === "") return null;
651
- const path = isAbsolute(opened) ? opened : resolve(projectRoot, opened);
652
- if (exists(path)) return null;
653
- return opened === opened.trim() ? path : JSON.stringify(path);
654
- }
655
- //#endregion
656
449
  //#region src/diagnostics/doctor.ts
657
450
  function finding(level, title, detail, fix) {
658
451
  return {
@@ -715,8 +508,8 @@ function brokenPodLinks(podsRoot) {
715
508
  return [];
716
509
  }
717
510
  }
718
- function hasIosWarmOutput(root) {
719
- if (existsSync$1(join$1(root, "ios", "build"))) return true;
511
+ function hasIosWarmOutput(root, iosRoot) {
512
+ if (existsSync$1(join$1(iosRoot, "build"))) return true;
720
513
  const derivedData = workspaceDerivedData(root);
721
514
  if (existsSync$1(join$1(derivedData, "Build", "Products"))) return true;
722
515
  try {
@@ -802,8 +595,13 @@ function seedFindings(mainRoot, upstream) {
802
595
  if (defaultBranch !== null && defaultBranch !== branch) findings.push(finding("note", `The source checkout is on ${branch}, not the default branch ${defaultBranch}`, `Every worktree warmed from here carries ${branch}'s dependencies. \`stim worktree warm --refresh\` warns and continues; it never switches a branch under another checkout.`, `Run \`git -C ${quotedRoot} checkout ${defaultBranch}\`.`));
803
596
  return findings;
804
597
  }
805
- function checkMainCheckout(projectRoot, { npmTreeValid, brokenPods = void 0, upstream = void 0, linkedWorktrees = void 0, platform, localIos = platform !== "android" } = {}) {
598
+ function checkMainCheckout(projectRoot, { npmTreeValid, brokenPods = void 0, upstream = void 0, linkedWorktrees = void 0, platform, localIos = platform !== "android", iosProjectPath = "ios", androidLayout = defaultAndroidLayout(projectRoot) } = {}) {
806
599
  const mainRoot = mainCheckoutProjectRoot(projectRoot);
600
+ const mainAndroid = {
601
+ ...androidLayout,
602
+ gradleRoot: join$1(mainRoot, relative$1(projectRoot, androidLayout.gradleRoot)),
603
+ moduleDir: join$1(mainRoot, relative$1(projectRoot, androidLayout.moduleDir))
604
+ };
807
605
  const findings = [];
808
606
  const dependencies = dependencyState(mainRoot);
809
607
  if (dependencies) {
@@ -814,9 +612,10 @@ function checkMainCheckout(projectRoot, { npmTreeValid, brokenPods = void 0, ups
814
612
  if ((npmTreeValid === void 0 ? installedNpmTreeIsValid(dependencies.root) : npmTreeValid) === false) findings.push(finding("cost", "The source checkout dependency tree is stale", `npm reports that ${dependencies.root}/node_modules does not match the project dependency graph. Copying it makes each worktree start from the same invalid state.`, `Run \`${installCommand}\` before creating native worktrees, or let \`stim worktree warm --refresh\` run it from a linked worktree.`));
815
613
  }
816
614
  }
817
- const podfileLock = join$1(mainRoot, "ios", "Podfile.lock");
818
- const podManifest = join$1(mainRoot, "ios", "Pods", "Manifest.lock");
819
- const podsRoot = join$1(mainRoot, "ios", "Pods");
615
+ const iosRoot = join$1(mainRoot, iosProjectPath);
616
+ const podfileLock = join$1(iosRoot, "Podfile.lock");
617
+ const podManifest = join$1(iosRoot, "Pods", "Manifest.lock");
618
+ const podsRoot = join$1(iosRoot, "Pods");
820
619
  if (localIos && existsSync$1(podfileLock)) {
821
620
  let podsState = null;
822
621
  if (!existsSync$1(podManifest)) podsState = "missing";
@@ -826,18 +625,16 @@ function checkMainCheckout(projectRoot, { npmTreeValid, brokenPods = void 0, ups
826
625
  podsState = "stale";
827
626
  }
828
627
  if (podsState) {
829
- const iosRoot = join$1(mainRoot, "ios");
830
628
  const podCommand = `cd ${quotedPath(iosRoot)} && ${podInstallCommand(mainRoot)}`;
831
629
  findings.push(finding("cost", `The source checkout CocoaPods state is ${podsState}`, `ios/Pods cannot be reused safely because its Manifest.lock ${podsState === "missing" ? "is absent" : "does not match ios/Podfile.lock"}.`, `Run \`${podCommand}\` before creating native worktrees, or let \`stim worktree warm --refresh\` run it from a linked worktree.`));
832
630
  }
833
631
  const broken = brokenPods === void 0 && existsSync$1(podsRoot) ? brokenPodLinks(podsRoot) : brokenPods || [];
834
632
  if (broken.length) {
835
- const iosRoot = join$1(mainRoot, "ios");
836
633
  const podCommand = `cd ${quotedPath(iosRoot)} && ${podInstallCommand(mainRoot, "--clean-install")}`;
837
634
  findings.push(finding("cost", "The source checkout CocoaPods state has broken links", `${broken.length} symlink${broken.length === 1 ? "" : "s"} under ios/Pods point to missing files. Worktrees copy these broken links and can fail during compilation. First: ${broken[0]}.`, `Run \`${podCommand}\` before creating native worktrees.`));
838
635
  }
839
636
  }
840
- const coldPlatforms = [localIos && existsSync$1(join$1(mainRoot, "ios")) && !hasIosWarmOutput(mainRoot) ? "iOS" : null, platform !== "ios" && existsSync$1(join$1(mainRoot, "android")) && !existsSync$1(join$1(mainRoot, "android", "build")) && !existsSync$1(join$1(mainRoot, "android", "app", "build")) ? "Android" : null].filter((coldPlatform) => coldPlatform !== null);
637
+ const coldPlatforms = [localIos && existsSync$1(iosRoot) && !hasIosWarmOutput(mainRoot, iosRoot) ? "iOS" : null, platform !== "ios" && hasAndroidProject(mainAndroid) && !existsSync$1(join$1(mainAndroid.gradleRoot, "build")) && !existsSync$1(join$1(mainAndroid.moduleDir, "build")) ? "Android" : null].filter((coldPlatform) => coldPlatform !== null);
841
638
  if (coldPlatforms.length) findings.push(finding("note", `The source checkout has no ${coldPlatforms.join(" or ")} warm build output`, "The shared Stim artifact or compilation cache can still be warm. Building the source checkout once gives later native worktrees the strongest warm starting point.", `When more native worktrees are expected, run \`stim start\`, \`stim ${coldPlatforms[0] === "iOS" ? "ios" : "android"}\`, and \`stim stop\` from ${mainRoot}.`));
842
639
  if (linkedWorktrees === void 0 ? hasLinkedWorktree(projectRoot) : linkedWorktrees) findings.push(...seedFindings(mainRoot, upstream === void 0 ? locallyKnownUpstream(mainRoot) : upstream));
843
640
  return findings;
@@ -882,11 +679,6 @@ function checkChrome({ usesWeb, chrome = findChrome }) {
882
679
  if (!usesWeb() || chrome()) return null;
883
680
  return finding("cost", "No Chrome is installed, so `stim web` cannot open this app", "`stim web` drives the installed Google Chrome or Chromium with a Stim-owned profile, and found neither in /Applications, ~/Applications, Program Files or on PATH. This project renders on the web (web.url is set, app.json lists the web platform, or react-native-web is installed).", CHROME_INSTALL_REMEDY);
884
681
  }
885
- function projectUsesWeb(projectRoot, settings, appConfig, platform) {
886
- if (platform) return false;
887
- const platforms = (appConfig?.expo ?? appConfig)?.platforms;
888
- return webSettings(settings).url !== null || Array.isArray(platforms) && platforms.includes("web") || isPackageResolvable(projectRoot, "react-native-web");
889
- }
890
682
  function checkCcacheInstalled(onPath) {
891
683
  if (onPath) return null;
892
684
  return finding("cost", "ccache is not on PATH, so Android C++ recompiles in every worktree", "Stim resolves the launcher with a PATH lookup for `ccache` -- the same lookup the build itself uses -- and found nothing, so it passes no CMAKE_C_COMPILER_LAUNCHER / CMAKE_CXX_COMPILER_LAUNCHER to Gradle and every AGP CMake task compiles without one. Those tasks are uncacheable by Gradle, so not one C++ object crosses a worktree. Measured on trailhead (arm64, fresh worktree): 49.6s without the launcher against 34.2s with it.", "brew install ccache, then delete android/app/.cxx and node_modules/**/android/.cxx once so CMake reconfigures with the launcher. The shell that runs Stim must have the install location on PATH -- agent shells often lack /opt/homebrew/bin.");
@@ -934,6 +726,7 @@ function checkEasAuth({ provider, owner = null, auth = null } = {}) {
934
726
  if (!status || status.ok) return null;
935
727
  if (status.unknown) return finding("note", "Could not check the EAS session", `\`eas whoami\` did not give a definite answer (${status.unknown}), so whether this project's EAS build cache can be reached is unknown. Offline is the ordinary reason, and it is not a problem: the cache simply does not answer until the machine is back on the network.`, null);
936
728
  if (status.code === "no-cli") return finding("cost", "The build cache provider is \"eas\", but no eas-cli is installed", "The provider shells out to `npx eas-cli` on every lookup and every upload. With no eas-cli resolvable, npx downloads one on the fly (slow, and a version nobody chose) or the call fails -- and the provider swallows that failure and returns null, so every build looks like a cache miss and nothing says why.", status.remedy ?? null);
729
+ if (status.code === "project-cli") return finding("note", "Did not check the EAS session: doctor runs only an eas-cli it can place outside the repository", `Doctor does not run code from the project tree, and \`eas whoami\` would run ${status.reason}, which is in the repository, in a node_modules/.bin above it, or in a checkout whose repository root git did not report. Builds run that eas-cli, so whether this project's EAS build cache can be reached is unchecked.`, status.remedy ?? null);
937
730
  if (status.code === "logged-out") return finding("cost", "Not logged in to EAS, so the shared build cache never answers", "eas-build-cache-provider catches its own errors and returns null, so an unauthenticated lookup reads as a plain cache miss: every build compiles, nothing is uploaded for anybody else, and no line in any log mentions authentication.", status.remedy ?? null);
938
731
  if (status.code === "wrong-account") return finding("note", `EAS is authenticated as ${status.account}, but this project's owner is ${status.owner}`, `A session on an account that does not cover ${status.owner} cannot read or write that account's builds, so the shared cache silently does nothing here. This is a NOTE and not a hard failure on purpose: \`eas whoami\` only enumerates accounts for some actors (a robot prints a display name that is not an account name at all), the list may be incomplete, and access is the server's decision rather than this list's. Confirm before acting on it.`, status.remedy ?? null);
939
732
  return null;
@@ -942,9 +735,57 @@ function checkConcurrency({ maxBuilds = 0, maxDevices = 0, liveDevices = 0, acti
942
735
  if (!maxBuilds && !maxDevices) return null;
943
736
  return finding("note", "Concurrency limits are set", `${`maxBuilds ${maxBuilds || "unlimited"}, maxDevices ${maxDevices || "unlimited"}`}. Right now ${typeof liveDevices === "number" ? `${liveDevices} Stim device(s) are booted` : `the number of booted Stim devices is unknown (${liveDevices.unknown})`} and ${activeBuilds} build slot(s) are in use on this machine. At the device cap a new \`stim ios\`/\`android\` waits in FIFO order for 600s (--wait changes it; --no-wait refuses with STIM_AT_CAPACITY); at the build cap a compile waits for a free slot.`, null);
944
737
  }
738
+ function easCliOutsideRepository(projectRoot) {
739
+ const found = (String(getExecutor().findExecutable("eas") ?? "").split("\n")[0] ?? "").trim();
740
+ const root = found ? repoRoot(projectRoot) : null;
741
+ if (!found || !root) return null;
742
+ try {
743
+ const repository = realpathSync$1(root);
744
+ const directory = realpathSync$1(dirname$1(resolve$1(found)));
745
+ const within = (path) => {
746
+ const rel = relative$1(repository, path);
747
+ return !isAbsolute$1(rel) && rel !== ".." && !rel.startsWith(`..${sep$1}`);
748
+ };
749
+ if (within(join$1(directory, basename$1(found))) || within(realpathSync$1(found))) return null;
750
+ for (let dir = realpathSync$1(projectRoot);; dir = dirname$1(dir)) {
751
+ if (directory === join$1(dir, "node_modules", ".bin")) return null;
752
+ if (dirname$1(dir) === dir) return found;
753
+ }
754
+ } catch {
755
+ return null;
756
+ }
757
+ }
945
758
  function readProjectEasCliVersion(projectRoot) {
946
- const bin = resolveEasCliBin(projectRoot);
947
- return bin ? readInstalledEasCliVersion(bin.file, projectRoot) : null;
759
+ const packageJson = resolvePackageJson(projectRoot, "eas-cli");
760
+ if (packageJson) {
761
+ const version = readJsonObject(packageJson)?.version;
762
+ return typeof version === "string" ? `eas-cli/${version}` : null;
763
+ }
764
+ const onPath = easCliOutsideRepository(projectRoot);
765
+ return onPath ? readInstalledEasCliVersion(onPath, projectRoot) : null;
766
+ }
767
+ function doctorEasAuth({ projectRoot, owner }) {
768
+ const file = easCliOutsideRepository(projectRoot);
769
+ if (file) return checkEasAuth$1({
770
+ projectRoot,
771
+ owner,
772
+ resolveBin: () => ({
773
+ file,
774
+ source: "path"
775
+ })
776
+ });
777
+ const unchecked = resolveEasCliBin(projectRoot);
778
+ if (!unchecked) return checkEasAuth$1({
779
+ projectRoot,
780
+ owner,
781
+ resolveBin: () => null
782
+ });
783
+ return {
784
+ failed: true,
785
+ code: "project-cli",
786
+ reason: unchecked.file,
787
+ remedy: "Once you trust this checkout, run `npx eas whoami` in it. Or install eas-cli globally (`npm i -g eas-cli`) so doctor can check the session."
788
+ };
948
789
  }
949
790
  function checkRemoteDevice({ configured = null, daemonInEnv = false, agentDeviceOnPath = false, easCliResolvable = false, readEasCliVersion = () => null } = {}) {
950
791
  if (!configured) return null;
@@ -1001,40 +842,22 @@ function configuredRemoteBackends(projectSettings, platform) {
1001
842
  const androidTarget = remoteAndroidSetting(projectSettings);
1002
843
  return [.../* @__PURE__ */ new Set([...platform !== "android" ? [iosTarget?.kind === "backend" ? iosTarget.backend : null] : [], ...platform !== "ios" ? [androidTarget?.kind === "backend" ? androidTarget.backend : null] : []])].filter((backend) => backend !== null);
1003
844
  }
1004
- function runDoctor(projectRoot, { readFile = readFileSync$1, xcodeMajor = null, easAuth = checkEasAuth$1, concurrency = getConcurrencyLimits, liveDevices = null, activeBuilds = null, remoteEnv = process.env, lookupAgentDevice = null, lookupEasCli = null, lookupSimSlim = null, memoryPressure = readHostMemoryPressure, lookupCcache = null, lookupChrome, tailnetStatus = realIo.status, now = Date.now, platform, host = process.platform } = {}) {
1005
- const localIos = platform !== "android" && host === "darwin";
1006
- const read = (rel) => {
1007
- const p = join$1(projectRoot, rel);
1008
- if (!existsSync$1(p)) return null;
1009
- try {
1010
- return readFile(p, "utf-8");
1011
- } catch {
1012
- return null;
1013
- }
1014
- };
1015
- const settingsRepoRoot = repoRoot(projectRoot) ?? projectRoot;
1016
- const machineSettings = readMachineSettings({
1017
- projectPath: projectRoot,
1018
- gitCommonDir: gitCommonDir(projectRoot),
1019
- repoRoot: settingsRepoRoot
1020
- });
1021
- if (machineSettings.corrupt) return [machineSettings.corrupt];
845
+ function runDoctor(projectRoot, options = {}, inspectors = []) {
846
+ const { concurrency = getConcurrencyLimits, liveDevices = null, activeBuilds = null, lookupSimSlim = null, memoryPressure = readHostMemoryPressure, lookupChrome, platform, host = process.platform, platforms = ["ios", "android"], machineSettings = readMachineSettings(projectSettingsContext(projectRoot)) } = options;
847
+ const localIos = platforms.includes("ios") && platform !== "android" && host === "darwin";
1022
848
  const projectSettings = machineSettings.settings;
1023
- const pkg = readJsonObject(join$1(projectRoot, "package.json"));
1024
- const appConfig = readJsonObject(join$1(projectRoot, "app.json"));
1025
- const dynamicConfig = appConfig ? null : [
1026
- "app.config.ts",
1027
- "app.config.js",
1028
- "app.config.mjs"
1029
- ].find((f) => existsSync$1(join$1(projectRoot, f))) || null;
1030
- const podfileProperties = readJsonObject(join$1(projectRoot, "ios", "Podfile.properties.json"));
1031
- const podfile = read(join$1("ios", "Podfile"));
1032
- const metroConfig = read("metro.config.js") ?? read("metro.config.cjs");
1033
- const isExpo = detectIsExpo(projectRoot);
1034
- const expoRange = (pkg?.dependencies)?.expo || "";
1035
- const sdkMajor = parseInt(String(expoRange).replace(/[^\d.]/g, "").split(".")[0] ?? "", 10) || null;
1036
- const provider = appConfig ? providerFromConfig(appConfig) : null;
1037
- const owner = appConfig ? ownerFromConfig(appConfig) : null;
849
+ const context = {
850
+ root: projectRoot,
851
+ options,
852
+ settings: projectSettings,
853
+ optimizations: null,
854
+ platforms,
855
+ repoRoot: options.repoRoot ?? null
856
+ };
857
+ if (machineSettings.corrupt) return {
858
+ findings: [machineSettings.corrupt],
859
+ context
860
+ };
1038
861
  const limits = typeof concurrency === "function" ? concurrency() : concurrency;
1039
862
  let concurrencyFinding = null;
1040
863
  if (limits && (limits.maxBuilds || limits.maxDevices)) concurrencyFinding = checkConcurrency({
@@ -1051,17 +874,9 @@ function runDoctor(projectRoot, { readFile = readFileSync$1, xcodeMajor = null,
1051
874
  use: readAndroidCasToolchain
1052
875
  }).optimizations;
1053
876
  } catch {}
1054
- const remoteBuildCache = optimizations?.buildCache && optimizations.remoteBuildCache;
1055
- const easFinding = remoteBuildCache && provider === "eas" ? checkEasAuth({
1056
- provider,
1057
- owner,
1058
- auth: easAuth({
1059
- projectRoot,
1060
- owner
1061
- })
1062
- }) : null;
877
+ context.optimizations = optimizations;
1063
878
  for (const poolPlatform of ["ios", "android"]) {
1064
- if (platform && platform !== poolPlatform) continue;
879
+ if (!platforms.includes(poolPlatform) || platform && platform !== poolPlatform) continue;
1065
880
  const poolSettingError = parkedMaxSetting(poolPlatform).error;
1066
881
  if (poolSettingError) settingShapeFindings.push(finding("cost", `The ${poolPlatform === "ios" ? "simulator" : "emulator"} pool bound is not a number`, poolSettingError, POOL_SETTING_REMEDY));
1067
882
  }
@@ -1077,6 +892,71 @@ function runDoctor(projectRoot, { readFile = readFileSync$1, xcodeMajor = null,
1077
892
  profileError: simslimProfileError,
1078
893
  onPath: simslimProfile ? lookupSimSlim ? lookupSimSlim() : simslimIsOnPath() : false
1079
894
  });
895
+ const memoryAdvice = localIos && !remoteIosSetting(projectSettings) ? hostMemoryPressureAdvice(memoryPressure()) : null;
896
+ return {
897
+ findings: [...[
898
+ ...checkStorageLayout(projectRoot, {
899
+ platform,
900
+ host,
901
+ scope: "shared"
902
+ }),
903
+ checkChrome({
904
+ usesWeb: () => !platform && webSettings(projectSettings).url !== null,
905
+ chrome: lookupChrome
906
+ }),
907
+ concurrencyFinding,
908
+ memoryAdvice ? finding("cost", "Host memory pressure can stall the iOS simulator", memoryAdvice, "Free host memory, then retry. Run `stim guide lifecycle simslim` for the optional memory reduction setup.") : null,
909
+ simslimFinding,
910
+ ...settingShapeFindings,
911
+ ...checkMachineSettings({
912
+ settings: projectSettings,
913
+ layers: machineSettings.layers,
914
+ projectRoot,
915
+ optimizations,
916
+ reportedElsewhere: simslimProfileError ? ["ios.simslimProfile"] : []
917
+ })
918
+ ].filter((f) => Boolean(f)), ...inspectors.flatMap((inspect) => inspect(context))],
919
+ context
920
+ };
921
+ }
922
+ function reactNativeDoctorFindings({ root: projectRoot, settings: projectSettings, optimizations, options, repoRoot: repositoryRoot }) {
923
+ const androidLayout = resolveAndroidLayout(projectSettings, projectRoot, repositoryRoot ?? projectRoot);
924
+ const { readFile = readFileSync$1, xcodeMajor = null, easAuth = doctorEasAuth, remoteEnv = process.env, lookupAgentDevice = null, lookupEasCli = null, lookupCcache = null, tailnetStatus = realIo.status, now = Date.now, platform, host = process.platform } = options;
925
+ const localIos = platform !== "android" && host === "darwin";
926
+ const read = (rel) => {
927
+ const p = join$1(projectRoot, rel);
928
+ if (!existsSync$1(p)) return null;
929
+ try {
930
+ return readFile(p, "utf-8");
931
+ } catch {
932
+ return null;
933
+ }
934
+ };
935
+ const pkg = readJsonObject(join$1(projectRoot, "package.json"));
936
+ const appConfig = readJsonObject(join$1(projectRoot, "app.json"));
937
+ const dynamicConfig = appConfig ? null : [
938
+ "app.config.ts",
939
+ "app.config.js",
940
+ "app.config.mjs"
941
+ ].find((f) => existsSync$1(join$1(projectRoot, f))) || null;
942
+ const iosProject = resolveIosProjectDir(projectSettings, projectRoot);
943
+ const podfileProperties = readJsonObject(join$1(iosProject.dir, "Podfile.properties.json"));
944
+ const podfile = read(relative$1(projectRoot, join$1(iosProject.dir, "Podfile")));
945
+ const metroConfig = read("metro.config.js") ?? read("metro.config.cjs");
946
+ const isExpo = detectIsExpo(projectRoot);
947
+ const expoRange = (pkg?.dependencies)?.expo || "";
948
+ const sdkMajor = parseInt(String(expoRange).replace(/[^\d.]/g, "").split(".")[0] ?? "", 10) || null;
949
+ const provider = appConfig ? providerFromConfig(appConfig) : null;
950
+ const owner = appConfig ? ownerFromConfig(appConfig) : null;
951
+ const remoteBuildCache = optimizations?.buildCache && optimizations.remoteBuildCache;
952
+ const easFinding = remoteBuildCache && provider === "eas" ? checkEasAuth({
953
+ provider,
954
+ owner,
955
+ auth: easAuth({
956
+ projectRoot,
957
+ owner
958
+ })
959
+ }) : null;
1080
960
  const remoteBackends = configuredRemoteBackends(projectSettings, platform);
1081
961
  const daemonInEnv = Boolean(remoteEnv.AGENT_DEVICE_DAEMON_BASE_URL?.trim() && remoteEnv.AGENT_DEVICE_DAEMON_AUTH_TOKEN?.trim());
1082
962
  const agentDeviceOnPath = remoteBackends.length ? lookupAgentDevice ? lookupAgentDevice() : agentDeviceIsOnPath() : false;
@@ -1089,47 +969,40 @@ function runDoctor(projectRoot, { readFile = readFileSync$1, xcodeMajor = null,
1089
969
  readEasCliVersion: () => readProjectEasCliVersion(projectRoot)
1090
970
  })).filter((remoteFinding) => remoteFinding !== null);
1091
971
  const easBuildDownloadFinding = projectEasBuildDownloadFinding(projectRoot, remoteBackends, lookupEasCli);
1092
- const memoryAdvice = localIos && !remoteIosSetting(projectSettings) ? hostMemoryPressureAdvice(memoryPressure()) : null;
1093
972
  return [
1094
973
  checkAppProject(projectRoot),
1095
974
  checkIosHost(platform, host),
1096
975
  checkXcodeEnvLineEndings(projectRoot, platform, host),
1097
976
  ...checkMainCheckout(projectRoot, {
1098
977
  platform,
1099
- localIos
978
+ localIos,
979
+ iosProjectPath: iosProject.relative,
980
+ androidLayout
1100
981
  }),
1101
- ...localIos ? inspectIosDebugArchitectures(mainCheckoutProjectRoot(projectRoot)) : [],
982
+ ...localIos ? inspectIosDebugArchitectures(mainCheckoutProjectRoot(projectRoot), iosProject.relative) : [],
1102
983
  ...checkStorageLayout(projectRoot, {
1103
984
  platform,
1104
- host
985
+ host,
986
+ scope: "native",
987
+ iosProjectPath: iosProject.relative,
988
+ androidLayout
1105
989
  }),
1106
990
  optimizations?.metroSharedCache ? checkMetroCache(metroConfig) : null,
1107
991
  localIos && optimizations?.ios.compilationCache ? checkCompilationCache(podfile, xcodeMajor) : null,
1108
992
  localIos && optimizations?.ios.compilationCache ? checkCcacheConflict(podfile, podfileProperties) : null,
1109
- ...platform === "ios" || optimizations?.android.compilerCache !== "ccache" ? [] : androidCcacheFindings(projectRoot, platform, lookupCcache),
993
+ ...platform === "ios" || optimizations?.android.compilerCache !== "ccache" ? [] : androidCcacheFindings(projectRoot, platform, lookupCcache, androidLayout),
1110
994
  checkAndroidPathRoom(projectRoot, platform, host),
1111
- checkAndroidSdk(projectRoot, platform),
995
+ checkAndroidSdk(projectRoot, platform, androidLayout),
1112
996
  checkChrome({
1113
- usesWeb: () => projectUsesWeb(projectRoot, projectSettings, appConfig, platform),
1114
- chrome: lookupChrome
997
+ usesWeb: () => !platform && webSettings(projectSettings).url === null && (Array.isArray((appConfig?.expo ?? appConfig)?.platforms) && (appConfig?.expo ?? appConfig).platforms.includes("web") || isPackageResolvable(projectRoot, "react-native-web")),
998
+ chrome: options.lookupChrome
1115
999
  }),
1116
1000
  remoteBuildCache ? checkBuildCacheProvider(appConfig, sdkMajor, isExpo, dynamicConfig) : null,
1117
1001
  easFinding,
1118
1002
  easBuildDownloadFinding,
1119
- concurrencyFinding,
1120
1003
  readOffloadCandidate(tailnetStatus, now(), host),
1121
- memoryAdvice ? finding("cost", "Host memory pressure can stall the iOS simulator", memoryAdvice, "Free host memory, then retry. Run `stim guide lifecycle simslim` for the optional memory reduction setup.") : null,
1122
- simslimFinding,
1123
- ...remoteFindings,
1124
- ...settingShapeFindings,
1125
- ...checkMachineSettings({
1126
- settings: projectSettings,
1127
- layers: machineSettings.layers,
1128
- projectRoot,
1129
- optimizations,
1130
- reportedElsewhere: simslimProfileError ? ["ios.simslimProfile"] : []
1131
- })
1132
- ].filter((f) => Boolean(f));
1004
+ ...remoteFindings
1005
+ ].filter((value) => Boolean(value));
1133
1006
  }
1134
1007
  function checkAndroidPathRoom(projectRoot, platform, host, hostArch = process.arch) {
1135
1008
  const room = platform === "ios" ? null : androidPathRoom(projectRoot, {
@@ -1142,15 +1015,18 @@ function checkAndroidPathRoom(projectRoot, platform, host, hostArch = process.ar
1142
1015
  ...finding("cost", "The project path leaves no room for Android native object paths", `${androidPathRoomMessage(room)} \`stim android\` refuses to build here with STIM_PATH_TOO_LONG, because Gradle would fail deep inside ninja instead.`, androidPathRoomRemedy(room))
1143
1016
  };
1144
1017
  }
1145
- function checkAndroidSdk(projectRoot, platform) {
1146
- const androidDir = join$1(projectRoot, "android");
1018
+ function hasAndroidProject(layout) {
1019
+ return existsSync$1(layout.custom ? layout.moduleDir : layout.gradleRoot);
1020
+ }
1021
+ function checkAndroidSdk(projectRoot, platform, layout = defaultAndroidLayout(projectRoot)) {
1022
+ const androidDir = layout.gradleRoot;
1147
1023
  if (platform === "ios" || platform !== "android" && !existsSync$1(androidDir)) return null;
1148
1024
  const sdkPath = androidHome();
1149
1025
  const refusal = androidSdkRefusal({
1150
1026
  sdkPath,
1151
1027
  sdkExists: existsSync$1(sdkPath),
1152
1028
  hasLocalProperties: existsSync$1(join$1(androidDir, "local.properties")),
1153
- localPropertiesPath: "android/local.properties"
1029
+ localPropertiesPath: `${relative$1(projectRoot, androidDir) || "."}/local.properties`
1154
1030
  });
1155
1031
  if (!refusal) return null;
1156
1032
  return {
@@ -1158,13 +1034,13 @@ function checkAndroidSdk(projectRoot, platform) {
1158
1034
  ...finding("cost", "No Android SDK was found", `${refusal.reason} \`stim android\` refuses with ${refusal.code} before Gradle runs.`, refusal.remedy)
1159
1035
  };
1160
1036
  }
1161
- function androidCcacheFindings(projectRoot, platform, lookupCcache) {
1162
- if (platform !== "android" && !existsSync$1(join$1(projectRoot, "android"))) return [];
1037
+ function androidCcacheFindings(projectRoot, platform, lookupCcache, layout) {
1038
+ if (platform !== "android" && !hasAndroidProject(layout)) return [];
1163
1039
  const onPath = lookupCcache ? lookupCcache() : ccacheIsOnPath();
1164
1040
  let cxx;
1165
1041
  try {
1166
1042
  cxx = checkCxxCompilerLauncher({
1167
- states: readCxxLauncherStates(projectRoot),
1043
+ states: readCxxLauncherStates(projectRoot, layout),
1168
1044
  ccacheOnPath: onPath
1169
1045
  });
1170
1046
  } catch (error) {
@@ -1199,6 +1075,14 @@ function checkFingerprintParity({ projectHash, worktreeHash, changed = [], dirty
1199
1075
  const names = changed.slice(0, 3).join(", ");
1200
1076
  return finding("note", "This checkout does not fingerprint like a fresh worktree of HEAD", `A clean detached worktree of HEAD computes a different @expo/fingerprint hash than this checkout, so worktrees will MISS the cache entries this checkout fills (and vice versa) until the two agree.${changed.length ? ` The differing source${changed.length === 1 ? "" : "s"}: ${names}${changed.length > 3 ? ` (and ${changed.length - 3} more)` : ""}.` : ""} ${dirtyFiles.length ? `The likely cause is uncommitted changes to tracked fingerprint inputs -- git reports ${dirtyFiles.slice(0, 3).join(", ")}${dirtyFiles.length > 3 ? ` (and ${dirtyFiles.length - 3} more)` : ""} dirty in this checkout.` : "The likely cause is uncommitted changes to tracked fingerprint inputs (this check compared against a clean worktree of HEAD)."} (To measure this, doctor ran a real fingerprint twice and briefly created a temporary git worktree -- .git/worktrees metadata was touched and cleaned up.)`, "Commit the dirty fingerprint inputs, or list the build-irrelevant ones in .fingerprintignore (same syntax as .gitignore, at the project root; Stim already ignores android/local.properties and android/.idea). Only the ones that genuinely cannot change the native build belong there -- generated reports, local env files, a lockfile whose checksums embed absolute machine paths. Never ignore a real native input (a Podfile, a gradle file, the app config) to force a hit: that trades a slow build for a wrong one.");
1201
1077
  }
1078
+ function fingerprintConfigSkipped(root) {
1079
+ const file = ["fingerprint.config.js", "fingerprint.config.cjs"].find((name) => existsSync$1(join$1(root, name)));
1080
+ if (!file) return null;
1081
+ return {
1082
+ ...finding("note", `Skipped the fingerprint checks: ${file} is code`, `@expo/fingerprint loads ${file} with require(), and doctor does not execute project code, so it did not compare this checkout's fingerprint with a fresh worktree or look for linked libraries that carry Git metadata into it.`, "Builds load the config. Once you trust this checkout, `stim ios` or `stim android` reports whether a worktree hits the cache this checkout fills."),
1083
+ code: "fingerprint-config-skipped"
1084
+ };
1085
+ }
1202
1086
  function gitMetadataAt(path) {
1203
1087
  try {
1204
1088
  const git = lstatSync$1(join$1(path, ".git"));
@@ -1244,6 +1128,8 @@ function linkedPackageWithGitIn(nodeModules) {
1244
1128
  async function detectLinkedLibraryGitMetadata(projectRoot, { createFingerprint = expoFingerprint.createFingerprintAsync, platform } = {}) {
1245
1129
  const mainRoot = mainCheckoutProjectRoot(projectRoot);
1246
1130
  if (!hasLinkedPackageWithGit(mainRoot)) return null;
1131
+ const skipped = fingerprintConfigSkipped(mainRoot);
1132
+ if (skipped) return skipped;
1247
1133
  let sources;
1248
1134
  try {
1249
1135
  sources = (await fingerprintProject(mainRoot, {
@@ -1266,7 +1152,7 @@ async function detectLinkedLibraryGitMetadata(projectRoot, { createFingerprint =
1266
1152
  code: "linked-library-git-metadata"
1267
1153
  };
1268
1154
  }
1269
- async function detectFingerprintParity(projectRoot, { createFingerprint = expoFingerprint.createFingerprintAsync, differ = expoFingerprint.diffFingerprints, dirtyFiles = dirtyFingerprintFiles, platform: selectedPlatform } = {}) {
1155
+ async function detectFingerprintParity(projectRoot, { createFingerprint = expoFingerprint.createFingerprintAsync, differ = expoFingerprint.diffFingerprints, dirtyFiles = dirtyFingerprintFiles, platform: selectedPlatform, iosProjectPath = "ios" } = {}) {
1270
1156
  const exec = getExecutor();
1271
1157
  if (exec.runFileQuiet("git", [
1272
1158
  "-C",
@@ -1275,7 +1161,9 @@ async function detectFingerprintParity(projectRoot, { createFingerprint = expoFi
1275
1161
  "--git-dir"
1276
1162
  ], { timeoutMs: 1e4 }) == null) return null;
1277
1163
  if (hasInstalledDependencies(projectRoot)) return null;
1278
- const platform = selectedPlatform ?? (existsSync$1(join$1(projectRoot, "ios")) ? "ios" : existsSync$1(join$1(projectRoot, "android")) ? "android" : void 0);
1164
+ const skipped = fingerprintConfigSkipped(projectRoot);
1165
+ if (skipped) return skipped;
1166
+ const platform = selectedPlatform ?? (existsSync$1(join$1(projectRoot, iosProjectPath)) ? "ios" : existsSync$1(join$1(projectRoot, "android")) ? "android" : void 0);
1279
1167
  let base;
1280
1168
  try {
1281
1169
  base = makeTemporaryDirectory(projectRoot, "stim-parity-");
@@ -1286,6 +1174,8 @@ async function detectFingerprintParity(projectRoot, { createFingerprint = expoFi
1286
1174
  if (exec.runFileQuiet("git", [
1287
1175
  "-C",
1288
1176
  projectRoot,
1177
+ "-c",
1178
+ `core.hooksPath=${join$1(base, "hooks")}`,
1289
1179
  "worktree",
1290
1180
  "add",
1291
1181
  "--detach",
@@ -1299,12 +1189,16 @@ async function detectFingerprintParity(projectRoot, { createFingerprint = expoFi
1299
1189
  return null;
1300
1190
  }
1301
1191
  try {
1192
+ const headSkipped = fingerprintConfigSkipped(worktree);
1193
+ if (headSkipped) return headSkipped;
1302
1194
  const project = await fingerprintProject(projectRoot, {
1303
1195
  platform,
1196
+ iosProjectPath,
1304
1197
  createFingerprint
1305
1198
  });
1306
1199
  const clean = await fingerprintProject(worktree, {
1307
1200
  platform,
1201
+ iosProjectPath,
1308
1202
  createFingerprint
1309
1203
  });
1310
1204
  if (!project || !clean) return null;
@@ -1345,309 +1239,4 @@ async function detectFingerprintParity(projectRoot, { createFingerprint = expoFi
1345
1239
  }
1346
1240
  }
1347
1241
  //#endregion
1348
- //#region src/device-host/agent-driver.ts
1349
- function hostedAgentDriverFinding(driver, apps) {
1350
- if (apps === 0 || driver !== void 0 && driver !== "none") return null;
1351
- return {
1352
- code: "hosted-agent-driver",
1353
- level: "note",
1354
- title: `${apps === 1 ? "A hosted app runs" : `${apps} hosted apps run`} with no agent driver`,
1355
- detail: "The client's coding agent cannot drive these apps because `hosting.agentDriver` is none, so stim-server starts no driver. agent-device confines macOS control to one macos-app lease and iOS or Android control to one device through its daemon policy.",
1356
- fix: "stim settings set hosting.agentDriver agent-device, with agent-device 0.21.22 or later"
1357
- };
1358
- }
1359
- function runningHostedApps() {
1360
- try {
1361
- return readHostedSessions().filter((session) => {
1362
- if (![
1363
- "macos",
1364
- "ios",
1365
- "android"
1366
- ].includes(session.platform) || session.state !== "ready" || !session.appAttempt) return false;
1367
- try {
1368
- return readHostedAppMetadata(session.id, session.appAttempt).state === "installed";
1369
- } catch {
1370
- return false;
1371
- }
1372
- }).length;
1373
- } catch {
1374
- return 0;
1375
- }
1376
- }
1377
- /** Notes installed hosted apps on this Mac while `hosting.agentDriver` starts no driver. */
1378
- function inspectHostedAgentDriver() {
1379
- const hosting = loadConfig$1()?.hosting;
1380
- return hostedAgentDriverFinding(isJsonObject(hosting) ? hosting.agentDriver : void 0, runningHostedApps());
1381
- }
1382
- //#endregion
1383
- //#region src/commands/doctor.ts
1384
- function parseDoctorPlatform(value) {
1385
- if (value === "ios" || value === "android") return value;
1386
- throw new InvalidArgumentError("expected one of: ios, android");
1387
- }
1388
- /**
1389
- * The simulator runtime `stim ios` would build for here: the one `ios.runtime` names, else the workspace's own
1390
- * simulator's, else the one it would create a simulator on.
1391
- */
1392
- function iosTargetRuntime(root) {
1393
- try {
1394
- const settings = resolveSettings({
1395
- projectPath: root,
1396
- gitCommonDir: gitCommonDir(root),
1397
- repoRoot: repoRoot(root) ?? root
1398
- });
1399
- const runtimes = listIosRuntimes();
1400
- const runtime = resolveRuntime(null, settings);
1401
- if (runtime) return runtimes.find((each) => iosRuntimeMatches(each, runtime))?.identifier ?? null;
1402
- const udid = getProject(root)?.platforms?.ios?.deviceUdid;
1403
- const own = typeof udid === "string" ? simulatorRuntime(udid) : null;
1404
- if (own) return own;
1405
- const deviceType = resolveDeviceType(null, settings) ?? void 0;
1406
- return pickDefaultIosCreation([], runtimes, { deviceType })?.runtimeId ?? null;
1407
- } catch {
1408
- return null;
1409
- }
1410
- }
1411
- function doctorTarget(platform) {
1412
- if (platform === "ios") return "iOS";
1413
- if (platform === "android") return "Android";
1414
- return "all platforms";
1415
- }
1416
- function stimVersionLines(report) {
1417
- const resolved = report.resolved ? `${report.resolved.version ?? "unknown version"} at ${report.resolved.path}` : "not found on PATH";
1418
- const paths = `${report.installations.length} distinct PATH install${report.installations.length === 1 ? "" : "s"}`;
1419
- const pathVersions = [...new Set(report.installations.map((entry) => entry.version).filter(Boolean))];
1420
- const versions = pathVersions.length > 1 ? ` (${pathVersions.join(", ")})` : "";
1421
- return [
1422
- phaseLine("version", report.runningVersion),
1423
- phaseLine("resolved", resolved),
1424
- phaseLine("installs", paths + versions)
1425
- ];
1426
- }
1427
- function budgetLines(budget) {
1428
- return budget ? [phaseLine("budget", budgetLine(budget))] : [];
1429
- }
1430
- function doctorSuccessLines(platform, stim, budget = null) {
1431
- const lines = [
1432
- `Doctor (${doctorTarget(platform)})`,
1433
- phaseLine("result", "PASS"),
1434
- phaseLine("findings", "0"),
1435
- ...stimVersionLines(stim),
1436
- ...budgetLines(budget),
1437
- "",
1438
- "Project",
1439
- phaseLine("project", "source checkout, dependencies, local upstream, nested worktrees Watchman would crawl"),
1440
- phaseLine("settings", "every Stim setting type, machine config paths, companions and inert keys")
1441
- ];
1442
- if (platform !== "android") {
1443
- lines.push("", "iOS");
1444
- lines.push(phaseLine("setup", "CocoaPods, warm state, effective Debug simulator architectures"));
1445
- lines.push(phaseLine("caches", "Metro, Xcode compilation, ccache, build provider"));
1446
- lines.push(phaseLine("devices", "remote device, SimSlim profile"));
1447
- }
1448
- if (platform !== "ios") {
1449
- lines.push("", "Android");
1450
- lines.push(phaseLine("setup", "Android SDK, warm state"));
1451
- lines.push(phaseLine("caches", "Metro, Gradle, ccache, build provider"));
1452
- lines.push(phaseLine("devices", "remote device"));
1453
- }
1454
- lines.push("", "Shared");
1455
- lines.push(phaseLine("services", "EAS session"));
1456
- lines.push(phaseLine("storage", "temporary staging and build-cache volume placement"));
1457
- lines.push(phaseLine("fingerprint", "parity when dependencies are absent"));
1458
- lines.push("", "Handled automatically");
1459
- const suppliedCaches = platform === "ios" ? "Metro transform store, Xcode compilation cache" : platform === "android" ? "Metro transform store, Gradle build cache, ccache" : "Metro transform store, Xcode compilation cache, Gradle build cache, ccache";
1460
- lines.push(phaseLine("caches", suppliedCaches));
1461
- lines.push(phaseLine("meaning", "missing project cache settings are healthy"));
1462
- return lines;
1463
- }
1464
- function shadowedStimFinding(report) {
1465
- if (!report.resolvedIsOlder || !report.resolved?.version || !report.highestVersion) return null;
1466
- const newer = [...(compareStimVersions(report.runningVersion, report.resolved.version) ?? 0) > 0 ? [{
1467
- path: report.runningPath,
1468
- version: report.runningVersion
1469
- }] : [], ...report.installations.filter((entry) => entry.version && (compareStimVersions(entry.version, report.resolved?.version ?? "") ?? 0) > 0)].find((entry) => entry.version === report.highestVersion);
1470
- const newerLocation = newer?.path ? ` at ${newer.path}` : "";
1471
- return {
1472
- level: "cost",
1473
- title: "The Stim resolved from PATH is older than another installation",
1474
- detail: `${report.resolved.path} reports ${report.resolved.version}, while ${report.highestVersion} is also installed${newerLocation}. A shell command named stim uses the first executable on PATH, so newer commands and fixes can appear to be missing.`,
1475
- fix: `Update or remove ${report.resolved.path}, or put the ${report.highestVersion} installation earlier on PATH. Then run \`stim doctor\` again.`
1476
- };
1477
- }
1478
- /**
1479
- * Status goes to stderr so `--json --fix` still prints one parseable payload
1480
- * on stdout, and the report that follows shows what the write left.
1481
- */
1482
- function applySandboxFix(root, env = process.env) {
1483
- const harness = detectHarness(env);
1484
- if (harness === "codex") {
1485
- console.error(chalk.yellow("Nothing to apply under Codex."));
1486
- console.error(chalk.dim("Its sandbox is one setting, `sandbox_mode`, with no per-path allowance: the only value that clears Stim is `danger-full-access`, which turns the sandbox off rather than allowing these three. Run Stim with the sandbox off instead, or set it yourself."));
1487
- process.exitCode = 1;
1488
- return;
1489
- }
1490
- if (harness !== "claude-code") {
1491
- console.error(chalk.dim("No sandboxing harness detected, so there is nothing to apply."));
1492
- return;
1493
- }
1494
- const settingsRoot = repoRoot(root) ?? root;
1495
- const stimHome = env.STIM_HOME || "~/.stim";
1496
- const target = claudeLocalSettingsPath(settingsRoot);
1497
- const missing = missingAllowance(allowanceSearchPaths(settingsRoot), stimHome);
1498
- if (missing.length === 0) {
1499
- console.error(chalk.green("Stim is already allowed through this sandbox. Nothing to apply."));
1500
- return;
1501
- }
1502
- const result = applyClaudeAllowance(target, sandboxAllowance(stimHome));
1503
- if (result.status === "refused") {
1504
- console.error(chalk.red(result.reason));
1505
- console.error(chalk.dim(`Nothing was written. Add ${missing.join(", ")} by hand.`));
1506
- process.exitCode = 1;
1507
- return;
1508
- }
1509
- console.error(chalk.green(`${result.status === "created" ? "Wrote" : "Updated"} ${target}`));
1510
- console.error(chalk.dim("Added writes to Stim's state directory, the simulator XPC service, and local port binding. Claude Code reads project settings from the directory a session starts in, so this file only counts for sessions rooted there. Restart the session for it to take effect."));
1511
- }
1512
- function doctorCommand(program, version, inspectVersions = inspectStimVersions, host = process.platform) {
1513
- program.command("doctor").description("Inspect the source checkout and report project state that can make native worktrees slow or invalid. The checkout is left untouched unless --fix is passed; --platform filters native findings. Each run in a React Native or Expo app is recorded in Stim state so guide can say when doctor is due.").option("--json", "print the findings as JSON").option("--platform <platform>", "report shared findings plus only this native platform: ios or android", parseDoctorPlatform).option("--fix", "repair the sandbox allowance when the report names it, and stale Android .cxx configurations in this checkout; add linked worktrees nested in a checkout to its .watchmanconfig ignore_dirs; ask each remote Mac in remote.machines for build and device-host approval. Stop native builds first. Generated CMake output must be ignored and untracked; custom launcher settings and source files are preserved.").action(async (opts) => {
1514
- const root = findProjectRoot(process.cwd());
1515
- if (!root) {
1516
- refuseNoProject({ json: Boolean(opts.json) });
1517
- return;
1518
- }
1519
- if (opts.fix) {
1520
- if (sandboxFinding(repoRoot(root) ?? root)) applySandboxFix(root);
1521
- if (opts.platform !== "ios") try {
1522
- const repair = repairCxxLauncherState(root);
1523
- for (const path of repair.removed) console.error(phaseLine("cache", `removed ${path}; next build reconfigures`));
1524
- for (const { path, reason } of repair.refused) console.error(phaseLine("cache", `kept ${path}: ${reason}`));
1525
- if (repair.refused.length > 0) process.exitCode = 1;
1526
- } catch (error) {
1527
- console.error(phaseLine("cache", `CMake cleanup failed: ${error.message}`));
1528
- process.exitCode = 1;
1529
- }
1530
- const nestedToFix = inspectNestedWorktrees(root);
1531
- const watchedToFix = await watchedCheckouts(nestedToFix.map((entry) => entry.checkout));
1532
- for (const entry of nestedToFix) {
1533
- let result;
1534
- try {
1535
- result = writeIgnoreDirs(entry);
1536
- } catch (error) {
1537
- result = {
1538
- status: "refused",
1539
- reason: `${entry.configPath} could not be written: ${error.message}`
1540
- };
1541
- }
1542
- if (result.status === "refused") {
1543
- console.error(phaseLine("checkout", `kept ${result.reason}; add ${entry.add.join(", ")} to ignore_dirs by hand`));
1544
- process.exitCode = 1;
1545
- } else {
1546
- console.error(phaseLine("checkout", `${result.status} ${entry.configPath}: ignore_dirs ${entry.add.join(", ")}`));
1547
- if (watchedToFix.has(entry.checkout)) console.error(phaseLine("checkout", `Watchman watches ${entry.checkout} and reads the file only when it adds a root: run \`watchman watch-del ${entry.checkout}\`, then restart watchman to free its memory`));
1548
- }
1549
- }
1550
- }
1551
- const stim = await inspectVersions(version);
1552
- const findings = runDoctor(root, {
1553
- xcodeMajor: opts.platform !== "android" && host === "darwin" ? detectXcodeMajor() : null,
1554
- platform: opts.platform,
1555
- host
1556
- });
1557
- const parity = await detectFingerprintParity(root, { platform: opts.platform });
1558
- if (parity) findings.push(parity);
1559
- const linkedGit = await detectLinkedLibraryGitMetadata(root, { platform: opts.platform });
1560
- if (linkedGit) findings.push(linkedGit);
1561
- if (detectHarness()) {
1562
- const sandbox = sandboxFinding(repoRoot(root) ?? root);
1563
- if (sandbox) findings.push(sandbox);
1564
- }
1565
- const shadowed = shadowedStimFinding(stim);
1566
- if (shadowed) findings.push(shadowed);
1567
- const budget = await inspectBudget(root);
1568
- findings.push(...budget.findings);
1569
- const watchman = await inspectWatchmanMemory();
1570
- if (watchman) findings.push(watchman);
1571
- const nestedWorktrees = inspectNestedWorktrees(root);
1572
- const watched = await watchedCheckouts(nestedWorktrees.map((entry) => entry.checkout));
1573
- for (const entry of nestedWorktrees) findings.push(nestedWorktreeFinding(entry, watched.has(entry.checkout)));
1574
- const checksIos = opts.platform !== "android" && host === "darwin";
1575
- const checksAndroid = opts.platform === "android" || opts.platform === void 0 && (existsSync(join(root, "android")) || detectIsExpo(root));
1576
- const checksMacos = host === "darwin" && opts.platform === void 0 && existsSync(join(root, "Package.swift"));
1577
- const offloadTargets = () => [
1578
- ...checksIos ? [{
1579
- platform: "ios",
1580
- local: iosToolchain(root),
1581
- runtime: iosTargetRuntime(root),
1582
- cocoapodsPinned: bundlerPin(root) !== null
1583
- }] : [],
1584
- ...checksAndroid ? [{
1585
- platform: "android",
1586
- local: androidToolchain(),
1587
- requires: androidRequirements(root)
1588
- }] : [],
1589
- ...checksMacos ? [{
1590
- platform: "macos",
1591
- local: macosToolchain()
1592
- }] : []
1593
- ];
1594
- const remoteMachines = await inspectBuildMachines({
1595
- fix: opts.fix === true,
1596
- check: checksIos || checksAndroid || checksMacos ? offloadCheck(root, offloadTargets) : null
1597
- });
1598
- findings.push(...remoteMachines.findings);
1599
- const deviceHosts = await inspectDeviceHostMachines({ fix: opts.fix === true });
1600
- const reported = new Set(remoteMachines.findings.map((each) => each.title));
1601
- findings.push(...deviceHosts.findings.filter((each) => !reported.has(each.title)));
1602
- const agentDriver = inspectHostedAgentDriver();
1603
- if (agentDriver) findings.push(agentDriver);
1604
- const maintenance = maintenanceStatus();
1605
- const maintenanceLines = [
1606
- phaseLine("maintenance.mode", maintenance.mode),
1607
- phaseLine("maintenance log", maintenanceNdjsonFile()),
1608
- phaseLine("last pass", maintenanceLine(maintenance, false) ?? "no pass has run yet")
1609
- ];
1610
- if (opts.json) {
1611
- console.log(JSON.stringify({
1612
- project: root,
1613
- platform: opts.platform ?? null,
1614
- stim,
1615
- budget: budget.report,
1616
- maintenance: {
1617
- ...maintenance,
1618
- logPath: maintenanceNdjsonFile()
1619
- },
1620
- remoteMachines: remoteMachines.machines,
1621
- deviceHosts: deviceHosts.machines,
1622
- findings
1623
- }));
1624
- recordDoctorRun(root, opts.platform, version);
1625
- return;
1626
- }
1627
- if (findings.length === 0) {
1628
- const lines = [...doctorSuccessLines(opts.platform, stim, budget.report), ...maintenanceLines];
1629
- for (const [index, line] of lines.entries()) if (index === 1) console.log(chalk.green(line));
1630
- else if (line && !line.startsWith(" ")) console.log(chalk.bold(line));
1631
- else console.log(chalk.dim(line));
1632
- recordDoctorRun(root, opts.platform, version);
1633
- return;
1634
- }
1635
- const ordered = findings.toSorted((a, b) => a.level === b.level ? 0 : a.level === "cost" ? -1 : 1);
1636
- console.log(chalk.bold(`Doctor (${doctorTarget(opts.platform)})`));
1637
- for (const line of [
1638
- ...stimVersionLines(stim),
1639
- ...budgetLines(budget.report),
1640
- ...maintenanceLines
1641
- ]) console.log(chalk.dim(line));
1642
- for (const f of ordered) {
1643
- const tag = f.level === "cost" ? chalk.yellow("costs time") : chalk.dim("note");
1644
- console.log(`\n${tag} ${chalk.bold(f.title)}`);
1645
- console.log(` ${f.detail}`);
1646
- if (f.fix) console.log(chalk.dim(` -> ${f.fix}`));
1647
- }
1648
- console.log(chalk.dim(`\n${findings.length} finding(s). Fix relevant "costs time" findings before copying the source checkout into a native worktree.`));
1649
- recordDoctorRun(root, opts.platform, version);
1650
- });
1651
- }
1652
- //#endregion
1653
- export { applySandboxFix, doctorCommand as default, doctorSuccessLines, parseDoctorPlatform, shadowedStimFinding };
1242
+ export { repairCxxLauncherState as a, runDoctor as i, detectLinkedLibraryGitMetadata as n, readMachineSettings as o, reactNativeDoctorFindings as r, detectFingerprintParity as t };