stim 1.5.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +1 -1
  2. package/dist/{android-DJWiTBkz.mjs → android-DWssqHQ8.mjs} +165 -661
  3. package/dist/{android-sobvO9ia.mjs → android-Da-G_o3n.mjs} +646 -548
  4. package/dist/{android-BW8i_YxU.mjs → android-DeYdjjBP.mjs} +6 -2
  5. package/dist/{android-cas-CSS8gjR7.mjs → android-cas-SWIjzeuL.mjs} +54 -18
  6. package/dist/android-cas-compiler.mjs +1 -1
  7. package/dist/{app-install--PAocWvq.mjs → app-install-DJg7bVv-.mjs} +36 -10
  8. package/dist/build-lock-CgtsB6TV.mjs +604 -0
  9. package/dist/{build-slots-CuPszJDu.mjs → build-slots-C-oAHRT1.mjs} +2 -2
  10. package/dist/{cache-manifest-uoOKhgUD.mjs → cache-manifest-CYJi8-Hx.mjs} +2 -2
  11. package/dist/cache-manifest.d.mts +1 -1
  12. package/dist/cache-manifest.mjs +1 -1
  13. package/dist/cli.mjs +14 -14
  14. package/dist/collector-run.d.mts +2 -0
  15. package/dist/collector-run.mjs +7 -7
  16. package/dist/{command-output-BuIb_ZOI.mjs → command-output-B3ycpsYv.mjs} +2 -2
  17. package/dist/{config-DMP7L7H6.mjs → config-B7iyKWkL.mjs} +56 -16
  18. package/dist/{dependency-state-CTL8A9p3.mjs → dependency-state-D0M7oKA7.mjs} +2 -2
  19. package/dist/{deps-BAGOnrc7.mjs → deps-oCXVNEqQ.mjs} +6 -6
  20. package/dist/detached-entry-Bpo6ZY_I.mjs +65 -0
  21. package/dist/{dev-client-BoifdqkU.mjs → dev-client-DDUkWgJL.mjs} +12 -12
  22. package/dist/{device-BUw4JBuH.mjs → device-CBSaC6BU.mjs} +7 -7
  23. package/dist/{device-DvKJ454A.mjs → device-CLeDp-KH.mjs} +17 -15
  24. package/dist/{device-lease-r7yEQ1R8.mjs → device-lease-Ffz_M5t0.mjs} +3 -3
  25. package/dist/{device-pool-DU4FQKp5.mjs → device-pool-DNSHL5z8.mjs} +4 -4
  26. package/dist/{device-remote-BTUqbl0i.mjs → device-remote-BprxZp-m.mjs} +11 -11
  27. package/dist/{doctor-Di_B26uh.mjs → doctor-CTAx9gMu.mjs} +90 -46
  28. package/dist/{error-diagnostics-CTePWAzW.mjs → error-diagnostics-D1Kaf-eT.mjs} +23 -17
  29. package/dist/{exec-CyylIdq9.mjs → exec-B29XNuhP.mjs} +14 -2
  30. package/dist/{gc-C20dWw_8.mjs → gc-CX0KRfLG.mjs} +17 -16
  31. package/dist/{guide-sAnoty2y.mjs → guide-CPmAG9me.mjs} +61 -22
  32. package/dist/{guide-status-BF6RsJJp.mjs → guide-status-Craq9z0t.mjs} +3 -3
  33. package/dist/{ios-BdpkncL6.mjs → ios-BRXYSxSc.mjs} +34 -37
  34. package/dist/{ios-device-DJOi9FR5.mjs → ios-device-CDbvn5H2.mjs} +2 -2
  35. package/dist/{ios-device-D3pzTKQE.mjs → ios-device-CFXA46ez.mjs} +1 -1
  36. package/dist/{ios-Deh5js5V.mjs → ios-lEJ5pKsq.mjs} +3 -3
  37. package/dist/{logs-BfQdIhiF.mjs → logs-CakdBTtm.mjs} +6 -6
  38. package/dist/{logs-query-Cdp5aaCH.mjs → logs-query-BGL-Pl12.mjs} +1 -1
  39. package/dist/metro-C7bcJ0uo.mjs +233 -0
  40. package/dist/{named-ports-DH9SXd0f.mjs → named-ports-DzMOlmaX.mjs} +57 -42
  41. package/dist/{ownership-CD_XnpDu.mjs → ownership-Cp52QAK1.mjs} +3 -3
  42. package/dist/{ownership-CVY9cqCr.mjs → ownership-D5vkdm8H.mjs} +13 -13
  43. package/dist/{ports-A6uTJSPj.mjs → ports-BIbJaKNW.mjs} +3 -3
  44. package/dist/{process-output-C7IlIzd7.mjs → process-output-Dl5uKE03.mjs} +1 -1
  45. package/dist/{project-B_SDt4ip.mjs → project-DDZWf_16.mjs} +3 -3
  46. package/dist/{reclaim-C_rB25f4.mjs → reclaim-D614jI6_.mjs} +11 -11
  47. package/dist/{reload-CYBJbJfI.mjs → reload-ChW46VDb.mjs} +9 -9
  48. package/dist/{server-bare-H3pAhbMs.mjs → server-bare-BQkyxa_b.mjs} +1 -1
  49. package/dist/{server-expo-Ds8_mfng.mjs → server-expo-BVAA1OqJ.mjs} +23 -11
  50. package/dist/server-expo-YDDG3eP7.mjs +2 -0
  51. package/dist/{settings-kO2ZqIKy.mjs → settings-BXXorVM2.mjs} +8 -5
  52. package/dist/{slot-launch-BpHiHH6A.mjs → slot-launch-DbLvyrup.mjs} +4 -4
  53. package/dist/{start-DiEfqEWk.mjs → start-TTVILlUi.mjs} +111 -38
  54. package/dist/{state-CnWzvCWP.mjs → state-DZobzxLW.mjs} +2 -2
  55. package/dist/{stats-Fo9ACIv7.mjs → stats-BYcZ6n1h.mjs} +1 -1
  56. package/dist/{stats-DHrO0frH.mjs → stats-CLg5RuDS.mjs} +4 -4
  57. package/dist/{status-J5LAXpag.mjs → status-DUNy_HZs.mjs} +14 -15
  58. package/dist/{stop-CM1fejKW.mjs → stop-CnUpnFT5.mjs} +2 -2
  59. package/dist/{stop-0jJNahwN.mjs → stop-ZrfODZ51.mjs} +13 -13
  60. package/dist/supervisor-run.mjs +7 -5
  61. package/dist/{teardown-CAVcWeRD.mjs → teardown-5J5UXz2P.mjs} +14 -17
  62. package/dist/{workspace-process-lock-Bz19_R9R.mjs → workspace-process-lock-BYfZz_S-.mjs} +1 -1
  63. package/dist/{workspace-state-WVwWw_hB.mjs → workspace-state-BT5E3SA2.mjs} +3 -3
  64. package/dist/{worktree-CeUKDAQw.mjs → worktree-BfE4lzHK.mjs} +21 -16
  65. package/dist/{worktree-DJnBbQZ9.mjs → worktree-FG_ZKKdb.mjs} +13 -9
  66. package/dist/{xcode-BqDeRkpT.mjs → xcode-B6YaWeg0.mjs} +92 -17
  67. package/package.json +12 -8
  68. package/shim/android-optimizations.gradle +6 -0
  69. package/dist/metro-ato6ef3J.mjs +0 -99
  70. package/dist/ports-BZprTJRa.mjs +0 -90
  71. package/dist/server-expo-CSwUVYOa.mjs +0 -2
