stim 1.18.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.
- package/README.md +21 -4
- package/dist/{activity-BxnkDZDi.mjs → activity-BrGh9EAI.mjs} +3 -3
- package/dist/{agent-device-usage-output-CZ66YA4T.mjs → agent-device-usage-output-DVyv_hWj.mjs} +7 -7
- package/dist/android-0vv-OruW.mjs +6 -0
- package/dist/android-Brx6EKvJ.mjs +1258 -0
- package/dist/{android-DC3ZLuw0.mjs → android-BsYNR81W.mjs} +103 -45
- package/dist/{android-CBwCNicO.mjs → android-CK3fpEyZ.mjs} +2 -2
- package/dist/{android-cas-BBFyr9CA.mjs → android-cas-DCAuXryb.mjs} +9 -5
- package/dist/android-cas-compiler.mjs +1 -1
- package/dist/api-run.mjs +159 -8
- package/dist/{api-D0VtFhqk.mjs → api-zuX3ynYN.mjs} +9 -7
- package/dist/api.d.mts +104 -4
- package/dist/api.mjs +1 -1
- package/dist/{app-install-D-PUENug.mjs → app-install-0LgaQRT7.mjs} +39 -18
- package/dist/artifact-CnC0mfov.mjs +807 -0
- package/dist/artifact-DClXJNWx.mjs +772 -0
- package/dist/artifact-lifecycle-DzPpBYvK.mjs +30 -0
- package/dist/{web-DTU0uiWj.mjs → browser-web-CzBmhmgs.mjs} +136 -174
- package/dist/{budget-DIhL2BKx.mjs → budget-BONq9vXY.mjs} +3096 -3244
- package/dist/build-BixRI6sB.mjs +462 -0
- package/dist/build-plan-CP0tckNU.mjs +553 -0
- package/dist/{cache-manifest-BI7ozfmH.mjs → cache-manifest-qA3dT2x2.mjs} +1 -1
- package/dist/cache-manifest.mjs +1 -1
- package/dist/ccache-CjHQ5F8g.mjs +142 -0
- package/dist/{chrome-DJnmnS5q.mjs → chrome-Dp2lMLBe.mjs} +1 -1
- package/dist/{cli-DNJMwADx.mjs → cli-B2R8wH1F.mjs} +20 -20
- package/dist/cli.mjs +1 -1
- package/dist/{client-DzhapoMB.mjs → client-BLy1Pw2V.mjs} +172 -165
- package/dist/collector-run.d.mts +10 -0
- package/dist/collector-run.mjs +7 -7
- package/dist/{command-output-CBtzyIgF.mjs → command-output-BjPN99ZB.mjs} +2 -2
- package/dist/{config-CgYMahz7.mjs → config-D0LhTY3S.mjs} +13 -13
- package/dist/{created-devices-DKbeWsiK.mjs → created-devices-HU_DAb_Q.mjs} +7 -6
- package/dist/{dependency-state-xKoOypBm.mjs → dependency-state-B2m8eqsS.mjs} +2 -2
- package/dist/{detached-entry-BLfcgc4O.mjs → detached-entry-BXq4fIl-.mjs} +1 -1
- package/dist/{dev-client-CLnIH02x.mjs → dev-client-ClATaJoH.mjs} +2 -2
- package/dist/{device-D7oCHGWi.mjs → device-B7lsC0K1.mjs} +8 -8
- package/dist/device-DVCwAFlX.mjs +727 -0
- package/dist/{device-capacity-EUOOYr8T.mjs → device-capacity-Ca_ke1LE.mjs} +25 -15
- package/dist/device-host-worker.mjs +38 -18
- package/dist/{device-ios-6MewKKnc.mjs → device-ios-CRUqPxKw.mjs} +12 -11
- package/dist/{device-lease-Co-EwifR.mjs → device-lease-DHOBXGgS.mjs} +135 -22
- package/dist/{device-lease-run-w1xhCqIW.mjs → device-lease-run-CvDeHTkG.mjs} +4 -4
- package/dist/{device-pool-F5WvRPtD.mjs → device-pool-L2-5GxMy.mjs} +3 -3
- package/dist/{device-remote-tHbd1fKy.mjs → device-remote-Bmg42063.mjs} +17 -69
- package/dist/doctor-C2MSuMMg.mjs +515 -0
- package/dist/{doctor-B9ckYUoU.mjs → doctor-DPAqhVCK.mjs} +334 -721
- package/dist/doctor-watchman-CB4NnVZm.mjs +260 -0
- package/dist/{simslim-EHBQ2R4G.mjs → eas-build-Dw_ec2Km.mjs} +6 -152
- package/dist/{stim-installations-aR_WsNOA.mjs → eas-session-ledger-DZlC-Fq3.mjs} +57 -4
- package/dist/{error-diagnostics-BRXOqDzG.mjs → error-diagnostics-Du7I_GuY.mjs} +17 -12
- package/dist/errors-DzkMYh8Q.mjs +205 -0
- package/dist/{exec-DWhCZDGm.mjs → exec-B81Qodjn.mjs} +69 -15
- package/dist/{gc-Dh6mXOKC.mjs → gc-PWp34zGb.mjs} +23 -22
- package/dist/{gradle-C6GKnunQ.mjs → gradle-C2OEKCCI.mjs} +50 -125
- package/dist/{guide-tm6igAaa.mjs → guide-Dai9lphv.mjs} +862 -538
- package/dist/{hosted-android-Bv285t_t.mjs → hosted-android-DQ-Agsd8.mjs} +5 -5
- package/dist/{hosted-client-C-rMtppF.mjs → hosted-client-Io01s5By.mjs} +9 -3
- package/dist/{hosted-ios-D5mNmtj4.mjs → hosted-ios-DIxFnYp-.mjs} +3 -3
- package/dist/{hosted-logs-DN8Iz4mJ.mjs → hosted-logs-DP-ABU_M.mjs} +8 -8
- package/dist/{hosted-macos-B-wEjlwG.mjs → hosted-macos-Aziw9T_9.mjs} +7 -6
- package/dist/{hosted-native-DeeOG8xZ.mjs → hosted-native-BdAQi21M.mjs} +23 -16
- package/dist/{idle-shutdown-jjtxAn5C.mjs → idle-shutdown-DdMqoSUB.mjs} +7 -7
- package/dist/ios-BZ9dg2Fr.mjs +9 -0
- package/dist/{ios-B3SbOWdw.mjs → ios-CXLIUxXJ.mjs} +180 -24
- package/dist/ios-CZuv6gRv.mjs +1112 -0
- package/dist/{ios-Bvbf0SGY.mjs → ios-VGvNdsv2.mjs} +1 -1
- package/dist/{ios-device-Bn7jWDB1.mjs → ios-device-B1B14xND.mjs} +1 -1
- package/dist/{ios-device-vOaByMhw.mjs → ios-device-BreZOpM0.mjs} +3 -3
- package/dist/ios-project-B9drt_aY.mjs +197 -0
- package/dist/{ios-state-B2pES6Vs.mjs → ios-state-CjrL2qv-.mjs} +1 -1
- package/dist/js-swap-C8icNYIY.mjs +432 -0
- package/dist/launch-DxH0THLP.mjs +745 -0
- package/dist/launch-YrbqaIFj.mjs +1530 -0
- package/dist/{launch-verify-hskNcWkZ.mjs → launch-verify-BzRdbuO1.mjs} +26 -7
- package/dist/{logs-1Ylc1Ri6.mjs → logs-DHaRNfg7.mjs} +11 -11
- package/dist/macos-C0iljhsN.mjs +2 -0
- package/dist/{macos-DF6C7wUU.mjs → macos-V_3Ui7g4.mjs} +125 -155
- package/dist/macos-run.mjs +1 -1
- package/dist/maintenance-run.mjs +13 -10
- package/dist/{metro-Dh8T4Ytd.mjs → metro-D_j9sN-B.mjs} +209 -43
- package/dist/{metro-gateway-CEUw9RMH.mjs → metro-gateway-BBJldOdi.mjs} +2 -2
- package/dist/miss-reason-BPAzZS0r.mjs +238 -0
- package/dist/{named-ports-BFIz1Shz.mjs → named-ports-C2lvoPsb.mjs} +12 -5
- package/dist/native-android-lHzaew7r.mjs +226 -0
- package/dist/native-gradle-inputs-BQnIdAlL.mjs +356 -0
- package/dist/{native-runtime-D_603JiC.mjs → native-runtime-BRmWL7ru.mjs} +9 -8
- package/dist/native-xcode-inputs-eutS19jU.mjs +307 -0
- package/dist/native-xcode-ios-DP3cHLwu.mjs +434 -0
- package/dist/{ndjson-LGZAMsqa.mjs → ndjson-GOnbyvso.mjs} +6 -6
- package/dist/offload-worker.d.mts +12 -6
- package/dist/offload-worker.mjs +409 -73
- package/dist/{ownership-D6Se7gg_.mjs → ownership-DXzMMSx6.mjs} +2 -2
- package/dist/{placement-log-BcPhRwR4.mjs → placement-log-reFbjqEr.mjs} +7 -3
- package/dist/plan-BDk2AeY_.mjs +260 -0
- package/dist/plan-placement-D_YYlE6N.mjs +73 -0
- package/dist/{ports-BLTtfBq-.mjs → ports-jzvAJXXL.mjs} +3 -3
- package/dist/{prebuild-juCyreaR.mjs → prebuild-Bw44QxVh.mjs} +4 -4
- package/dist/{preview-D5V07Nrl.mjs → preview-BaBJ72Al.mjs} +51 -34
- package/dist/project-B8_duAzW.mjs +1067 -0
- package/dist/project-plan-BPwbLwQu.mjs +10 -0
- package/dist/{pull-request-u4vp7zmK.mjs → pull-request-vOhLNmIb.mjs} +1 -1
- package/dist/pull-requests.mjs +1 -1
- package/dist/react-native-android-chm1DMlA.mjs +921 -0
- package/dist/react-native-build-oAw6gQeW.mjs +239 -0
- package/dist/react-native-doctor-DYXoh-6I.mjs +78 -0
- package/dist/react-native-ios-SCTK_CT5.mjs +595 -0
- package/dist/{recordings-BAWltsVt.mjs → recordings-Bk-VivUV.mjs} +1 -1
- package/dist/{reload-DvyWzVjJ.mjs → reload-D34GkY78.mjs} +17 -14
- package/dist/remote-BU9UyhCJ.mjs +192 -0
- package/dist/{remote-cache-BGg7aGIZ.mjs → remote-cache-Bsts6f6Y.mjs} +5 -5
- package/dist/result-BDoRk1k3.mjs +186 -0
- package/dist/{run-vhtDgpHO.mjs → run-qvff78hv.mjs} +3 -3
- package/dist/{server-bare-B9E7S1aR.mjs → server-bare-dWjAarki.mjs} +6 -6
- package/dist/server-command-7XIKlWtO.mjs +69 -0
- package/dist/{server-expo-DoBHhuzK.mjs → server-expo-CsYu2aKL.mjs} +107 -82
- package/dist/server-expo-DV43vep4.mjs +2 -0
- package/dist/settings-Bbztm4oV.mjs +1503 -0
- package/dist/{settings-Byp2AQ_E.mjs → settings-CT3E71-i.mjs} +6 -7
- package/dist/{state-lKh8shBp.d.mts → settings-vk3mJGSm.d.mts} +4 -2
- package/dist/settings.schema.json +156 -6
- package/dist/simslim-ChMfe8rg.mjs +149 -0
- package/dist/{slot-launch-C1nqh4mu.mjs → slot-launch-B9hn96wd.mjs} +6 -6
- package/dist/{stage-g_WqWeaE.mjs → stage-BNKKdDNc.mjs} +6 -3
- package/dist/start-CbQCSnXQ.mjs +2 -0
- package/dist/{start-D-IeOyoK.mjs → start-CdvcTaXr.mjs} +113 -77
- package/dist/{state-xnqpx3AD.mjs → state-Boo0IMJq.mjs} +4 -2
- package/dist/{state-BG-aAk4k.mjs → state-DI84A-uu.mjs} +29 -10
- package/dist/{stats-DY6ekz8F.mjs → stats-BIIDrjA8.mjs} +6 -6
- package/dist/{status-BEeL6IKz.mjs → status-BDGHwkCs.mjs} +3 -1
- package/dist/{status-DCjZeE-K.mjs → status-DARMkc9q.mjs} +80 -294
- package/dist/status-watch-DvmI3SmN.mjs +296 -0
- package/dist/{stim-desktop-Behws_fi.mjs → stim-desktop-8GQX2OV8.mjs} +1 -1
- package/dist/{stop-DIJtD2GL.mjs → stop-CCZS-C9m.mjs} +2 -2
- package/dist/stop-WQPQHLuy.mjs +72 -0
- package/dist/{stop-BTjs3xbc.mjs → stop-iNemHsJC.mjs} +81 -33
- package/dist/supervisor-run.d.mts +7 -2
- package/dist/supervisor-run.mjs +32 -20
- package/dist/{support-Bwvbrq5-.mjs → support-CnE_SIjt.mjs} +17 -7
- package/dist/{support-BaayeYCf.mjs → support-DCco5fut.mjs} +61 -8
- package/dist/swiftpm-macos-afeNUExW.mjs +119 -0
- package/dist/{native-run-B06FkmZx.mjs → tailnet-CoJUb2tB.mjs} +113 -6
- package/dist/toolchain-BIupIro1.mjs +2 -0
- package/dist/{toolchain-Bnyd_K3A.mjs → toolchain-DqyDXs30.mjs} +225 -315
- package/dist/{trigger-CJMhwXe8.mjs → trigger-C0MWBi8O.mjs} +3 -3
- package/dist/trigger-SHLOWJ4O.mjs +2 -0
- package/dist/{warm-progress-CjB-MP6H.mjs → warm-progress-C3ASD6z5.mjs} +134 -37
- package/dist/{status-Dcr0Qexd.mjs → watchman-CQl51YZu.mjs} +75 -7
- package/dist/web-B3eldHFH.mjs +123 -0
- package/dist/web-BLXpOe6b.mjs +2 -0
- package/dist/web-run.mjs +10 -10
- package/dist/{workspace-state-C59dqG-Z.mjs → workspace-state-CSIFuuDa.mjs} +33 -33
- package/dist/{workspaces-xXItD8Dj.mjs → workspaces-D2xSHBDY.mjs} +1297 -1211
- package/dist/{worktree-BSYlofkq.mjs → worktree-BxDqaTev.mjs} +102 -63
- package/dist/worktree-q7_OZNY0.mjs +2 -0
- package/package.json +8 -6
- package/shim/bundle-response.cjs +2 -1
- package/shim/bundle-response.d.cts +1 -1
- package/shim/expo-metro-config.cjs +1 -1
- package/shim/native-android.gradle +101 -0
- package/dist/android-BKA16DTf.mjs +0 -4
- package/dist/android-BvwuMUJ9.mjs +0 -3572
- package/dist/guide-status-Bhf9SJXc.mjs +0 -125
- package/dist/ios-CPdkoKaa.mjs +0 -3953
- package/dist/ios-DNpdygiv.mjs +0 -7
- package/dist/macos-Ck7OBMNY.mjs +0 -2
- package/dist/metro-store-BAHFcJm6.mjs +0 -106
- package/dist/plan-placement-CtxkCO54.mjs +0 -1859
- package/dist/project-C3ChRLZy.mjs +0 -212
- package/dist/projects-DD7dBTCj.mjs +0 -92
- package/dist/server-expo-Db9HBUoa.mjs +0 -2
- package/dist/settings-CQyMElEP.mjs +0 -701
- package/dist/start-NTc1Ifqs.mjs +0 -2
- package/dist/stop-BGhKMOuH.mjs +0 -48
- package/dist/trigger-DWAq9hkw.mjs +0 -2
- package/dist/web-B6_bFDn7.mjs +0 -2
- package/dist/worktree-BJfnU6r5.mjs +0 -2
- package/dist/worktree-CsKYexJM.mjs +0 -753
|
@@ -1,246 +1,143 @@
|
|
|
1
|
-
import { t as getExecutor } from "./exec-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { i as
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import { r as maintenanceStatus, t as maintenanceLine } from "./status-BEeL6IKz.mjs";
|
|
26
|
-
import { n as recordDoctorRun } from "./guide-status-Bhf9SJXc.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,
|
|
31
|
-
import
|
|
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/
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
|
224
|
-
fix:
|
|
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: "
|
|
125
|
+
level: "note",
|
|
236
126
|
title,
|
|
237
|
-
detail
|
|
238
|
-
fix:
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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,
|
|
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 = [
|
|
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(
|
|
371
|
+
function selectedCompilerCache(settings) {
|
|
372
|
+
if (!settings) return null;
|
|
472
373
|
try {
|
|
473
374
|
return resolveAndroidCompilerCache({
|
|
474
|
-
optimizations:
|
|
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(
|
|
392
|
+
if (selectedCompilerCache(settings) !== "ccache") return result;
|
|
492
393
|
if (!getExecutor().findExecutable("ccache")) return result;
|
|
493
|
-
const appOverrides = declaredLauncher(
|
|
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(
|
|
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
|
|
818
|
-
const
|
|
819
|
-
const
|
|
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(
|
|
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
|
|
947
|
-
|
|
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,
|
|
1005
|
-
const
|
|
1006
|
-
const
|
|
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
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
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
|
-
|
|
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: () =>
|
|
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
|
-
|
|
1122
|
-
|
|
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,14 +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
|
|
1146
|
-
|
|
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
|
-
hasLocalProperties: existsSync$1(join$1(androidDir, "local.properties"))
|
|
1028
|
+
hasLocalProperties: existsSync$1(join$1(androidDir, "local.properties")),
|
|
1029
|
+
localPropertiesPath: `${relative$1(projectRoot, androidDir) || "."}/local.properties`
|
|
1153
1030
|
});
|
|
1154
1031
|
if (!refusal) return null;
|
|
1155
1032
|
return {
|
|
@@ -1157,13 +1034,13 @@ function checkAndroidSdk(projectRoot, platform) {
|
|
|
1157
1034
|
...finding("cost", "No Android SDK was found", `${refusal.reason} \`stim android\` refuses with ${refusal.code} before Gradle runs.`, refusal.remedy)
|
|
1158
1035
|
};
|
|
1159
1036
|
}
|
|
1160
|
-
function androidCcacheFindings(projectRoot, platform, lookupCcache) {
|
|
1161
|
-
if (platform !== "android" && !
|
|
1037
|
+
function androidCcacheFindings(projectRoot, platform, lookupCcache, layout) {
|
|
1038
|
+
if (platform !== "android" && !hasAndroidProject(layout)) return [];
|
|
1162
1039
|
const onPath = lookupCcache ? lookupCcache() : ccacheIsOnPath();
|
|
1163
1040
|
let cxx;
|
|
1164
1041
|
try {
|
|
1165
1042
|
cxx = checkCxxCompilerLauncher({
|
|
1166
|
-
states: readCxxLauncherStates(projectRoot),
|
|
1043
|
+
states: readCxxLauncherStates(projectRoot, layout),
|
|
1167
1044
|
ccacheOnPath: onPath
|
|
1168
1045
|
});
|
|
1169
1046
|
} catch (error) {
|
|
@@ -1198,6 +1075,14 @@ function checkFingerprintParity({ projectHash, worktreeHash, changed = [], dirty
|
|
|
1198
1075
|
const names = changed.slice(0, 3).join(", ");
|
|
1199
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.");
|
|
1200
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
|
+
}
|
|
1201
1086
|
function gitMetadataAt(path) {
|
|
1202
1087
|
try {
|
|
1203
1088
|
const git = lstatSync$1(join$1(path, ".git"));
|
|
@@ -1243,6 +1128,8 @@ function linkedPackageWithGitIn(nodeModules) {
|
|
|
1243
1128
|
async function detectLinkedLibraryGitMetadata(projectRoot, { createFingerprint = expoFingerprint.createFingerprintAsync, platform } = {}) {
|
|
1244
1129
|
const mainRoot = mainCheckoutProjectRoot(projectRoot);
|
|
1245
1130
|
if (!hasLinkedPackageWithGit(mainRoot)) return null;
|
|
1131
|
+
const skipped = fingerprintConfigSkipped(mainRoot);
|
|
1132
|
+
if (skipped) return skipped;
|
|
1246
1133
|
let sources;
|
|
1247
1134
|
try {
|
|
1248
1135
|
sources = (await fingerprintProject(mainRoot, {
|
|
@@ -1265,7 +1152,7 @@ async function detectLinkedLibraryGitMetadata(projectRoot, { createFingerprint =
|
|
|
1265
1152
|
code: "linked-library-git-metadata"
|
|
1266
1153
|
};
|
|
1267
1154
|
}
|
|
1268
|
-
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" } = {}) {
|
|
1269
1156
|
const exec = getExecutor();
|
|
1270
1157
|
if (exec.runFileQuiet("git", [
|
|
1271
1158
|
"-C",
|
|
@@ -1274,7 +1161,9 @@ async function detectFingerprintParity(projectRoot, { createFingerprint = expoFi
|
|
|
1274
1161
|
"--git-dir"
|
|
1275
1162
|
], { timeoutMs: 1e4 }) == null) return null;
|
|
1276
1163
|
if (hasInstalledDependencies(projectRoot)) return null;
|
|
1277
|
-
const
|
|
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);
|
|
1278
1167
|
let base;
|
|
1279
1168
|
try {
|
|
1280
1169
|
base = makeTemporaryDirectory(projectRoot, "stim-parity-");
|
|
@@ -1285,6 +1174,8 @@ async function detectFingerprintParity(projectRoot, { createFingerprint = expoFi
|
|
|
1285
1174
|
if (exec.runFileQuiet("git", [
|
|
1286
1175
|
"-C",
|
|
1287
1176
|
projectRoot,
|
|
1177
|
+
"-c",
|
|
1178
|
+
`core.hooksPath=${join$1(base, "hooks")}`,
|
|
1288
1179
|
"worktree",
|
|
1289
1180
|
"add",
|
|
1290
1181
|
"--detach",
|
|
@@ -1298,12 +1189,16 @@ async function detectFingerprintParity(projectRoot, { createFingerprint = expoFi
|
|
|
1298
1189
|
return null;
|
|
1299
1190
|
}
|
|
1300
1191
|
try {
|
|
1192
|
+
const headSkipped = fingerprintConfigSkipped(worktree);
|
|
1193
|
+
if (headSkipped) return headSkipped;
|
|
1301
1194
|
const project = await fingerprintProject(projectRoot, {
|
|
1302
1195
|
platform,
|
|
1196
|
+
iosProjectPath,
|
|
1303
1197
|
createFingerprint
|
|
1304
1198
|
});
|
|
1305
1199
|
const clean = await fingerprintProject(worktree, {
|
|
1306
1200
|
platform,
|
|
1201
|
+
iosProjectPath,
|
|
1307
1202
|
createFingerprint
|
|
1308
1203
|
});
|
|
1309
1204
|
if (!project || !clean) return null;
|
|
@@ -1344,286 +1239,4 @@ async function detectFingerprintParity(projectRoot, { createFingerprint = expoFi
|
|
|
1344
1239
|
}
|
|
1345
1240
|
}
|
|
1346
1241
|
//#endregion
|
|
1347
|
-
|
|
1348
|
-
function hostedAgentDriverFinding(driver, apps) {
|
|
1349
|
-
if (apps === 0 || driver !== void 0 && driver !== "none") return null;
|
|
1350
|
-
return {
|
|
1351
|
-
code: "hosted-agent-driver",
|
|
1352
|
-
level: "note",
|
|
1353
|
-
title: `${apps === 1 ? "A hosted app runs" : `${apps} hosted apps run`} with no agent driver`,
|
|
1354
|
-
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.",
|
|
1355
|
-
fix: "stim settings set hosting.agentDriver agent-device, with agent-device 0.21.22 or later"
|
|
1356
|
-
};
|
|
1357
|
-
}
|
|
1358
|
-
function runningHostedApps() {
|
|
1359
|
-
try {
|
|
1360
|
-
return readHostedSessions().filter((session) => {
|
|
1361
|
-
if (![
|
|
1362
|
-
"macos",
|
|
1363
|
-
"ios",
|
|
1364
|
-
"android"
|
|
1365
|
-
].includes(session.platform) || session.state !== "ready" || !session.appAttempt) return false;
|
|
1366
|
-
try {
|
|
1367
|
-
return readHostedAppMetadata(session.id, session.appAttempt).state === "installed";
|
|
1368
|
-
} catch {
|
|
1369
|
-
return false;
|
|
1370
|
-
}
|
|
1371
|
-
}).length;
|
|
1372
|
-
} catch {
|
|
1373
|
-
return 0;
|
|
1374
|
-
}
|
|
1375
|
-
}
|
|
1376
|
-
/** Notes installed hosted apps on this Mac while `hosting.agentDriver` starts no driver. */
|
|
1377
|
-
function inspectHostedAgentDriver() {
|
|
1378
|
-
const hosting = loadConfig$1()?.hosting;
|
|
1379
|
-
return hostedAgentDriverFinding(isJsonObject(hosting) ? hosting.agentDriver : void 0, runningHostedApps());
|
|
1380
|
-
}
|
|
1381
|
-
//#endregion
|
|
1382
|
-
//#region src/commands/doctor.ts
|
|
1383
|
-
function parseDoctorPlatform(value) {
|
|
1384
|
-
if (value === "ios" || value === "android") return value;
|
|
1385
|
-
throw new InvalidArgumentError("expected one of: ios, android");
|
|
1386
|
-
}
|
|
1387
|
-
/**
|
|
1388
|
-
* The simulator runtime `stim ios` would build for here: the one `ios.runtime` names, else the workspace's own
|
|
1389
|
-
* simulator's, else the one it would create a simulator on.
|
|
1390
|
-
*/
|
|
1391
|
-
function iosTargetRuntime(root) {
|
|
1392
|
-
try {
|
|
1393
|
-
const settings = resolveSettings({
|
|
1394
|
-
projectPath: root,
|
|
1395
|
-
gitCommonDir: gitCommonDir(root),
|
|
1396
|
-
repoRoot: repoRoot(root) ?? root
|
|
1397
|
-
});
|
|
1398
|
-
const runtimes = listIosRuntimes();
|
|
1399
|
-
const runtime = resolveRuntime(null, settings);
|
|
1400
|
-
if (runtime) return runtimes.find((each) => iosRuntimeMatches(each, runtime))?.identifier ?? null;
|
|
1401
|
-
const udid = getProject(root)?.platforms?.ios?.deviceUdid;
|
|
1402
|
-
const own = typeof udid === "string" ? simulatorRuntime(udid) : null;
|
|
1403
|
-
if (own) return own;
|
|
1404
|
-
const deviceType = resolveDeviceType(null, settings) ?? void 0;
|
|
1405
|
-
return pickDefaultIosCreation([], runtimes, { deviceType })?.runtimeId ?? null;
|
|
1406
|
-
} catch {
|
|
1407
|
-
return null;
|
|
1408
|
-
}
|
|
1409
|
-
}
|
|
1410
|
-
function doctorTarget(platform) {
|
|
1411
|
-
if (platform === "ios") return "iOS";
|
|
1412
|
-
if (platform === "android") return "Android";
|
|
1413
|
-
return "all platforms";
|
|
1414
|
-
}
|
|
1415
|
-
function stimVersionLines(report) {
|
|
1416
|
-
const resolved = report.resolved ? `${report.resolved.version ?? "unknown version"} at ${report.resolved.path}` : "not found on PATH";
|
|
1417
|
-
const paths = `${report.installations.length} distinct PATH install${report.installations.length === 1 ? "" : "s"}`;
|
|
1418
|
-
const pathVersions = [...new Set(report.installations.map((entry) => entry.version).filter(Boolean))];
|
|
1419
|
-
const versions = pathVersions.length > 1 ? ` (${pathVersions.join(", ")})` : "";
|
|
1420
|
-
return [
|
|
1421
|
-
phaseLine("version", report.runningVersion),
|
|
1422
|
-
phaseLine("resolved", resolved),
|
|
1423
|
-
phaseLine("installs", paths + versions)
|
|
1424
|
-
];
|
|
1425
|
-
}
|
|
1426
|
-
function budgetLines(budget) {
|
|
1427
|
-
return budget ? [phaseLine("budget", budgetLine(budget))] : [];
|
|
1428
|
-
}
|
|
1429
|
-
function doctorSuccessLines(platform, stim, budget = null) {
|
|
1430
|
-
const lines = [
|
|
1431
|
-
`Doctor (${doctorTarget(platform)})`,
|
|
1432
|
-
phaseLine("result", "PASS"),
|
|
1433
|
-
phaseLine("findings", "0"),
|
|
1434
|
-
...stimVersionLines(stim),
|
|
1435
|
-
...budgetLines(budget),
|
|
1436
|
-
"",
|
|
1437
|
-
"Project",
|
|
1438
|
-
phaseLine("project", "source checkout, dependencies, local upstream"),
|
|
1439
|
-
phaseLine("settings", "every Stim setting type, machine config paths, companions and inert keys")
|
|
1440
|
-
];
|
|
1441
|
-
if (platform !== "android") {
|
|
1442
|
-
lines.push("", "iOS");
|
|
1443
|
-
lines.push(phaseLine("setup", "CocoaPods, warm state, effective Debug simulator architectures"));
|
|
1444
|
-
lines.push(phaseLine("caches", "Metro, Xcode compilation, ccache, build provider"));
|
|
1445
|
-
lines.push(phaseLine("devices", "remote device, SimSlim profile"));
|
|
1446
|
-
}
|
|
1447
|
-
if (platform !== "ios") {
|
|
1448
|
-
lines.push("", "Android");
|
|
1449
|
-
lines.push(phaseLine("setup", "Android SDK, warm state"));
|
|
1450
|
-
lines.push(phaseLine("caches", "Metro, Gradle, ccache, build provider"));
|
|
1451
|
-
lines.push(phaseLine("devices", "remote device"));
|
|
1452
|
-
}
|
|
1453
|
-
lines.push("", "Shared");
|
|
1454
|
-
lines.push(phaseLine("services", "EAS session"));
|
|
1455
|
-
lines.push(phaseLine("storage", "temporary staging and build-cache volume placement"));
|
|
1456
|
-
lines.push(phaseLine("fingerprint", "parity when dependencies are absent"));
|
|
1457
|
-
lines.push("", "Handled automatically");
|
|
1458
|
-
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";
|
|
1459
|
-
lines.push(phaseLine("caches", suppliedCaches));
|
|
1460
|
-
lines.push(phaseLine("meaning", "missing project cache settings are healthy"));
|
|
1461
|
-
return lines;
|
|
1462
|
-
}
|
|
1463
|
-
function shadowedStimFinding(report) {
|
|
1464
|
-
if (!report.resolvedIsOlder || !report.resolved?.version || !report.highestVersion) return null;
|
|
1465
|
-
const newer = [...(compareStimVersions(report.runningVersion, report.resolved.version) ?? 0) > 0 ? [{
|
|
1466
|
-
path: report.runningPath,
|
|
1467
|
-
version: report.runningVersion
|
|
1468
|
-
}] : [], ...report.installations.filter((entry) => entry.version && (compareStimVersions(entry.version, report.resolved?.version ?? "") ?? 0) > 0)].find((entry) => entry.version === report.highestVersion);
|
|
1469
|
-
const newerLocation = newer?.path ? ` at ${newer.path}` : "";
|
|
1470
|
-
return {
|
|
1471
|
-
level: "cost",
|
|
1472
|
-
title: "The Stim resolved from PATH is older than another installation",
|
|
1473
|
-
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.`,
|
|
1474
|
-
fix: `Update or remove ${report.resolved.path}, or put the ${report.highestVersion} installation earlier on PATH. Then run \`stim doctor\` again.`
|
|
1475
|
-
};
|
|
1476
|
-
}
|
|
1477
|
-
/**
|
|
1478
|
-
* Status goes to stderr so `--json --fix` still prints one parseable payload
|
|
1479
|
-
* on stdout, and the report that follows shows what the write left.
|
|
1480
|
-
*/
|
|
1481
|
-
function applySandboxFix(root, env = process.env) {
|
|
1482
|
-
const harness = detectHarness(env);
|
|
1483
|
-
if (harness === "codex") {
|
|
1484
|
-
console.error(chalk.yellow("Nothing to apply under Codex."));
|
|
1485
|
-
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."));
|
|
1486
|
-
process.exitCode = 1;
|
|
1487
|
-
return;
|
|
1488
|
-
}
|
|
1489
|
-
if (harness !== "claude-code") {
|
|
1490
|
-
console.error(chalk.dim("No sandboxing harness detected, so there is nothing to apply."));
|
|
1491
|
-
return;
|
|
1492
|
-
}
|
|
1493
|
-
const settingsRoot = repoRoot(root) ?? root;
|
|
1494
|
-
const stimHome = env.STIM_HOME || "~/.stim";
|
|
1495
|
-
const target = claudeLocalSettingsPath(settingsRoot);
|
|
1496
|
-
const missing = missingAllowance(allowanceSearchPaths(settingsRoot), stimHome);
|
|
1497
|
-
if (missing.length === 0) {
|
|
1498
|
-
console.error(chalk.green("Stim is already allowed through this sandbox. Nothing to apply."));
|
|
1499
|
-
return;
|
|
1500
|
-
}
|
|
1501
|
-
const result = applyClaudeAllowance(target, sandboxAllowance(stimHome));
|
|
1502
|
-
if (result.status === "refused") {
|
|
1503
|
-
console.error(chalk.red(result.reason));
|
|
1504
|
-
console.error(chalk.dim(`Nothing was written. Add ${missing.join(", ")} by hand.`));
|
|
1505
|
-
process.exitCode = 1;
|
|
1506
|
-
return;
|
|
1507
|
-
}
|
|
1508
|
-
console.error(chalk.green(`${result.status === "created" ? "Wrote" : "Updated"} ${target}`));
|
|
1509
|
-
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."));
|
|
1510
|
-
}
|
|
1511
|
-
function doctorCommand(program, version, inspectVersions = inspectStimVersions, host = process.platform) {
|
|
1512
|
-
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; 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) => {
|
|
1513
|
-
const root = findProjectRoot$1(process.cwd());
|
|
1514
|
-
if (!root) {
|
|
1515
|
-
refuseNoProject({ json: Boolean(opts.json) });
|
|
1516
|
-
return;
|
|
1517
|
-
}
|
|
1518
|
-
if (opts.fix) {
|
|
1519
|
-
if (sandboxFinding(repoRoot(root) ?? root)) applySandboxFix(root);
|
|
1520
|
-
if (opts.platform !== "ios") try {
|
|
1521
|
-
const repair = repairCxxLauncherState(root);
|
|
1522
|
-
for (const path of repair.removed) console.error(phaseLine("cache", `removed ${path}; next build reconfigures`));
|
|
1523
|
-
for (const { path, reason } of repair.refused) console.error(phaseLine("cache", `kept ${path}: ${reason}`));
|
|
1524
|
-
if (repair.refused.length > 0) process.exitCode = 1;
|
|
1525
|
-
} catch (error) {
|
|
1526
|
-
console.error(phaseLine("cache", `CMake cleanup failed: ${error.message}`));
|
|
1527
|
-
process.exitCode = 1;
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
const stim = await inspectVersions(version);
|
|
1531
|
-
const findings = runDoctor(root, {
|
|
1532
|
-
xcodeMajor: opts.platform !== "android" && host === "darwin" ? detectXcodeMajor() : null,
|
|
1533
|
-
platform: opts.platform,
|
|
1534
|
-
host
|
|
1535
|
-
});
|
|
1536
|
-
const parity = await detectFingerprintParity(root, { platform: opts.platform });
|
|
1537
|
-
if (parity) findings.push(parity);
|
|
1538
|
-
const linkedGit = await detectLinkedLibraryGitMetadata(root, { platform: opts.platform });
|
|
1539
|
-
if (linkedGit) findings.push(linkedGit);
|
|
1540
|
-
if (detectHarness()) {
|
|
1541
|
-
const sandbox = sandboxFinding(repoRoot(root) ?? root);
|
|
1542
|
-
if (sandbox) findings.push(sandbox);
|
|
1543
|
-
}
|
|
1544
|
-
const shadowed = shadowedStimFinding(stim);
|
|
1545
|
-
if (shadowed) findings.push(shadowed);
|
|
1546
|
-
const budget = await inspectBudget(root);
|
|
1547
|
-
findings.push(...budget.findings);
|
|
1548
|
-
const watchman = await inspectWatchmanMemory();
|
|
1549
|
-
if (watchman) findings.push(watchman);
|
|
1550
|
-
const checksIos = opts.platform !== "android" && host === "darwin";
|
|
1551
|
-
const checksAndroid = opts.platform === "android" || opts.platform === void 0 && (existsSync(join(root, "android")) || detectIsExpo(root));
|
|
1552
|
-
const checksMacos = host === "darwin" && opts.platform === void 0 && existsSync(join(root, "Package.swift"));
|
|
1553
|
-
const offloadTargets = () => [
|
|
1554
|
-
...checksIos ? [{
|
|
1555
|
-
platform: "ios",
|
|
1556
|
-
local: iosToolchain(root),
|
|
1557
|
-
runtime: iosTargetRuntime(root),
|
|
1558
|
-
cocoapodsPinned: bundlerPin(root) !== null
|
|
1559
|
-
}] : [],
|
|
1560
|
-
...checksAndroid ? [{
|
|
1561
|
-
platform: "android",
|
|
1562
|
-
local: androidToolchain(),
|
|
1563
|
-
requires: androidRequirements(root)
|
|
1564
|
-
}] : [],
|
|
1565
|
-
...checksMacos ? [{
|
|
1566
|
-
platform: "macos",
|
|
1567
|
-
local: macosToolchain()
|
|
1568
|
-
}] : []
|
|
1569
|
-
];
|
|
1570
|
-
const remoteMachines = await inspectBuildMachines({
|
|
1571
|
-
fix: opts.fix === true,
|
|
1572
|
-
check: checksIos || checksAndroid || checksMacos ? offloadCheck(root, offloadTargets) : null
|
|
1573
|
-
});
|
|
1574
|
-
findings.push(...remoteMachines.findings);
|
|
1575
|
-
const deviceHosts = await inspectDeviceHostMachines({ fix: opts.fix === true });
|
|
1576
|
-
const reported = new Set(remoteMachines.findings.map((each) => each.title));
|
|
1577
|
-
findings.push(...deviceHosts.findings.filter((each) => !reported.has(each.title)));
|
|
1578
|
-
const agentDriver = inspectHostedAgentDriver();
|
|
1579
|
-
if (agentDriver) findings.push(agentDriver);
|
|
1580
|
-
const maintenance = maintenanceStatus();
|
|
1581
|
-
const maintenanceLines = [
|
|
1582
|
-
phaseLine("maintenance.mode", maintenance.mode),
|
|
1583
|
-
phaseLine("maintenance log", maintenanceNdjsonFile()),
|
|
1584
|
-
phaseLine("last pass", maintenanceLine(maintenance, false) ?? "no pass has run yet")
|
|
1585
|
-
];
|
|
1586
|
-
if (opts.json) {
|
|
1587
|
-
console.log(JSON.stringify({
|
|
1588
|
-
project: root,
|
|
1589
|
-
platform: opts.platform ?? null,
|
|
1590
|
-
stim,
|
|
1591
|
-
budget: budget.report,
|
|
1592
|
-
maintenance: {
|
|
1593
|
-
...maintenance,
|
|
1594
|
-
logPath: maintenanceNdjsonFile()
|
|
1595
|
-
},
|
|
1596
|
-
remoteMachines: remoteMachines.machines,
|
|
1597
|
-
deviceHosts: deviceHosts.machines,
|
|
1598
|
-
findings
|
|
1599
|
-
}));
|
|
1600
|
-
recordDoctorRun(root, opts.platform, version);
|
|
1601
|
-
return;
|
|
1602
|
-
}
|
|
1603
|
-
if (findings.length === 0) {
|
|
1604
|
-
const lines = [...doctorSuccessLines(opts.platform, stim, budget.report), ...maintenanceLines];
|
|
1605
|
-
for (const [index, line] of lines.entries()) if (index === 1) console.log(chalk.green(line));
|
|
1606
|
-
else if (line && !line.startsWith(" ")) console.log(chalk.bold(line));
|
|
1607
|
-
else console.log(chalk.dim(line));
|
|
1608
|
-
recordDoctorRun(root, opts.platform, version);
|
|
1609
|
-
return;
|
|
1610
|
-
}
|
|
1611
|
-
const ordered = findings.toSorted((a, b) => a.level === b.level ? 0 : a.level === "cost" ? -1 : 1);
|
|
1612
|
-
console.log(chalk.bold(`Doctor (${doctorTarget(opts.platform)})`));
|
|
1613
|
-
for (const line of [
|
|
1614
|
-
...stimVersionLines(stim),
|
|
1615
|
-
...budgetLines(budget.report),
|
|
1616
|
-
...maintenanceLines
|
|
1617
|
-
]) console.log(chalk.dim(line));
|
|
1618
|
-
for (const f of ordered) {
|
|
1619
|
-
const tag = f.level === "cost" ? chalk.yellow("costs time") : chalk.dim("note");
|
|
1620
|
-
console.log(`\n${tag} ${chalk.bold(f.title)}`);
|
|
1621
|
-
console.log(` ${f.detail}`);
|
|
1622
|
-
if (f.fix) console.log(chalk.dim(` -> ${f.fix}`));
|
|
1623
|
-
}
|
|
1624
|
-
console.log(chalk.dim(`\n${findings.length} finding(s). Fix relevant "costs time" findings before copying the source checkout into a native worktree.`));
|
|
1625
|
-
recordDoctorRun(root, opts.platform, version);
|
|
1626
|
-
});
|
|
1627
|
-
}
|
|
1628
|
-
//#endregion
|
|
1629
|
-
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 };
|