@@ -1,28 +1,29 @@
1
- import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { g as loadConfig, l as getConcurrencyLimits, o as configCorruptRepair } from "./config-DMP7L7H6.mjs";
3
- import { l as sharedBuildCache, m as workspaceDerivedData } from "./workspace-state-WVwWw_hB.mjs";
4
- import { C as filesystemDevice, E as temporaryRoot, a as dirtyFingerprintFiles, d as listWorktrees, f as locallyKnownUpstream, g as repoRoot, s as gitCommonDir, v as resolveSourceCheckout, w as makeTemporaryDirectory } from "./worktree-DJnBbQZ9.mjs";
5
- import { a as findProjectRoot, i as detectIsExpo, t as appProjectProblem } from "./project-B_SDt4ip.mjs";
6
- import { d as quotedPath, l as phaseLine, u as plural } from "./command-output-BuIb_ZOI.mjs";
7
- import { B as listBuildLocks, Z as fingerprintProject, q as diffFingerprintSources, y as listAdbDevices } from "./android-DJWiTBkz.mjs";
8
- import { A as resolveSettings, E as remoteAndroidSetting, I as unknownSettingKeys, M as settingOrigin, N as settingShapeErrors, O as remoteIosSetting, P as settingsLayers, U as resolveOptimizations, V as compilerCacheFallbackMessage, a as SETTING_SHAPE_REMEDY, i as SETTINGS_WITH_RESOLVE_TIME_FALLBACK, n as PATH_SETTINGS, v as iosSimSlimProfileSetting, w as projectOptimizations, y as mergeSettingsLayers } from "./settings-kO2ZqIKy.mjs";
9
- import { S as resolveEasCliBin, a as POOL_SETTING_REMEDY, b as ownerFromConfig, h as checkEasAuth$1, l as parkedMaxSetting, x as providerFromConfig } from "./teardown-CAVcWeRD.mjs";
10
- import { E as readHostMemoryPressure, T as hostMemoryPressureAdvice, u as listAllIosSims } from "./ios-Deh5js5V.mjs";
11
- import { n as listBuildSlots } from "./build-slots-CuPszJDu.mjs";
12
- import { a as liveOwnedDeviceCount, d as simslimIsOnPath } from "./device-DvKJ454A.mjs";
13
- import { d as projectCmakeLauncher, i as apkOutputsDir, r as resolveAndroidCompilerCache, t as readAndroidCasToolchain } from "./android-cas-CSS8gjR7.mjs";
14
- import { h as podInstallCommand, i as ccacheEnabled, o as detectXcodeMajor } from "./xcode-BqDeRkpT.mjs";
15
- import { i as inspectStimVersions, n as recordDoctorRun, r as compareStimVersions } from "./guide-status-BF6RsJJp.mjs";
16
- import { n as hasInstalledDependencies, r as installedNpmTreeIsValid, t as dependencyState } from "./dependency-state-CTL8A9p3.mjs";
1
+ import { t as getExecutor } from "./exec-B29XNuhP.mjs";
2
+ import { g as loadConfig, l as getConcurrencyLimits, o as configCorruptRepair } from "./config-B7iyKWkL.mjs";
3
+ import { l as sharedBuildCache, m as workspaceDerivedData } from "./workspace-state-BT5E3SA2.mjs";
4
+ import { C as filesystemDevice, E as temporaryRoot, a as dirtyFingerprintFiles, d as listWorktrees, f as locallyKnownUpstream, g as repoRoot, s as gitCommonDir, v as resolveSourceCheckout, w as makeTemporaryDirectory } from "./worktree-FG_ZKKdb.mjs";
5
+ import { a as findProjectRoot, i as detectIsExpo, t as appProjectProblem } from "./project-DDZWf_16.mjs";
6
+ import { A as resolveSettings, E as remoteAndroidSetting, I as unknownSettingKeys, M as settingOrigin, N as settingShapeErrors, O as remoteIosSetting, P as settingsLayers, U as resolveOptimizations, V as compilerCacheFallbackMessage, a as SETTING_SHAPE_REMEDY, i as SETTINGS_WITH_RESOLVE_TIME_FALLBACK, n as PATH_SETTINGS, v as iosSimSlimProfileSetting, w as projectOptimizations, y as mergeSettingsLayers } from "./settings-BXXorVM2.mjs";
7
+ import { S as listAdbDevices, x as hostSystemImageArch } from "./android-DWssqHQ8.mjs";
8
+ import { d as quotedPath, l as phaseLine, u as plural } from "./command-output-B3ycpsYv.mjs";
9
+ import { m as fingerprintProject, r as listBuildLocks, u as diffFingerprintSources } from "./build-lock-CgtsB6TV.mjs";
10
+ import { S as resolveEasCliBin, a as POOL_SETTING_REMEDY, b as ownerFromConfig, h as checkEasAuth$1, l as parkedMaxSetting, x as providerFromConfig } from "./teardown-5J5UXz2P.mjs";
11
+ import { E as readHostMemoryPressure, T as hostMemoryPressureAdvice, u as listAllIosSims } from "./ios-lEJ5pKsq.mjs";
12
+ import { n as listBuildSlots } from "./build-slots-C-oAHRT1.mjs";
13
+ import { a as liveOwnedDeviceCount, d as simslimIsOnPath } from "./device-CLeDp-KH.mjs";
14
+ import { d as projectCmakeLauncher, g as androidPathRoomRemedy, h as androidPathRoomMessage, i as apkOutputsDir, m as androidPathRoom, r as resolveAndroidCompilerCache, t as readAndroidCasToolchain } from "./android-cas-SWIjzeuL.mjs";
15
+ import { h as podInstallCommand, i as ccacheEnabled, o as detectXcodeMajor } from "./xcode-B6YaWeg0.mjs";
16
+ import { i as inspectStimVersions, n as recordDoctorRun, r as compareStimVersions } from "./guide-status-Craq9z0t.mjs";
17
+ import { n as hasInstalledDependencies, r as installedNpmTreeIsValid, t as dependencyState } from "./dependency-state-D0M7oKA7.mjs";
17
18
  import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync, rmSync } from "node:fs";
18
19
  import { InvalidArgumentError } from "commander";
19
20
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
20
21
  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";
21
22
  import { homedir } from "os";
22
- import { dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1, relative as relative$1, resolve as resolve$1 } from "path";
23
+ 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";
23
24
  import * as expoFingerprint from "@expo/fingerprint";
24
25
  import chalk from "chalk";
25
- //#region src/sandbox.ts
26
+ //#region src/diagnostics/sandbox.ts
26
27
  /**
27
28
  * What a sandboxed harness has to permit for Stim to work: its state
28
29
  * directory, the XPC service simctl talks to, and the port adb's server binds.
@@ -111,8 +112,12 @@ function nested(source, ...keys) {
111
112
  }
112
113
  /** Whether an allowWrite entry covers the Stim home, including as a parent. */
113
114
  function covers(entry, target, home) {
114
- const e = expandHome(entry.replace(/\/+\*+$/, "").replace(/\/+$/, ""), home);
115
- const t = expandHome(target.replace(/\/+$/, ""), home);
115
+ const posix = (value) => {
116
+ const expanded = expandHome(value, home);
117
+ return sep$1 === "/" ? expanded : expanded.replaceAll(sep$1, "/");
118
+ };
119
+ const e = posix(entry.replace(/\/+\*+$/, "").replace(/\/+$/, ""));
120
+ const t = posix(target.replace(/\/+$/, ""));
116
121
  return e === t || t.startsWith(`${e}/`);
117
122
  }
118
123
  /**
@@ -224,8 +229,8 @@ function sandboxFinding(settingsRoot, { env = process.env, home = homedir(), sti
224
229
  };
225
230
  }
226
231
  //#endregion
227
- //#region src/doctor-storage.ts
228
- function checkStorageLayout(projectRoot, { platform, device = filesystemDevice, stagingRoot = temporaryRoot } = {}) {
232
+ //#region src/diagnostics/doctor-storage.ts
233
+ function checkStorageLayout(projectRoot, { platform, host = process.platform, device = filesystemDevice, stagingRoot = temporaryRoot } = {}) {
229
234
  const findings = [];
230
235
  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.";
231
236
  const check = (operation, paths, fix) => {
@@ -244,7 +249,7 @@ function checkStorageLayout(projectRoot, { platform, device = filesystemDevice,
244
249
  const cache = sharedBuildCache();
245
250
  check("Cached app/APK staging", [cache, stagingRoot(join(cache, "artifact.app"))], temporaryFix);
246
251
  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.";
247
- if (platform === "ios" || platform !== "android" && existsSync(join(projectRoot, "ios"))) {
252
+ if ((platform === "ios" || platform !== "android" && existsSync(join(projectRoot, "ios"))) && host === "darwin") {
248
253
  const output = join(workspaceDerivedData(projectRoot), "Build", "Products");
249
254
  check("iOS build-cache storage", [output, cache], cacheFix);
250
255
  check("iOS device app staging", [output, stagingRoot(join(output, "artifact.app"))], temporaryFix);
@@ -261,7 +266,7 @@ function checkStorageLayout(projectRoot, { platform, device = filesystemDevice,
261
266
  return findings;
262
267
  }
263
268
  //#endregion
264
- //#region src/doctor-ios-architectures.ts
269
+ //#region src/diagnostics/doctor-ios-architectures.ts
265
270
  const COMPILED_PRODUCTS = /* @__PURE__ */ new Set([
266
271
  "com.apple.product-type.application",
267
272
  "com.apple.product-type.application.on-demand-install-capable",
@@ -397,7 +402,7 @@ function inspectIosDebugArchitectures(projectRoot) {
397
402
  return findings;
398
403
  }
399
404
  //#endregion
400
- //#region src/doctor-cxx.ts
405
+ //#region src/diagnostics/doctor-cxx.ts
401
406
  function parseCmakeCacheLauncher(source) {
402
407
  if (typeof source !== "string") return null;
403
408
  return /^CMAKE_CXX_COMPILER_LAUNCHER(?::[A-Z]+)?=(.*)$/m.exec(source)?.[1]?.trim() || null;
@@ -474,7 +479,7 @@ function repairCxxLauncherState(root) {
474
479
  refused: []
475
480
  };
476
481
  if (selectedCompilerCache(root) !== "ccache") return result;
477
- if (!getExecutor().runQuiet("command -v ccache", { timeoutMs: 5e3 })) return result;
482
+ if (!getExecutor().findExecutable("ccache")) return result;
478
483
  const appOverrides = declaredLauncher(join(root, "android", "app"));
479
484
  const targets = readCxxLauncherStates(root).filter(({ launcher }) => {
480
485
  if (!launcher) return true;
@@ -530,7 +535,7 @@ function repairCxxLauncherState(root) {
530
535
  return result;
531
536
  }
532
537
  //#endregion
533
- //#region src/doctor-config.ts
538
+ //#region src/diagnostics/doctor-config.ts
534
539
  function readMachineSettings(context) {
535
540
  try {
536
541
  const layers = settingsLayers(context);
@@ -639,7 +644,7 @@ function missingPath(value, projectRoot, exists, trim = false) {
639
644
  return opened === opened.trim() ? path : JSON.stringify(path);
640
645
  }
641
646
  //#endregion
642
- //#region src/doctor.ts
647
+ //#region src/diagnostics/doctor.ts
643
648
  function finding(level, title, detail, fix) {
644
649
  return {
645
650
  level,
@@ -768,7 +773,7 @@ function seedFindings(mainRoot, upstream) {
768
773
  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}\`.`));
769
774
  return findings;
770
775
  }
771
- function checkMainCheckout(projectRoot, { npmTreeValid, brokenPods = void 0, upstream = void 0, linkedWorktrees = void 0, platform } = {}) {
776
+ function checkMainCheckout(projectRoot, { npmTreeValid, brokenPods = void 0, upstream = void 0, linkedWorktrees = void 0, platform, localIos = platform !== "android" } = {}) {
772
777
  const mainRoot = mainCheckoutProjectRoot(projectRoot);
773
778
  const findings = [];
774
779
  const dependencies = dependencyState(mainRoot);
@@ -783,7 +788,7 @@ function checkMainCheckout(projectRoot, { npmTreeValid, brokenPods = void 0, ups
783
788
  const podfileLock = join$1(mainRoot, "ios", "Podfile.lock");
784
789
  const podManifest = join$1(mainRoot, "ios", "Pods", "Manifest.lock");
785
790
  const podsRoot = join$1(mainRoot, "ios", "Pods");
786
- if (platform !== "android" && existsSync$1(podfileLock)) {
791
+ if (localIos && existsSync$1(podfileLock)) {
787
792
  let podsState = null;
788
793
  if (!existsSync$1(podManifest)) podsState = "missing";
789
794
  else try {
@@ -803,7 +808,7 @@ function checkMainCheckout(projectRoot, { npmTreeValid, brokenPods = void 0, ups
803
808
  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.`));
804
809
  }
805
810
  }
806
- const coldPlatforms = [platform !== "android" && existsSync$1(join$1(mainRoot, "ios")) && !hasIosWarmOutput(mainRoot) ? "iOS" : null, platform !== "ios" && existsSync$1(join$1(mainRoot, "android")) && !existsSync$1(join$1(mainRoot, "android", "build")) && !existsSync$1(join$1(mainRoot, "android", "app", "build")) ? "Android" : null].filter((coldPlatform) => coldPlatform !== null);
811
+ const coldPlatforms = [localIos && existsSync$1(join$1(mainRoot, "ios")) && !hasIosWarmOutput(mainRoot) ? "iOS" : null, platform !== "ios" && existsSync$1(join$1(mainRoot, "android")) && !existsSync$1(join$1(mainRoot, "android", "build")) && !existsSync$1(join$1(mainRoot, "android", "app", "build")) ? "Android" : null].filter((coldPlatform) => coldPlatform !== null);
807
812
  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}.`));
808
813
  if (linkedWorktrees === void 0 ? hasLinkedWorktree(projectRoot) : linkedWorktrees) findings.push(...seedFindings(mainRoot, upstream === void 0 ? locallyKnownUpstream(mainRoot) : upstream));
809
814
  return findings;
@@ -846,7 +851,7 @@ function checkCcacheConflict(podfileSource, podfileProperties) {
846
851
  }
847
852
  function checkCcacheInstalled(onPath) {
848
853
  if (onPath) return null;
849
- return finding("cost", "ccache is not on PATH, so Android C++ recompiles in every worktree", "Stim resolves the launcher with `command -v ccache` -- the same PATH 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.");
854
+ 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.");
850
855
  }
851
856
  function launcherPath(launcher) {
852
857
  const command = launcher?.split(";")[0]?.trim();
@@ -868,7 +873,7 @@ function checkCxxCompilerLauncher({ states, ccacheOnPath, launcherExists = exist
868
873
  }
869
874
  function ccacheIsOnPath() {
870
875
  try {
871
- return Boolean(getExecutor().runQuiet("command -v ccache", { timeoutMs: 5e3 }));
876
+ return Boolean(getExecutor().findExecutable("ccache"));
872
877
  } catch {
873
878
  return false;
874
879
  }
@@ -915,7 +920,25 @@ function checkSimSlim({ configured = false, profileError = null, onPath = false
915
920
  if (onPath) return null;
916
921
  return finding("cost", "A SimSlim profile is configured, but SimSlim is not installed", "`stim ios` needs the `simslim` command to apply the profile to its owned simulator.", "brew install mobai-app/tap/simslim");
917
922
  }
918
- function runDoctor(projectRoot, { readFile = readFileSync$1, xcodeMajor = null, easAuth = checkEasAuth$1, concurrency = getConcurrencyLimits, liveDevices = null, activeBuilds = null, remoteEnv = process.env, lookupAgentDevice = null, lookupEasCli = null, lookupSimSlim = null, memoryPressure = readHostMemoryPressure, lookupCcache = null, platform } = {}) {
923
+ function checkIosHost(platform, host) {
924
+ if (platform !== "ios" || host === "darwin") return null;
925
+ return finding("note", `iOS runs through EAS on this ${host} host`, "Xcode, CocoaPods and simctl exist only on macOS, so `stim ios` cannot build or boot a simulator here. `stim ios --remote eas --eas-profile <simulator profile>` downloads a finished EAS development build and runs it on an EAS Simulator session driven through agent-device; Metro stays on this machine behind a tunnel.", "Install eas-cli and agent-device, then run `stim start --remote` and `stim ios --remote eas --eas-profile <profile>`. Read `stim guide metro` for the remote device backends.");
926
+ }
927
+ function checkXcodeEnvLineEndings(projectRoot, platform, host) {
928
+ if (platform === "android" || host !== "win32") return null;
929
+ const xcodeEnv = join$1(projectRoot, "ios", ".xcode.env");
930
+ if (!existsSync$1(xcodeEnv)) return null;
931
+ let content;
932
+ try {
933
+ content = readFileSync$1(xcodeEnv, "utf-8");
934
+ } catch {
935
+ return null;
936
+ }
937
+ if (!content.includes("\r")) return null;
938
+ return finding("cost", "ios/.xcode.env has CRLF line endings", "An EAS build uploads the working-tree bytes of ios/.xcode.env, and Xcode sources that file with sh on the build worker, where each carriage return becomes part of the line and the build fails with `: command not found`. On Windows the usual cause is core.autocrlf=true rewriting the file at checkout.", "Run `git config core.autocrlf input` then `git checkout -- ios/.xcode.env`, or add `ios/.xcode.env text eol=lf` to .gitattributes and check the file out again.");
939
+ }
940
+ function runDoctor(projectRoot, { readFile = readFileSync$1, xcodeMajor = null, easAuth = checkEasAuth$1, concurrency = getConcurrencyLimits, liveDevices = null, activeBuilds = null, remoteEnv = process.env, lookupAgentDevice = null, lookupEasCli = null, lookupSimSlim = null, memoryPressure = readHostMemoryPressure, lookupCcache = null, platform, host = process.platform } = {}) {
941
+ const localIos = platform !== "android" && host === "darwin";
919
942
  const read = (rel) => {
920
943
  const p = join$1(projectRoot, rel);
921
944
  if (!existsSync$1(p)) return null;
@@ -982,12 +1005,12 @@ function runDoctor(projectRoot, { readFile = readFileSync$1, xcodeMajor = null,
982
1005
  }
983
1006
  let simslimProfile = null;
984
1007
  let simslimProfileError = null;
985
- if (platform !== "android") try {
1008
+ if (localIos) try {
986
1009
  simslimProfile = iosSimSlimProfileSetting(projectSettings, projectRoot);
987
1010
  } catch (error) {
988
1011
  simslimProfileError = String(error?.message || error);
989
1012
  }
990
- const simslimFinding = platform === "android" || !simslimProfile && !simslimProfileError && remoteIosSetting(projectSettings) ? null : checkSimSlim({
1013
+ const simslimFinding = !localIos || !simslimProfile && !simslimProfileError && remoteIosSetting(projectSettings) ? null : checkSimSlim({
991
1014
  configured: Boolean(simslimProfile),
992
1015
  profileError: simslimProfileError,
993
1016
  onPath: simslimProfile ? lookupSimSlim ? lookupSimSlim() : simslimIsOnPath() : false
@@ -1002,16 +1025,25 @@ function runDoctor(projectRoot, { readFile = readFileSync$1, xcodeMajor = null,
1002
1025
  agentDeviceOnPath,
1003
1026
  easCliResolvable
1004
1027
  })).filter((remoteFinding) => remoteFinding !== null);
1005
- const memoryAdvice = platform !== "android" && !remoteIosSetting(projectSettings) ? hostMemoryPressureAdvice(memoryPressure()) : null;
1028
+ const memoryAdvice = localIos && !remoteIosSetting(projectSettings) ? hostMemoryPressureAdvice(memoryPressure()) : null;
1006
1029
  return [
1007
1030
  checkAppProject(projectRoot),
1008
- ...checkMainCheckout(projectRoot, { platform }),
1009
- ...platform === "android" ? [] : inspectIosDebugArchitectures(mainCheckoutProjectRoot(projectRoot)),
1010
- ...checkStorageLayout(projectRoot, { platform }),
1031
+ checkIosHost(platform, host),
1032
+ checkXcodeEnvLineEndings(projectRoot, platform, host),
1033
+ ...checkMainCheckout(projectRoot, {
1034
+ platform,
1035
+ localIos
1036
+ }),
1037
+ ...localIos ? inspectIosDebugArchitectures(mainCheckoutProjectRoot(projectRoot)) : [],
1038
+ ...checkStorageLayout(projectRoot, {
1039
+ platform,
1040
+ host
1041
+ }),
1011
1042
  optimizations?.metroSharedCache ? checkMetroCache(metroConfig) : null,
1012
- platform === "android" || !optimizations?.ios.compilationCache ? null : checkCompilationCache(podfile, xcodeMajor),
1013
- platform === "android" || !optimizations?.ios.compilationCache ? null : checkCcacheConflict(podfile, podfileProperties),
1043
+ localIos && optimizations?.ios.compilationCache ? checkCompilationCache(podfile, xcodeMajor) : null,
1044
+ localIos && optimizations?.ios.compilationCache ? checkCcacheConflict(podfile, podfileProperties) : null,
1014
1045
  ...platform === "ios" || optimizations?.android.compilerCache !== "ccache" ? [] : androidCcacheFindings(projectRoot, platform, lookupCcache),
1046
+ checkAndroidPathRoom(projectRoot, platform, host),
1015
1047
  remoteBuildCache ? checkBuildCacheProvider(appConfig, sdkMajor, isExpo, dynamicConfig) : null,
1016
1048
  easFinding,
1017
1049
  concurrencyFinding,
@@ -1028,6 +1060,17 @@ function runDoctor(projectRoot, { readFile = readFileSync$1, xcodeMajor = null,
1028
1060
  })
1029
1061
  ].filter((f) => Boolean(f));
1030
1062
  }
1063
+ function checkAndroidPathRoom(projectRoot, platform, host, hostArch = process.arch) {
1064
+ const room = platform === "ios" ? null : androidPathRoom(projectRoot, {
1065
+ abi: hostSystemImageArch(hostArch),
1066
+ platform: host
1067
+ });
1068
+ if (!room) return null;
1069
+ return {
1070
+ code: "android-path-room",
1071
+ ...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))
1072
+ };
1073
+ }
1031
1074
  function androidCcacheFindings(projectRoot, platform, lookupCcache) {
1032
1075
  if (platform !== "android" && !existsSync$1(join$1(projectRoot, "android"))) return [];
1033
1076
  const onPath = lookupCcache ? lookupCcache() : ccacheIsOnPath();
@@ -1050,7 +1093,7 @@ function checkAppProject(projectRoot) {
1050
1093
  }
1051
1094
  function agentDeviceIsOnPath() {
1052
1095
  try {
1053
- return Boolean(getExecutor().runQuiet("command -v agent-device", { timeoutMs: 5e3 }));
1096
+ return Boolean(getExecutor().findExecutable("agent-device"));
1054
1097
  } catch {
1055
1098
  return true;
1056
1099
  }
@@ -1338,7 +1381,7 @@ function applySandboxFix(root, env = process.env) {
1338
1381
  console.error(chalk.green(`${result.status === "created" ? "Wrote" : "Updated"} ${target}`));
1339
1382
  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."));
1340
1383
  }
1341
- function doctorCommand(program, version, inspectVersions = inspectStimVersions) {
1384
+ function doctorCommand(program, version, inspectVersions = inspectStimVersions, host = process.platform) {
1342
1385
  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 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. Stop native builds first. Generated CMake output must be ignored and untracked; custom launcher settings and source files are preserved.").action(async (opts) => {
1343
1386
  const root = findProjectRoot(process.cwd());
1344
1387
  if (!root) {
@@ -1360,8 +1403,9 @@ function doctorCommand(program, version, inspectVersions = inspectStimVersions)
1360
1403
  }
1361
1404
  const stim = await inspectVersions(version);
1362
1405
  const findings = runDoctor(root, {
1363
- xcodeMajor: opts.platform === "android" ? null : detectXcodeMajor(),
1364
- platform: opts.platform
1406
+ xcodeMajor: opts.platform !== "android" && host === "darwin" ? detectXcodeMajor() : null,
1407
+ platform: opts.platform,
1408
+ host
1365
1409
  });
1366
1410
  const parity = await detectFingerprintParity(root, { platform: opts.platform });
1367
1411
  if (parity) findings.push(parity);
@@ -1,19 +1,19 @@
1
- import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { D as withDirLock, F as deviceSlotPlatforms, P as deviceSlotKey, f as getProject } from "./config-DMP7L7H6.mjs";
3
- import { r as readWorkspaceState } from "./workspace-state-WVwWw_hB.mjs";
4
- import { d as readWorkspaceLaunches } from "./state-CnWzvCWP.mjs";
5
- import { n as deviceConsoleLevel } from "./ios-device-D3pzTKQE.mjs";
6
- import { i as resolveProjectMetro } from "./metro-ato6ef3J.mjs";
7
- import { n as parseLogcatLine, t as androidClockOffset } from "./android-BW8i_YxU.mjs";
8
- import { _ as launchErrorPreview } from "./command-output-BuIb_ZOI.mjs";
9
- import { n as androidHome } from "./android-DJWiTBkz.mjs";
10
- import { a as parseLease, n as fileLeaseIo, t as deviceLeasePath } from "./device-lease-r7yEQ1R8.mjs";
11
- import { l as readLogRecords, t as attachExpoErrorContext } from "./logs-query-Cdp5aaCH.mjs";
1
+ import { t as getExecutor } from "./exec-B29XNuhP.mjs";
2
+ import { D as withDirLock, F as deviceSlotKey, I as deviceSlotPlatforms, f as getProject } from "./config-B7iyKWkL.mjs";
3
+ import { r as readWorkspaceState } from "./workspace-state-BT5E3SA2.mjs";
4
+ import { d as readWorkspaceLaunches } from "./state-DZobzxLW.mjs";
5
+ import { n as deviceConsoleLevel } from "./ios-device-CFXA46ez.mjs";
6
+ import { a as resolveProjectMetro } from "./metro-C7bcJ0uo.mjs";
7
+ import { r as androidHome, s as androidToolCwd } from "./android-DWssqHQ8.mjs";
8
+ import { n as parseLogcatLine, t as androidClockOffset } from "./android-DeYdjjBP.mjs";
9
+ import { _ as launchErrorPreview } from "./command-output-B3ycpsYv.mjs";
10
+ import { a as parseLease, n as fileLeaseIo, t as deviceLeasePath } from "./device-lease-Ffz_M5t0.mjs";
11
+ import { l as readLogRecords, t as attachExpoErrorContext } from "./logs-query-BGL-Pl12.mjs";
12
12
  import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
13
13
  import { createHash, randomUUID } from "node:crypto";
14
14
  import { basename, dirname, join, resolve, sep } from "node:path";
15
15
  import { homedir } from "node:os";
16
- //#region src/diagnostic-store.ts
16
+ //#region src/diagnostics/diagnostic-store.ts
17
17
  /** Publishes an immutable diagnostic snapshot atomically; existing evidence wins. */
18
18
  function writeDiagnosticOnce(path, content) {
19
19
  mkdirSync(dirname(path), { recursive: true });
@@ -34,7 +34,7 @@ function writeDiagnosticOnce(path, content) {
34
34
  });
35
35
  }
36
36
  //#endregion
37
- //#region src/native-crash.ts
37
+ //#region src/diagnostics/native-crash.ts
38
38
  const IOS_CRASH_REPORT_RETRY = "iOS crash reports can take about a minute or longer to appear. Run `stim logs --errors` again for the native stack.";
39
39
  /** Accepts only a report for this app, simulator and launch window. */
40
40
  function parseIosCrash(text, target) {
@@ -329,7 +329,10 @@ function captureNativeCrashes(target, logsDir) {
329
329
  "time,epoch",
330
330
  "-t",
331
331
  "2000"
332
- ], { timeoutMs: 3e3 });
332
+ ], {
333
+ timeoutMs: 3e3,
334
+ cwd: androidToolCwd()
335
+ });
333
336
  const deadline = Date.now() + 4e3;
334
337
  if (text) records = parseAndroidCrashes(text, target, offset).map((record) => symbolicateAndroid(record, target.root, deadline));
335
338
  }
@@ -384,7 +387,10 @@ function captureWorkspaceCrashes(root, logsDir) {
384
387
  "emu",
385
388
  "avd",
386
389
  "name"
387
- ], { timeoutMs: 2e3 })?.split("\n")[0]?.trim() !== configured.avdName) continue;
390
+ ], {
391
+ timeoutMs: 2e3,
392
+ cwd: androidToolCwd()
393
+ })?.split("\n")[0]?.trim() !== configured.avdName) continue;
388
394
  }
389
395
  }
390
396
  captureNativeCrashes({
@@ -402,7 +408,7 @@ function captureWorkspaceCrashes(root, logsDir) {
402
408
  }
403
409
  }
404
410
  //#endregion
405
- //#region src/error-symbolication.ts
411
+ //#region src/diagnostics/error-symbolication.ts
406
412
  const LOCATION = /https?:\/\/[^\s)'"\\]+?:\d+:\d+/g;
407
413
  function frameFromLocation(location) {
408
414
  const match = /^(https?:\/\/.+):(\d+):(\d+)$/.exec(location);
@@ -490,7 +496,7 @@ async function symbolicateErrors(records, { port, timeoutMs = 2e3, request = fet
490
496
  }
491
497
  }
492
498
  //#endregion
493
- //#region src/error-diagnostics.ts
499
+ //#region src/diagnostics/error-diagnostics.ts
494
500
  function key(record) {
495
501
  return createHash("sha256").update(JSON.stringify(record)).digest("hex");
496
502
  }
@@ -1,4 +1,6 @@
1
- import { execFileSync, execSync, spawn } from "child_process";
1
+ import { execSync } from "child_process";
2
+ import spawn from "cross-spawn";
3
+ import which from "which";
2
4
  //#region src/exec.ts
3
5
  const MAX_BUFFER = 67108864;
4
6
  let active = {
@@ -38,7 +40,14 @@ let active = {
38
40
  for (const key of omitEnv ?? []) delete childEnv[key];
39
41
  opts.env = childEnv;
40
42
  }
41
- return String(execFileSync(file, args, opts)).trim();
43
+ const result = spawn.sync(file, args, opts);
44
+ if (result.error) throw Object.assign(result.error, result);
45
+ if (result.status !== 0) {
46
+ const stderr = String(result.stderr ?? "");
47
+ const message = `Command failed: ${[file, ...args].join(" ")}${stderr ? `\n${stderr}` : ""}`;
48
+ throw Object.assign(new Error(message), result);
49
+ }
50
+ return String(result.stdout).trim();
42
51
  },
43
52
  runQuiet(cmd, opts) {
44
53
  try {
@@ -56,6 +65,9 @@ let active = {
56
65
  },
57
66
  spawn(cmd, args = [], opts = {}) {
58
67
  return spawn(cmd, args, opts);
68
+ },
69
+ findExecutable(name) {
70
+ return which.sync(name, { nothrow: true });
59
71
  }
60
72
  };
61
73
  function getExecutor() {
@@ -1,25 +1,26 @@
1
- import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { A as isOnMountedVolume, C as withConfigLock, E as ownership_claim_exports, F as deviceSlotPlatforms, L as projectDeviceSlots, M as volumeRootFor, O as directorySize, f as getProject, g as loadConfig, i as clearDevice, j as listMountedVolumes, k as formatBytes, u as getConfigDir, v as removeProject } from "./config-DMP7L7H6.mjs";
3
- import { i as registeredCaches } from "./cache-manifest-uoOKhgUD.mjs";
4
- import { h as workspaceDir, y as workspaceStateFile } from "./workspace-state-WVwWw_hB.mjs";
5
- import { g as repoRoot, s as gitCommonDir } from "./worktree-DJnBbQZ9.mjs";
6
- import { a as findProjectRoot, i as detectIsExpo } from "./project-B_SDt4ip.mjs";
7
- import { m as shortUdid, o as formatLongDuration, u as plural } from "./command-output-BuIb_ZOI.mjs";
8
- import { B as listBuildLocks, S as listOrphanedAvdDirectories, T as ownedAvdDirectory, b as listAvds } from "./android-DJWiTBkz.mjs";
9
- import { A as resolveSettings, N as settingShapeErrors, a as SETTING_SHAPE_REMEDY } from "./settings-kO2ZqIKy.mjs";
10
- import { S as resolveEasCliBin, a as POOL_SETTING_REMEDY, i as teardownParkedIosSim, l as parkedMaxSetting, n as teardownOwnedIosSim, r as teardownParkedAvd, s as dropParked, t as teardownOwnedAvd, u as readParked } from "./teardown-CAVcWeRD.mjs";
11
- import { d as listIosDeviceTypes, g as parseRuntimeVersion, u as listAllIosSims } from "./ios-Deh5js5V.mjs";
12
- import { i as listLeaseFiles, r as leaseIsExpired, u as removeExpiredLease } from "./device-lease-r7yEQ1R8.mjs";
13
- import { n as listBuildSlots } from "./build-slots-CuPszJDu.mjs";
14
- import { C as verifyStoppedSession, S as stopSessionArgs, _ as inspectSessionForTeardown, d as withEasProjectLock, f as easMachineStateRoot, g as getSessionArgs, h as findOrphanedOwnedSessions, m as removeEasSessionClaim, p as readEasSessionLedger, u as withRemoteSessionLock, v as isDefinitiveMissingSessionError, x as parseSessionListPage, y as listOwnedSessionsArgs } from "./device-remote-BTUqbl0i.mjs";
15
- import { n as reclaimProject, t as describeDereferenced } from "./reclaim-C_rB25f4.mjs";
1
+ import { t as getExecutor } from "./exec-B29XNuhP.mjs";
2
+ import { A as isOnMountedVolume, C as withConfigLock, E as ownership_claim_exports, I as deviceSlotPlatforms, M as volumeRootFor, O as directorySize, R as projectDeviceSlots, f as getProject, g as loadConfig, i as clearDevice, j as listMountedVolumes, k as formatBytes, u as getConfigDir, v as removeProject } from "./config-B7iyKWkL.mjs";
3
+ import { i as registeredCaches } from "./cache-manifest-CYJi8-Hx.mjs";
4
+ import { h as workspaceDir, y as workspaceStateFile } from "./workspace-state-BT5E3SA2.mjs";
5
+ import { g as repoRoot, s as gitCommonDir } from "./worktree-FG_ZKKdb.mjs";
6
+ import { a as findProjectRoot, i as detectIsExpo } from "./project-DDZWf_16.mjs";
7
+ import { A as resolveSettings, N as settingShapeErrors, a as SETTING_SHAPE_REMEDY } from "./settings-BXXorVM2.mjs";
8
+ import { C as listAvds, O as ownedAvdDirectory, T as listOrphanedAvdDirectories } from "./android-DWssqHQ8.mjs";
9
+ import { m as shortUdid, o as formatLongDuration, u as plural } from "./command-output-B3ycpsYv.mjs";
10
+ import { r as listBuildLocks } from "./build-lock-CgtsB6TV.mjs";
11
+ import { S as resolveEasCliBin, a as POOL_SETTING_REMEDY, i as teardownParkedIosSim, l as parkedMaxSetting, n as teardownOwnedIosSim, r as teardownParkedAvd, s as dropParked, t as teardownOwnedAvd, u as readParked } from "./teardown-5J5UXz2P.mjs";
12
+ import { d as listIosDeviceTypes, g as parseRuntimeVersion, u as listAllIosSims } from "./ios-lEJ5pKsq.mjs";
13
+ import { i as listLeaseFiles, r as leaseIsExpired, u as removeExpiredLease } from "./device-lease-Ffz_M5t0.mjs";
14
+ import { n as listBuildSlots } from "./build-slots-C-oAHRT1.mjs";
15
+ import { C as verifyStoppedSession, S as stopSessionArgs, _ as inspectSessionForTeardown, d as withEasProjectLock, f as easMachineStateRoot, g as getSessionArgs, h as findOrphanedOwnedSessions, m as removeEasSessionClaim, p as readEasSessionLedger, u as withRemoteSessionLock, v as isDefinitiveMissingSessionError, x as parseSessionListPage, y as listOwnedSessionsArgs } from "./device-remote-BprxZp-m.mjs";
16
+ import { n as reclaimProject, t as describeDereferenced } from "./reclaim-D614jI6_.mjs";
16
17
  import { InvalidArgumentError } from "commander";
17
18
  import { existsSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from "fs";
18
19
  import { homedir, tmpdir } from "os";
19
20
  import { basename, dirname, isAbsolute, join, relative, resolve } from "path";
20
21
  import { METRO_NAMED_CACHE_LAYOUT } from "@stim-cli/core";
21
22
  import chalk from "chalk";
22
- //#region src/caches.ts
23
+ //#region src/cache/caches.ts
23
24
  function xcodeDerivedDataRoot() {
24
25
  return join(homedir(), "Library", "Developer", "Xcode", "DerivedData");
25
26
  }