stim 1.19.0 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/README.md +20 -3
  2. package/dist/{activity-BSw0x84R.mjs → activity-BrGh9EAI.mjs} +3 -3
  3. package/dist/{agent-device-usage-output-Bjxu_VmX.mjs → agent-device-usage-output-DVyv_hWj.mjs} +7 -7
  4. package/dist/android-0vv-OruW.mjs +6 -0
  5. package/dist/android-Brx6EKvJ.mjs +1258 -0
  6. package/dist/{android-BdQC6zs1.mjs → android-BsYNR81W.mjs} +103 -45
  7. package/dist/{android-DE5g_5gv.mjs → android-CK3fpEyZ.mjs} +2 -2
  8. package/dist/{android-cas-C87geAg-.mjs → android-cas-DCAuXryb.mjs} +9 -5
  9. package/dist/android-cas-compiler.mjs +1 -1
  10. package/dist/api-run.mjs +159 -8
  11. package/dist/{api-DIf_s86s.mjs → api-zuX3ynYN.mjs} +5 -1
  12. package/dist/api.d.mts +98 -4
  13. package/dist/api.mjs +1 -1
  14. package/dist/{app-install-CFI4ptUL.mjs → app-install-0LgaQRT7.mjs} +9 -8
  15. package/dist/artifact-CnC0mfov.mjs +807 -0
  16. package/dist/artifact-DClXJNWx.mjs +772 -0
  17. package/dist/artifact-lifecycle-DzPpBYvK.mjs +30 -0
  18. package/dist/{browser-web-CDhqQxhv.mjs → browser-web-CzBmhmgs.mjs} +19 -19
  19. package/dist/{budget-Dz-kyXLv.mjs → budget-BONq9vXY.mjs} +3101 -3062
  20. package/dist/build-BixRI6sB.mjs +462 -0
  21. package/dist/{build-facts-B87vo-Im.d.mts → build-facts-DcLv2HSd.d.mts} +1 -32
  22. package/dist/build-plan-CP0tckNU.mjs +553 -0
  23. package/dist/{cache-manifest-DiwKSyv9.mjs → cache-manifest-qA3dT2x2.mjs} +1 -1
  24. package/dist/cache-manifest.mjs +1 -1
  25. package/dist/{manifest-CX0d1IkZ.mjs → ccache-CjHQ5F8g.mjs} +13 -19
  26. package/dist/{chrome-BTUOo8p2.mjs → chrome-Dp2lMLBe.mjs} +1 -1
  27. package/dist/{cli-C9Zvgbdc.mjs → cli-B2R8wH1F.mjs} +20 -20
  28. package/dist/cli.mjs +1 -1
  29. package/dist/{client-CuUo8Nvg.mjs → client-BLy1Pw2V.mjs} +89 -307
  30. package/dist/collector-run.d.mts +4 -0
  31. package/dist/collector-run.mjs +7 -7
  32. package/dist/{command-output-Bt8ygHE7.mjs → command-output-BjPN99ZB.mjs} +2 -2
  33. package/dist/{config-DC5jEtd1.mjs → config-D0LhTY3S.mjs} +8 -12
  34. package/dist/{created-devices-D0s26Hx5.mjs → created-devices-HU_DAb_Q.mjs} +7 -6
  35. package/dist/{dependency-state-DKaS1OM9.mjs → dependency-state-B2m8eqsS.mjs} +2 -2
  36. package/dist/{detached-entry-pB_IPfV4.mjs → detached-entry-BXq4fIl-.mjs} +1 -1
  37. package/dist/{dev-client-CDY_zAmD.mjs → dev-client-ClATaJoH.mjs} +2 -2
  38. package/dist/{device-BHw0WlHl.mjs → device-B7lsC0K1.mjs} +8 -8
  39. package/dist/device-DVCwAFlX.mjs +727 -0
  40. package/dist/{device-capacity-BoN1jQkO.mjs → device-capacity-Ca_ke1LE.mjs} +13 -14
  41. package/dist/device-host-worker.mjs +38 -18
  42. package/dist/device-ios-CRUqPxKw.mjs +413 -0
  43. package/dist/{device-lease-Cu7PtQpZ.mjs → device-lease-DHOBXGgS.mjs} +135 -22
  44. package/dist/{device-lease-run-D38SnZtt.mjs → device-lease-run-CvDeHTkG.mjs} +4 -4
  45. package/dist/{device-pool-CP4rAsHx.mjs → device-pool-L2-5GxMy.mjs} +3 -3
  46. package/dist/{device-remote-B5GiUifk.mjs → device-remote-Bmg42063.mjs} +17 -68
  47. package/dist/doctor-C2MSuMMg.mjs +515 -0
  48. package/dist/{doctor-PRJH94v6.mjs → doctor-DPAqhVCK.mjs} +333 -744
  49. package/dist/{doctor-watchman-wZhyTdp4.mjs → doctor-watchman-CB4NnVZm.mjs} +13 -12
  50. package/dist/{simslim-D9LkJxIO.mjs → eas-build-Dw_ec2Km.mjs} +6 -152
  51. package/dist/{stim-installations-DbmUjsLv.mjs → eas-session-ledger-DZlC-Fq3.mjs} +57 -4
  52. package/dist/{error-diagnostics--v5HGOU0.mjs → error-diagnostics-Du7I_GuY.mjs} +17 -12
  53. package/dist/errors-DzkMYh8Q.mjs +205 -0
  54. package/dist/{exec-i8GDPwq9.mjs → exec-B81Qodjn.mjs} +40 -10
  55. package/dist/{gc-CyHMcKcn.mjs → gc-PWp34zGb.mjs} +21 -20
  56. package/dist/{device-ios-B2ruohE-.mjs → gradle-C2OEKCCI.mjs} +21 -650
  57. package/dist/{guide-dRaQJJk9.mjs → guide-Dai9lphv.mjs} +504 -117
  58. package/dist/{hosted-android-Bs7fytj7.mjs → hosted-android-DQ-Agsd8.mjs} +5 -5
  59. package/dist/{hosted-client-C5G4h_OJ.mjs → hosted-client-Io01s5By.mjs} +9 -3
  60. package/dist/{hosted-ios-Bik035eL.mjs → hosted-ios-DIxFnYp-.mjs} +3 -3
  61. package/dist/{hosted-logs-CI9Az9a4.mjs → hosted-logs-DP-ABU_M.mjs} +8 -8
  62. package/dist/{hosted-macos-DABm3ScO.mjs → hosted-macos-Aziw9T_9.mjs} +7 -6
  63. package/dist/{hosted-native-DbV2aUVw.mjs → hosted-native-BdAQi21M.mjs} +17 -14
  64. package/dist/{idle-shutdown-DYiMBn34.mjs → idle-shutdown-DdMqoSUB.mjs} +7 -7
  65. package/dist/{ios-DYUr9LGc.mjs → ios-BZ9dg2Fr.mjs} +8 -7
  66. package/dist/{ios-BWbHFZVz.mjs → ios-CXLIUxXJ.mjs} +180 -24
  67. package/dist/ios-CZuv6gRv.mjs +1112 -0
  68. package/dist/{ios-DCrkgXEM.mjs → ios-VGvNdsv2.mjs} +1 -1
  69. package/dist/{ios-device-CrW8aMb5.mjs → ios-device-B1B14xND.mjs} +1 -1
  70. package/dist/{ios-device-CJ_ZOGky.mjs → ios-device-BreZOpM0.mjs} +3 -3
  71. package/dist/ios-project-B9drt_aY.mjs +197 -0
  72. package/dist/{ios-state-By0XuLTQ.mjs → ios-state-CjrL2qv-.mjs} +1 -1
  73. package/dist/js-swap-C8icNYIY.mjs +432 -0
  74. package/dist/launch-DxH0THLP.mjs +745 -0
  75. package/dist/{react-native-ios-CSpIxexZ.mjs → launch-YrbqaIFj.mjs} +59 -612
  76. package/dist/{launch-verify-DCveSE6d.mjs → launch-verify-BzRdbuO1.mjs} +26 -7
  77. package/dist/{logs-Bao1HOMy.mjs → logs-DHaRNfg7.mjs} +11 -11
  78. package/dist/macos-C0iljhsN.mjs +2 -0
  79. package/dist/{macos-ciyBdTY5.mjs → macos-V_3Ui7g4.mjs} +42 -50
  80. package/dist/macos-run.mjs +1 -1
  81. package/dist/maintenance-run.mjs +13 -10
  82. package/dist/{metro-DI2AjPF_.mjs → metro-D_j9sN-B.mjs} +18 -5
  83. package/dist/{metro-gateway-BxpJLIwh.mjs → metro-gateway-BBJldOdi.mjs} +2 -2
  84. package/dist/miss-reason-BPAzZS0r.mjs +238 -0
  85. package/dist/{named-ports-B4iWH4E-.mjs → named-ports-C2lvoPsb.mjs} +3 -3
  86. package/dist/native-android-lHzaew7r.mjs +226 -0
  87. package/dist/native-gradle-inputs-BQnIdAlL.mjs +356 -0
  88. package/dist/{native-runtime-BGxsbfny.mjs → native-runtime-BRmWL7ru.mjs} +9 -8
  89. package/dist/native-xcode-inputs-eutS19jU.mjs +307 -0
  90. package/dist/native-xcode-ios-DP3cHLwu.mjs +434 -0
  91. package/dist/{ndjson-LGZAMsqa.mjs → ndjson-GOnbyvso.mjs} +6 -6
  92. package/dist/offload-worker.d.mts +41 -7
  93. package/dist/offload-worker.mjs +409 -74
  94. package/dist/{ownership-EB0ySdGK.mjs → ownership-DXzMMSx6.mjs} +2 -2
  95. package/dist/{placement-log-Dxe34rJm.mjs → placement-log-reFbjqEr.mjs} +1 -1
  96. package/dist/plan-BDk2AeY_.mjs +260 -0
  97. package/dist/plan-placement-D_YYlE6N.mjs +73 -0
  98. package/dist/{ports-ByPc0_97.mjs → ports-jzvAJXXL.mjs} +3 -3
  99. package/dist/{prebuild-BKJ3wJI1.mjs → prebuild-Bw44QxVh.mjs} +4 -4
  100. package/dist/{preview-BbSottmO.mjs → preview-BaBJ72Al.mjs} +50 -32
  101. package/dist/project-B8_duAzW.mjs +1067 -0
  102. package/dist/project-plan-BPwbLwQu.mjs +10 -0
  103. package/dist/{pull-request-IHNdMnG8.mjs → pull-request-vOhLNmIb.mjs} +1 -1
  104. package/dist/pull-requests.mjs +1 -1
  105. package/dist/react-native-android-chm1DMlA.mjs +921 -0
  106. package/dist/react-native-build-oAw6gQeW.mjs +239 -0
  107. package/dist/react-native-doctor-DYXoh-6I.mjs +78 -0
  108. package/dist/react-native-ios-SCTK_CT5.mjs +595 -0
  109. package/dist/{recordings-Bv8McZN4.mjs → recordings-Bk-VivUV.mjs} +1 -1
  110. package/dist/{reload-rAPdqeRR.mjs → reload-D34GkY78.mjs} +17 -13
  111. package/dist/remote-BU9UyhCJ.mjs +192 -0
  112. package/dist/{remote-cache-N3hAEaAL.mjs → remote-cache-Bsts6f6Y.mjs} +5 -5
  113. package/dist/result-BDoRk1k3.mjs +186 -0
  114. package/dist/{run-lAkle1-c.mjs → run-qvff78hv.mjs} +3 -3
  115. package/dist/{server-bare-CkIjnegq.mjs → server-bare-dWjAarki.mjs} +5 -5
  116. package/dist/server-command-7XIKlWtO.mjs +69 -0
  117. package/dist/{server-expo-n3tUi3Em.mjs → server-expo-CsYu2aKL.mjs} +107 -82
  118. package/dist/server-expo-DV43vep4.mjs +2 -0
  119. package/dist/{settings-CxgngUZk.mjs → settings-Bbztm4oV.mjs} +179 -129
  120. package/dist/{settings-2am7TSxp.mjs → settings-CT3E71-i.mjs} +6 -6
  121. package/dist/{state-lKh8shBp.d.mts → settings-vk3mJGSm.d.mts} +4 -2
  122. package/dist/settings.schema.json +84 -0
  123. package/dist/simslim-ChMfe8rg.mjs +149 -0
  124. package/dist/{slot-launch-HV_KtPIw.mjs → slot-launch-B9hn96wd.mjs} +6 -6
  125. package/dist/{stage-BgaZJVZY.mjs → stage-BNKKdDNc.mjs} +6 -3
  126. package/dist/start-CbQCSnXQ.mjs +2 -0
  127. package/dist/{start-DtxthMfI.mjs → start-CdvcTaXr.mjs} +97 -68
  128. package/dist/{state-Tp4uImDS.mjs → state-Boo0IMJq.mjs} +1 -1
  129. package/dist/{state-D2KtnALG.mjs → state-DI84A-uu.mjs} +29 -10
  130. package/dist/{stats-8EJ5ivFT.mjs → stats-BIIDrjA8.mjs} +5 -5
  131. package/dist/{status-BEeL6IKz.mjs → status-BDGHwkCs.mjs} +3 -1
  132. package/dist/{status-BOyp0CUl.mjs → status-DARMkc9q.mjs} +81 -293
  133. package/dist/status-watch-DvmI3SmN.mjs +296 -0
  134. package/dist/{stim-desktop-C65jk_q2.mjs → stim-desktop-8GQX2OV8.mjs} +1 -1
  135. package/dist/{stop-DRipAg-e.mjs → stop-CCZS-C9m.mjs} +2 -2
  136. package/dist/{stop-D0UIpZuh.mjs → stop-WQPQHLuy.mjs} +5 -5
  137. package/dist/{stop-zDlknFoz.mjs → stop-iNemHsJC.mjs} +21 -20
  138. package/dist/supervisor-run.d.mts +7 -2
  139. package/dist/supervisor-run.mjs +32 -20
  140. package/dist/{support-DsNaFg6D.mjs → support-CnE_SIjt.mjs} +17 -7
  141. package/dist/{support-CNqYkV0y.mjs → support-DCco5fut.mjs} +61 -8
  142. package/dist/{swiftpm-macos-aJzPAj1t.mjs → swiftpm-macos-afeNUExW.mjs} +5 -5
  143. package/dist/tailnet-CoJUb2tB.mjs +265 -0
  144. package/dist/toolchain-BIupIro1.mjs +2 -0
  145. package/dist/{toolchain-C3xSoyUl.mjs → toolchain-DqyDXs30.mjs} +206 -53
  146. package/dist/{trigger-CAhmG-4s.mjs → trigger-C0MWBi8O.mjs} +2 -2
  147. package/dist/trigger-SHLOWJ4O.mjs +2 -0
  148. package/dist/{warm-progress-pFLB2e6P.mjs → warm-progress-C3ASD6z5.mjs} +6 -6
  149. package/dist/{watchman-BL1IdOtq.mjs → watchman-CQl51YZu.mjs} +6 -6
  150. package/dist/{web-DbbrPUn0.mjs → web-B3eldHFH.mjs} +9 -13
  151. package/dist/web-BLXpOe6b.mjs +2 -0
  152. package/dist/web-run.mjs +10 -10
  153. package/dist/{workspace-state-CR9X86xJ.mjs → workspace-state-CSIFuuDa.mjs} +33 -33
  154. package/dist/{workspaces-Djb2gfi9.mjs → workspaces-D2xSHBDY.mjs} +1260 -1258
  155. package/dist/{worktree-BL4M1rhB.mjs → worktree-BxDqaTev.mjs} +84 -48
  156. package/dist/worktree-q7_OZNY0.mjs +2 -0
  157. package/package.json +8 -6
  158. package/shim/native-android.gradle +101 -0
  159. package/dist/android-BXc6BmEi.mjs +0 -3732
  160. package/dist/android-BvV1gGdN.mjs +0 -4
  161. package/dist/ios-ITYno9rO.mjs +0 -2241
  162. package/dist/macos-nIOATJmR.mjs +0 -2
  163. package/dist/metro-store-BAHFcJm6.mjs +0 -106
  164. package/dist/miss-reason-CjQCOKqq.mjs +0 -1743
  165. package/dist/plan-placement-BVY59qa8.mjs +0 -290
  166. package/dist/project-B5eQ1USs.mjs +0 -612
  167. package/dist/react-native-ios-BMh2i25I.mjs +0 -2
  168. package/dist/server-expo-DGsdUGX2.mjs +0 -2
  169. package/dist/start-D8NbxNBb.mjs +0 -2
  170. package/dist/trigger-CoalsMTV.mjs +0 -2
  171. package/dist/web-DFpFeZu5.mjs +0 -2
  172. package/dist/worktree-DV-A0LNL.mjs +0 -2
@@ -1,19 +1,94 @@
1
- import { t as getExecutor } from "./exec-i8GDPwq9.mjs";
1
+ import { t as getExecutor } from "./exec-B81Qodjn.mjs";
2
+ import { K as resolveProjectSettings } from "./settings-Bbztm4oV.mjs";
2
3
  import { n as stripAnsi, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
3
- import { v as resolvePackageJson } from "./project-B5eQ1USs.mjs";
4
- import { O as projectMetroSharedCache } from "./settings-CxgngUZk.mjs";
5
- import { a as metroStoreRoot, c as supervisorError, i as metroStoreConfirmedRoot, n as expoMetroConfigPath, o as registerMetroStore, r as expoMetroStoreEnv } from "./metro-store-BAHFcJm6.mjs";
6
- import { r as createNdjsonWriter } from "./ndjson-LGZAMsqa.mjs";
4
+ import { S as resolvePackageJson } from "./project-B8_duAzW.mjs";
5
+ import { r as createNdjsonWriter } from "./ndjson-GOnbyvso.mjs";
6
+ import { a as expoMetroStoreEnv, c as registerMetroStore, f as resolveMetroSharedCache, i as expoMetroConfigPath, n as supervisorError, o as metroStoreConfirmedRoot, s as metroStoreRoot } from "./errors-DzkMYh8Q.mjs";
7
7
  import { accessSync, constants, existsSync, readFileSync } from "node:fs";
8
- import { dirname, join, parse } from "node:path";
8
+ import { dirname, join } from "node:path";
9
9
  import { LOG_ROTATE_BYTES } from "@stim-cli/core";
10
- //#region src/supervisor/server-expo.ts
10
+ //#region src/supervisor/child-server.ts
11
11
  function delay(ms) {
12
12
  return new Promise((resolve) => {
13
13
  const timer = setTimeout(resolve, ms);
14
14
  if (typeof timer.unref === "function") timer.unref();
15
15
  });
16
16
  }
17
+ function superviseChildServer({ mode, child, log, toRecord, signal, alive = null, killTimeoutMs, onRecord = null }) {
18
+ let lastMsg = null;
19
+ let lastAt = 0;
20
+ const emit = (stream) => (chunk) => {
21
+ const record = toRecord(chunk, stream);
22
+ if (!record) return;
23
+ if (typeof record.requestId === "string") {
24
+ log.write(record);
25
+ onRecord?.(record);
26
+ return;
27
+ }
28
+ const now = Date.now();
29
+ if (record.msg === lastMsg && now - lastAt < 1e3) return;
30
+ lastMsg = typeof record.msg === "string" ? record.msg : null;
31
+ lastAt = now;
32
+ log.write(record);
33
+ onRecord?.(record);
34
+ };
35
+ const outReader = createLineReader(emit("stdout"));
36
+ const errReader = createLineReader(emit("stderr"));
37
+ child.stdout?.setEncoding?.("utf-8");
38
+ child.stderr?.setEncoding?.("utf-8");
39
+ child.stdout?.on("data", (chunk) => outReader.push(chunk));
40
+ child.stderr?.on("data", (chunk) => errReader.push(chunk));
41
+ let exited = false;
42
+ let exitInfo = null;
43
+ const listeners = [];
44
+ child.on("exit", (code, sig) => {
45
+ exited = true;
46
+ exitInfo = {
47
+ code,
48
+ signal: sig
49
+ };
50
+ outReader.flush();
51
+ errReader.flush();
52
+ for (const cb of listeners) cb(exitInfo);
53
+ });
54
+ child.on("error", (err) => {
55
+ if (exited) return;
56
+ exited = true;
57
+ exitInfo = {
58
+ code: null,
59
+ signal: null,
60
+ error: err
61
+ };
62
+ for (const cb of listeners) cb(exitInfo);
63
+ });
64
+ return {
65
+ mode,
66
+ serverPid: child.pid ?? null,
67
+ child,
68
+ onExit(cb) {
69
+ if (exited) {
70
+ cb(exitInfo);
71
+ return;
72
+ }
73
+ listeners.push(cb);
74
+ },
75
+ async close() {
76
+ const running = () => alive ? alive() : !exited && Boolean(child.pid);
77
+ const gone = async () => {
78
+ const deadline = Date.now() + killTimeoutMs;
79
+ while (running() && Date.now() < deadline) await delay(25);
80
+ return !running();
81
+ };
82
+ if (!running() || !signal("SIGTERM")) return;
83
+ if (!await gone()) {
84
+ signal("SIGKILL");
85
+ await gone();
86
+ }
87
+ }
88
+ };
89
+ }
90
+ //#endregion
91
+ //#region src/supervisor/server-expo.ts
17
92
  function resolveExpoBin(root) {
18
93
  const fromPackage = expoBinFromPackage(resolvePackageJson(root, "expo"));
19
94
  if (fromPackage) return {
@@ -56,15 +131,19 @@ function expoBinFromPackage(packageJsonPath, binName = "expo") {
56
131
  return isExecutableFile(file) ? file : null;
57
132
  }
58
133
  function findBinUpward(startDir, name, { exists = existsSync } = {}) {
134
+ const stop = repositoryRoot(startDir, exists) ?? startDir;
59
135
  let dir = startDir;
60
- const stop = parse(startDir).root;
61
136
  while (true) {
62
137
  const candidate = join(dir, "node_modules", ".bin", name);
63
138
  if (exists(candidate)) return candidate;
64
139
  if (dir === stop) return null;
65
- const parent = dirname(dir);
66
- if (parent === dir) return null;
67
- dir = parent;
140
+ dir = dirname(dir);
141
+ }
142
+ }
143
+ function repositoryRoot(startDir, exists) {
144
+ for (let dir = startDir;; dir = dirname(dir)) {
145
+ if (exists(join(dir, ".git"))) return dir;
146
+ if (dirname(dir) === dir) return null;
68
147
  }
69
148
  }
70
149
  function isExecutableFile(file) {
@@ -114,7 +193,7 @@ function cleanLine(line) {
114
193
  const parts = stripAnsi(line).split("\r");
115
194
  return (parts[parts.length - 1] ?? "").trimEnd();
116
195
  }
117
- function recordFromLine(line, { stream = "stdout" } = {}) {
196
+ function recordFromLine(line, { stream = "stdout", source = "expo" } = {}) {
118
197
  const msg = cleanLine(line);
119
198
  if (!msg.trim()) return null;
120
199
  if (stream === "stderr" && msg.startsWith("stim-bundle-response: ")) try {
@@ -133,7 +212,7 @@ function recordFromLine(line, { stream = "stdout" } = {}) {
133
212
  level: inferLevel(msg),
134
213
  msg,
135
214
  raw: true,
136
- event: stream === "stderr" ? "expo_stderr" : "expo_stdout"
215
+ event: `${source}_${stream === "stderr" ? "stderr" : "stdout"}`
137
216
  };
138
217
  if (isBundleMarker(msg)) record.marker = true;
139
218
  return record;
@@ -153,8 +232,8 @@ function parseExpoWaitingOnUrl(line) {
153
232
  if (HTTP_SCHEME_RE.test(url)) return url;
154
233
  return URL_SCHEME_RE.test(url) ? url.replace(/^[^:]+:/, "https:") : url;
155
234
  }
156
- function resolveMetroStoreInjection(root, { log, env }) {
157
- const sharedCache = projectMetroSharedCache(root);
235
+ function resolveMetroStoreInjection(root, { log, env, settings }) {
236
+ const sharedCache = resolveMetroSharedCache(settings);
158
237
  if (!sharedCache) log.write({
159
238
  src: "metro",
160
239
  level: "debug",
@@ -198,7 +277,7 @@ function resolveMetroStoreInjection(root, { log, env }) {
198
277
  });
199
278
  return additions;
200
279
  }
201
- async function startExpoServer({ root, port, logsDir, writer = null, spawnFn = null, killTimeoutMs = 5e3, tunnel = false, resetCache = false, onTunnelUrl = null, platform = process.platform }) {
280
+ async function startExpoServer({ root, port, logsDir, writer = null, spawnFn = null, killTimeoutMs = 5e3, tunnel = false, resetCache = false, onTunnelUrl = null, platform = process.platform, settings }) {
202
281
  const resolved = resolveExpoBin(root);
203
282
  if (!resolved) {
204
283
  const refusal = expoBinRefusal(root);
@@ -216,7 +295,8 @@ async function startExpoServer({ root, port, logsDir, writer = null, spawnFn = n
216
295
  ];
217
296
  const storeEnv = resolveMetroStoreInjection(root, {
218
297
  log,
219
- env: process.env
298
+ env: process.env,
299
+ settings: settings ?? resolveProjectSettings(root).settings
220
300
  });
221
301
  const [command, commandArgs] = platform === "win32" && resolved.source === "package" ? [process.execPath, [bin, ...args]] : [bin, args];
222
302
  const child = spawn(command, commandArgs, {
@@ -235,78 +315,23 @@ async function startExpoServer({ root, port, logsDir, writer = null, spawnFn = n
235
315
  ...tunnel ? { EXPO_UNSTABLE_TUNNEL_V2: "1" } : {}
236
316
  }
237
317
  });
238
- let lastMsg = null;
239
- let lastAt = 0;
240
318
  let tunnelUrlSeen = false;
241
- const emit = (stream) => (chunk) => {
242
- const record = recordFromLine(chunk, { stream });
243
- if (!record) return;
244
- const now = Date.now();
245
- if (record.msg === lastMsg && now - lastAt < 1e3) return;
246
- lastMsg = typeof record.msg === "string" ? record.msg : null;
247
- lastAt = now;
248
- log.write(record);
249
- if (tunnel && !tunnelUrlSeen && onTunnelUrl && typeof record.msg === "string") {
319
+ return superviseChildServer({
320
+ mode: "expo-child",
321
+ child,
322
+ log,
323
+ toRecord: (chunk, stream) => recordFromLine(chunk, { stream }),
324
+ signal: (sig) => child.kill(sig),
325
+ killTimeoutMs,
326
+ onRecord: (record) => {
327
+ if (!tunnel || tunnelUrlSeen || !onTunnelUrl || typeof record.msg !== "string") return;
250
328
  const url = parseExpoWaitingOnUrl(record.msg);
251
329
  if (url) {
252
330
  tunnelUrlSeen = true;
253
331
  onTunnelUrl(url);
254
332
  }
255
333
  }
256
- };
257
- const outReader = createLineReader(emit("stdout"));
258
- const errReader = createLineReader(emit("stderr"));
259
- child.stdout?.setEncoding?.("utf-8");
260
- child.stderr?.setEncoding?.("utf-8");
261
- child.stdout?.on("data", (chunk) => outReader.push(chunk));
262
- child.stderr?.on("data", (chunk) => errReader.push(chunk));
263
- let exited = false;
264
- let exitInfo = null;
265
- const listeners = [];
266
- child.on("exit", (code, signal) => {
267
- exited = true;
268
- exitInfo = {
269
- code,
270
- signal
271
- };
272
- outReader.flush();
273
- errReader.flush();
274
- for (const cb of listeners) cb(exitInfo);
275
- });
276
- child.on("error", (err) => {
277
- if (exited) return;
278
- exited = true;
279
- exitInfo = {
280
- code: null,
281
- signal: null,
282
- error: err
283
- };
284
- for (const cb of listeners) cb(exitInfo);
285
334
  });
286
- return {
287
- mode: "expo-child",
288
- serverPid: child.pid ?? null,
289
- child,
290
- onExit(cb) {
291
- if (exited) {
292
- cb(exitInfo);
293
- return;
294
- }
295
- listeners.push(cb);
296
- },
297
- async close() {
298
- if (exited || !child.pid) return;
299
- const dead = new Promise((resolve) => {
300
- child.once("exit", () => resolve());
301
- });
302
- if (!child.kill("SIGTERM")) return;
303
- await Promise.race([dead, delay(killTimeoutMs)]);
304
- if (!exited) {
305
- child.kill("SIGKILL");
306
- await Promise.race([dead, delay(killTimeoutMs)]);
307
- }
308
- }
309
- };
310
335
  }
311
336
  //#endregion
312
- export { expoProxyEnv as a, inferLevel as c, parseExpoWaitingOnUrl as d, recordFromLine as f, expoBinRefusal as i, isBundleMarker as l, expoBinFromPackage as n, expoSdkMajor as o, startExpoServer as p, expoBinPath as r, findBinUpward as s, cleanLine as t, isDevClientNavigationNotice as u };
337
+ export { expoProxyEnv as a, inferLevel as c, parseExpoWaitingOnUrl as d, recordFromLine as f, expoBinRefusal as i, isBundleMarker as l, superviseChildServer as m, expoBinFromPackage as n, expoSdkMajor as o, startExpoServer as p, expoBinPath as r, findBinUpward as s, cleanLine as t, isDevClientNavigationNotice as u };
@@ -0,0 +1,2 @@
1
+ import { p as startExpoServer } from "./server-expo-CsYu2aKL.mjs";
2
+ export { startExpoServer };
@@ -1,110 +1,12 @@
1
- import { t as getExecutor } from "./exec-i8GDPwq9.mjs";
2
- import { _ as loadConfig$1, d as getConfigDir, f as getConfigPath$1, h as getRepoSettings, m as getProjectSettings } from "./config-DC5jEtd1.mjs";
1
+ import { t as getExecutor } from "./exec-B81Qodjn.mjs";
2
+ import { _ as loadConfig$1, d as getConfigDir, f as getConfigPath$1, h as getRepoSettings, m as getProjectSettings } from "./config-D0LhTY3S.mjs";
3
3
  import { accessSync, chmodSync, constants, lstatSync, mkdirSync, mkdtempSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
4
4
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
- 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";
6
- import { ANDROID_COMPILER_CACHE_CHOICES, ANDROID_PCH_CHOICES, REMOTE_DEVICE_BACKENDS, SETTINGS, SETTING_GROUPS, TUNNEL_MODES, WEB_VIEWPORTS, acceptsShape, coerceSettingText, expectedShape, isLayeredSetting, settingDefinition, settingValueError } from "@stim-cli/core/state";
7
- import { tmpdir } from "node:os";
5
+ import { dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1, normalize, relative as relative$1, resolve as resolve$1, sep as sep$1 } from "path";
6
+ import { REMOTE_DEVICE_BACKENDS, SETTINGS, SETTING_GROUPS, TUNNEL_MODES, WEB_VIEWPORTS, acceptsShape, coerceSettingText, expectedShape, isLayeredSetting, settingDefinition, settingValueError } from "@stim-cli/core/state";
8
7
  import { chmodSync as chmodSync$1, existsSync as existsSync$1, lstatSync as lstatSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, realpathSync as realpathSync$1, statSync as statSync$1, utimesSync as utimesSync$1 } from "fs";
9
8
  import { buildCacheRoot } from "@stim-cli/core";
10
- import { createHash } from "node:crypto";
11
- //#region src/optimizations.ts
12
- function compilerCacheFallbackMessage({ fallback, compilerCache, file = null }) {
13
- const origin = fallback.fromEnvironment ? "the environment" : file;
14
- const backend = compilerCache === "none" ? "Android builds use no compiler cache, because optimizations.android.compilerCache is \"none\"." : "Android builds fall back to ccache when it is available.";
15
- return `${fallback.key}${origin ? ` in ${origin}` : ""} ${fallback.reason}. ${backend}`;
16
- }
17
- function optimizationBuildProfile(platform, options) {
18
- const selected = platform === "ios" ? {
19
- ...options.ios,
20
- swiftCompilationCache: options.ios.swiftCompilationCache ?? false
21
- } : { pch: options.android.pch };
22
- const defaults = platform === "ios" ? {
23
- compilationCache: true,
24
- swiftCompilationCache: false,
25
- prefixMapping: true
26
- } : { pch: "auto" };
27
- if (JSON.stringify(selected) === JSON.stringify(defaults)) return void 0;
28
- return `opt-${createHash("sha256").update(JSON.stringify(selected)).digest("hex").slice(0, 16)}`;
29
- }
30
- function resolveOptimizations(settings = {}, env = process.env) {
31
- function choice(path, choices, fallback) {
32
- const raw = optimizationValue(settings, path);
33
- if (raw === void 0) return fallback;
34
- if (typeof raw !== "string" || !choices.includes(raw)) throw new Error(`Invalid optimizations.${path}. Expected ${choices.join(", ")}.`);
35
- return raw;
36
- }
37
- const compiler = choice("android.compilerCache", ANDROID_COMPILER_CACHE_CHOICES, "auto");
38
- const fromEnvironment = Boolean(env.STIM_ANDROID_CAS_TOOLCHAIN);
39
- const manifest = env.STIM_ANDROID_CAS_TOOLCHAIN || optimizationValue(settings, "android.casToolchain");
40
- let fallback = null;
41
- let toolchain = null;
42
- if (manifest !== void 0) {
43
- if (typeof manifest === "string" && isAbsolute(manifest) && !/[\r\n\0]/.test(manifest)) toolchain = manifest;
44
- else fallback = {
45
- key: fromEnvironment ? "STIM_ANDROID_CAS_TOOLCHAIN" : "optimizations.android.casToolchain",
46
- reason: `is ${JSON.stringify(manifest)}, which is not an absolute path to a toolchain JSON manifest`,
47
- fromEnvironment,
48
- manifest: null
49
- };
50
- }
51
- let compilerCache = compiler === "auto" ? toolchain ? "cas" : "ccache" : compiler;
52
- if (compilerCache === "cas" && !toolchain) {
53
- fallback ??= {
54
- key: "optimizations.android.compilerCache",
55
- reason: "is \"cas\", but no optimizations.android.casToolchain or STIM_ANDROID_CAS_TOOLCHAIN names the toolchain manifest",
56
- fromEnvironment: false,
57
- manifest: null
58
- };
59
- compilerCache = "ccache";
60
- }
61
- return {
62
- buildCache: optimizationBoolean(settings, "buildCache"),
63
- remoteBuildCache: optimizationBoolean(settings, "remoteBuildCache"),
64
- releaseBundleSwap: optimizationBoolean(settings, "releaseBundleSwap"),
65
- metroSharedCache: optimizationBoolean(settings, "metroSharedCache"),
66
- metroWarmup: optimizationBoolean(settings, "metroWarmup"),
67
- ios: {
68
- compilationCache: optimizationBoolean(settings, "ios.compilationCache"),
69
- swiftCompilationCache: optimizationValue(settings, "ios.swiftCompilationCache") === void 0 ? null : optimizationBoolean(settings, "ios.swiftCompilationCache"),
70
- prefixMapping: optimizationBoolean(settings, "ios.prefixMapping")
71
- },
72
- android: {
73
- compilerCache,
74
- casToolchain: toolchain,
75
- compilerCacheFallback: fallback,
76
- pch: choice("android.pch", ANDROID_PCH_CHOICES, "auto"),
77
- gradleBuildCache: optimizationBoolean(settings, "android.gradleBuildCache"),
78
- targetAbiOnly: optimizationBoolean(settings, "android.targetAbiOnly")
79
- }
80
- };
81
- }
82
- function artifactCachePolicy(options, reuse, release) {
83
- return {
84
- read: reuse && options.buildCache && (!release || options.releaseBundleSwap),
85
- write: options.buildCache,
86
- remote: options.buildCache && options.remoteBuildCache
87
- };
88
- }
89
- function optimizationValue(settings, path) {
90
- let node = settings;
91
- for (const key of ["optimizations", ...path.split(".")]) {
92
- if (node === void 0) return void 0;
93
- if (!node || typeof node !== "object" || Array.isArray(node)) throw new Error("Invalid optimizations setting. Expected nested objects.");
94
- node = node[key];
95
- }
96
- return node;
97
- }
98
- function optimizationBoolean(settings, path, fallback = true) {
99
- const raw = optimizationValue(settings, path);
100
- if (raw === void 0) return fallback;
101
- if (typeof raw !== "boolean") throw new Error(`Invalid optimizations.${path}. Expected true or false.`);
102
- return raw;
103
- }
104
- function resolveMetroSharedCache(settings) {
105
- return optimizationBoolean(settings, "metroSharedCache");
106
- }
107
- //#endregion
9
+ import { tmpdir } from "node:os";
108
10
  //#region src/temporary.ts
109
11
  function existingPath(path) {
110
12
  const missing = [];
@@ -904,8 +806,11 @@ function mergeSettingsLayers(layers) {
904
806
  const out = {};
905
807
  for (const layer of layers) {
906
808
  if (!isPlainObject(layer)) continue;
907
- for (const [key, value] of Object.entries(layer)) if (isPlainObject(value) && isPlainObject(out[key])) out[key] = mergeSettingsLayers([out[key], value]);
908
- else if (!(key in out)) out[key] = value;
809
+ for (const [key, value] of Object.entries(layer)) {
810
+ if (key === "__proto__") continue;
811
+ if (isPlainObject(value) && isPlainObject(out[key])) out[key] = mergeSettingsLayers([out[key], value]);
812
+ else if (!(key in out)) out[key] = value;
813
+ }
909
814
  }
910
815
  return out;
911
816
  }
@@ -933,7 +838,7 @@ function settingShapeErrors(settings) {
933
838
  if (SETTINGS_WITH_RESOLVE_TIME_FALLBACK.has(setting.key)) continue;
934
839
  const value = settingValueAt(settings, setting.key);
935
840
  if (value === void 0) continue;
936
- const expected = setting.key === "ios.remote" || setting.key === "android.remote" || setting.type.kind === "number" && !BOUNDS_CHECKED_WHEN_USED.has(setting.key) ? settingValueError(setting, value) : acceptsShape(setting, value) ? null : expectedShape(setting.type);
841
+ const expected = setting.key === "ios.remote" || setting.key === "android.remote" || setting.key === "android.deviceProfile" || setting.type.kind === "number" && !BOUNDS_CHECKED_WHEN_USED.has(setting.key) ? settingValueError(setting, value) : acceptsShape(setting, value) ? null : expectedShape(setting.type);
937
842
  if (expected) errors.push(`Invalid ${setting.key} setting ${JSON.stringify(value)}. Expected ${expected}.`);
938
843
  }
939
844
  return errors;
@@ -1087,6 +992,12 @@ function pathEscapesRoot(root, path) {
1087
992
  const child = relative$1(root, path);
1088
993
  return child === ".." || child.startsWith(`..${sep$1}`) || isAbsolute$1(child);
1089
994
  }
995
+ function realpathInside(root, path) {
996
+ const base = realpathSync$1(root);
997
+ const real = realpathSync$1(resolve$1(base, path));
998
+ if (pathEscapesRoot(base, real)) throw new Error(`it resolves outside ${base}`);
999
+ return real;
1000
+ }
1090
1001
  function androidAvdConfigSetting(settings, settingsRoot) {
1091
1002
  if (!isPlainObject(settings) || !isPlainObject(settings.android)) return {};
1092
1003
  const android = settings.android;
@@ -1126,6 +1037,132 @@ function androidAvdConfigSettingError(settings, projectPath) {
1126
1037
  return String(error?.message || error);
1127
1038
  }
1128
1039
  }
1040
+ function iosProjectDirSetting(settings, appRoot) {
1041
+ const value = iosString(settings, "projectPath");
1042
+ if (value === void 0 || typeof value === "string" && normalize(value).replace(/[\\/]+$/, "") === "ios") return {
1043
+ dir: join$1(appRoot, "ios"),
1044
+ relative: "ios",
1045
+ custom: false
1046
+ };
1047
+ if (typeof value !== "string" || !value.trim() || value !== value.trim() || /[\r\n\0]/.test(value) || isAbsolute$1(value)) throw new Error("Invalid ios.projectPath setting. Expected a directory path relative to the app directory.");
1048
+ try {
1049
+ const root = realpathSync$1(appRoot);
1050
+ const candidate = resolve$1(root, value);
1051
+ if (pathEscapesRoot(root, candidate)) throw new Error("path escapes the app directory");
1052
+ const dir = realpathSync$1(candidate);
1053
+ if (pathEscapesRoot(root, dir)) throw new Error("symlink target escapes the app directory");
1054
+ if (!statSync$1(dir).isDirectory()) throw new Error("path is not a directory");
1055
+ if (!readdirSync$1(dir).some((name) => name.endsWith(".xcworkspace") || name.endsWith(".xcodeproj"))) throw new Error("it holds no .xcworkspace or .xcodeproj");
1056
+ return {
1057
+ dir,
1058
+ relative: relative$1(root, dir) || ".",
1059
+ custom: true
1060
+ };
1061
+ } catch (error) {
1062
+ throw new Error(`Could not use ios.projectPath ${value}: ${String(error?.message || error)}`, { cause: error });
1063
+ }
1064
+ }
1065
+ function iosProjectDirSettingError(settings, appRoot) {
1066
+ try {
1067
+ iosProjectDirSetting(settings, appRoot);
1068
+ return null;
1069
+ } catch (error) {
1070
+ return String(error?.message || error);
1071
+ }
1072
+ }
1073
+ const INVALID_IOS_PROJECT_DIR = "\0invalid ios.projectPath";
1074
+ const DEFAULT_ANDROID_GRADLE_ROOT = "android";
1075
+ const DEFAULT_ANDROID_MODULE = ":app";
1076
+ const ANDROID_MODULE = /^(:(?!\.\.?(?::|$))[A-Za-z0-9._-]+)+$/;
1077
+ function androidString(settings, key) {
1078
+ if (!isPlainObject(settings) || !isPlainObject(settings.android)) return void 0;
1079
+ return settings.android[key];
1080
+ }
1081
+ function hasGradleScript(dir, name) {
1082
+ return existsSync$1(join$1(dir, `${name}.gradle`)) || existsSync$1(join$1(dir, `${name}.gradle.kts`));
1083
+ }
1084
+ function androidLayoutSetting(settings, appRoot, bound) {
1085
+ const rootValue = androidString(settings, "gradleRoot");
1086
+ const moduleValue = androidString(settings, "module");
1087
+ if (!(rootValue !== void 0 && rootValue !== "android" || moduleValue !== void 0 && moduleValue !== ":app")) {
1088
+ const gradleRoot = join$1(appRoot, DEFAULT_ANDROID_GRADLE_ROOT);
1089
+ return {
1090
+ gradleRoot,
1091
+ gradleRootRelative: DEFAULT_ANDROID_GRADLE_ROOT,
1092
+ module: DEFAULT_ANDROID_MODULE,
1093
+ moduleDir: join$1(gradleRoot, "app"),
1094
+ custom: false
1095
+ };
1096
+ }
1097
+ const rawRoot = rootValue ?? "android";
1098
+ if (typeof rawRoot !== "string" || !rawRoot.trim() || rawRoot !== rawRoot.trim() || /[\r\n\0]/.test(rawRoot) || isAbsolute$1(rawRoot)) throw new Error("Invalid android.gradleRoot setting. Expected a directory path relative to the app directory.");
1099
+ const module = moduleValue ?? ":app";
1100
+ if (typeof module !== "string" || !ANDROID_MODULE.test(module)) throw new Error(`Invalid android.module setting ${JSON.stringify(module)}. Expected a Gradle project path such as :app.`);
1101
+ try {
1102
+ const app = realpathSync$1(appRoot);
1103
+ const limit = realpathSync$1(bound);
1104
+ const gradleRoot = realpathSync$1(resolve$1(app, rawRoot));
1105
+ if (pathEscapesRoot(limit, gradleRoot)) throw new Error(`it resolves outside ${limit}`);
1106
+ if (!statSync$1(gradleRoot).isDirectory()) throw new Error("it is not a directory");
1107
+ if (!hasGradleScript(gradleRoot, "settings")) throw new Error("it holds no settings.gradle or settings.gradle.kts");
1108
+ const mapped = join$1(gradleRoot, ...module.split(":").filter(Boolean));
1109
+ if (!hasGradleScript(mapped, "build")) throw new Error(`module ${module} has no build.gradle or build.gradle.kts at ${mapped}`);
1110
+ const moduleDir = realpathSync$1(mapped);
1111
+ if (pathEscapesRoot(limit, moduleDir)) throw new Error(`module ${module} resolves outside ${limit}`);
1112
+ return {
1113
+ gradleRoot,
1114
+ gradleRootRelative: relative$1(app, gradleRoot).split(sep$1).join("/") || ".",
1115
+ module,
1116
+ moduleDir,
1117
+ custom: true
1118
+ };
1119
+ } catch (error) {
1120
+ throw new Error(`Could not use android.gradleRoot ${rawRoot}: ${String(error?.message || error)}`, { cause: error });
1121
+ }
1122
+ }
1123
+ function androidLayoutSettingError(settings, appRoot, bound) {
1124
+ try {
1125
+ androidLayoutSetting(settings, appRoot, bound);
1126
+ return null;
1127
+ } catch (error) {
1128
+ return String(error?.message || error);
1129
+ }
1130
+ }
1131
+ const INVALID_ANDROID_GRADLE_ROOT = "\0invalid android.gradleRoot";
1132
+ function defaultAndroidLayout(appRoot) {
1133
+ return androidLayoutSetting({}, appRoot, appRoot);
1134
+ }
1135
+ function resolveAndroidLayout(settings, root, bound) {
1136
+ if (settings === null) return defaultAndroidLayout(root);
1137
+ try {
1138
+ return androidLayoutSetting(settings, root, bound);
1139
+ } catch {
1140
+ const gradleRoot = join$1(root, INVALID_ANDROID_GRADLE_ROOT);
1141
+ return {
1142
+ gradleRoot,
1143
+ gradleRootRelative: INVALID_ANDROID_GRADLE_ROOT,
1144
+ module: DEFAULT_ANDROID_MODULE,
1145
+ moduleDir: join$1(gradleRoot, "app"),
1146
+ custom: true
1147
+ };
1148
+ }
1149
+ }
1150
+ function resolveIosProjectDir(settings, root) {
1151
+ if (settings === null) return {
1152
+ dir: join$1(root, "ios"),
1153
+ relative: "ios",
1154
+ custom: false
1155
+ };
1156
+ try {
1157
+ return iosProjectDirSetting(settings, root);
1158
+ } catch {
1159
+ return {
1160
+ dir: join$1(root, INVALID_IOS_PROJECT_DIR),
1161
+ relative: INVALID_IOS_PROJECT_DIR,
1162
+ custom: true
1163
+ };
1164
+ }
1165
+ }
1129
1166
  function iosSimSlimProfileSetting(settings, settingsRoot) {
1130
1167
  if (!isPlainObject(settings) || !isPlainObject(settings.ios) || !("simslimProfile" in settings.ios)) return null;
1131
1168
  const value = settings.ios.simslimProfile;
@@ -1276,18 +1313,38 @@ function settingFile(context, dottedKey) {
1276
1313
  function resolveSettings(context) {
1277
1314
  return mergeSettingsLayers(settingsLayers(context).map((layer) => layer.settings));
1278
1315
  }
1279
- function settingsForProject(root) {
1280
- return resolveSettings({
1316
+ function projectSettingsContext(root, git = {
1317
+ gitCommonDir,
1318
+ repoRoot
1319
+ }) {
1320
+ return {
1281
1321
  projectPath: root,
1282
- gitCommonDir: gitCommonDir(root),
1283
- repoRoot: repoRoot(root) ?? root
1284
- });
1322
+ gitCommonDir: git.gitCommonDir(root),
1323
+ repoRoot: git.repoRoot(root)
1324
+ };
1325
+ }
1326
+ function resolveProjectSettings(root) {
1327
+ const context = projectSettingsContext(root);
1328
+ return {
1329
+ context,
1330
+ settings: resolveSettings(context)
1331
+ };
1285
1332
  }
1286
- function projectOptimizations(root) {
1287
- return resolveOptimizations(settingsForProject(root));
1333
+ const METRO_COMMAND_PORT = "{port}";
1334
+ function metroCommandSetting(settings) {
1335
+ const value = settingValueAt(settings, "metro.command");
1336
+ if (!Array.isArray(value) || !value.every((arg) => typeof arg === "string")) return null;
1337
+ return typeof value[0] === "string" && value[0].trim() !== "" ? value : null;
1338
+ }
1339
+ function metroCommandSettingError(settings) {
1340
+ if (settingValueAt(settings, "metro.command") === void 0) return null;
1341
+ const command = metroCommandSetting(settings);
1342
+ if (!command) return "metro.command must be a non-empty array of strings whose first entry is the program to run.";
1343
+ if (!command.some((arg) => arg.includes("{port}"))) return `metro.command must pass ${METRO_COMMAND_PORT} so the dev server listens on this workspace's reserved port.`;
1344
+ return null;
1288
1345
  }
1289
- function projectMetroSharedCache(root) {
1290
- return resolveMetroSharedCache(settingsForProject(root));
1346
+ function runnableMetroCommand(settings) {
1347
+ return metroCommandSettingError(settings) ? null : metroCommandSetting(settings);
1291
1348
  }
1292
1349
  function cacheBlock(layer) {
1293
1350
  if (!isPlainObject(layer) || !isPlainObject(layer.cache)) return null;
@@ -1374,16 +1431,9 @@ function deviceIdleShutdownMinutesSetting(settings) {
1374
1431
  function deviceReclaimIdleMinutesSetting(settings) {
1375
1432
  return minutesSetting(settings, "devices", "reclaimIdleMinutes");
1376
1433
  }
1377
- function projectMaintenancePinned(root) {
1378
- try {
1379
- const value = settingValueAt(settingsForProject(root), "maintenance.keep");
1380
- return value !== void 0 && value !== false;
1381
- } catch {
1382
- return true;
1383
- }
1384
- }
1385
- function projectDeviceReclaimIdleMinutes(root) {
1386
- return deviceReclaimIdleMinutesSetting(settingsForProject(root));
1434
+ function maintenanceKeepSetting(settings) {
1435
+ const value = settingValueAt(settings, "maintenance.keep");
1436
+ return value !== void 0 && value !== false;
1387
1437
  }
1388
1438
  function tunnelModeSetting(settings) {
1389
1439
  const block = settings.metro;
@@ -1393,15 +1443,15 @@ function tunnelModeSetting(settings) {
1393
1443
  }
1394
1444
  function metroTunnelSettingError(settings) {
1395
1445
  const block = settings.metro;
1396
- if (!isPlainObject(block) || !("ngrokUrl" in block)) return null;
1446
+ if (!isPlainObject(block) || block.ngrokUrl === void 0) return null;
1397
1447
  if (block.tunnel !== "ngrok") return "metro.ngrokUrl requires metro.tunnel to be \"ngrok\".";
1398
1448
  if (normalizedHttpsUrl(block.ngrokUrl) === null) return "metro.ngrokUrl must be a valid HTTPS URL.";
1399
1449
  return null;
1400
1450
  }
1401
- function metroPortSetting(projectPath, env = process.env) {
1451
+ function metroPortSetting(settings, env = process.env) {
1402
1452
  const setting = settingDefinition("metro.port");
1403
1453
  const fromEnv = env.STIM_METRO_PORT?.trim();
1404
- const value = fromEnv ? coerceSettingText(setting, fromEnv) : settingValueAt(settingsForProject(projectPath), "metro.port");
1454
+ const value = fromEnv ? coerceSettingText(setting, fromEnv) : settingValueAt(settings, "metro.port");
1405
1455
  if (value === void 0) return {
1406
1456
  port: null,
1407
1457
  error: null
@@ -1450,4 +1500,4 @@ function webSettings(settings) {
1450
1500
  };
1451
1501
  }
1452
1502
  //#endregion
1453
- export { depsOutOfSync as $, publicUrlSetting as A, settingShapeErrors as B, metroWarmupUrlSetting as C, removeTemporaryEntry as Ct, projectMaintenancePinned as D, optimizationBuildProfile as Dt, projectDeviceReclaimIdleMinutes as E, compilerCacheFallbackMessage as Et, resolveCacheProviderConfig as F, webSettings as G, settingsLayers as H, resolveSettings as I, planMetroReach as J, PUBLIC_METRO_ENV as K, settingFile as L, remoteAndroidSetting as M, remoteEasFallbackSetting as N, projectMetroSharedCache as O, resolveOptimizations as Ot, remoteIosSetting as P, deleteBranch as Q, settingOrigin as R, metroTunnelSettingError as S, makeTemporaryDirectory as St, parseIosRemote as T, artifactCachePolicy as Tt, tunnelModeSetting as U, settingValueAt as V, unknownSettingKeys as W, branchExists as X, resolveTailscaleBinary as Y, cloneIgnoredEntries as Z, iosSigningIdentitySha1SettingError as _, restoreFile as _t, androidAvdConfigSetting as a, hasRemote as at, metroIdleStopMinutesSetting as b, warmWorktreePaths as bt, androidDataPartitionSizeGbSetting as c, linkedWorktreesOnDisk as ct, deviceIdleShutdownMinutesSetting as d, podsOutOfSync as dt, dirtyFingerprintFiles as et, iosLanHostSetting as f, readWorktreeExclude as ft, iosSigningIdentitySha1Setting as g, resolveSourceCheckout as gt, iosSigningIdentitySettingError as h, resolveFullRef as ht, SETTING_SHAPE_REMEDY as i, hasPopulatedSubmodules as it, readCommittedSettings as j, projectOptimizations as k, androidDataPartitionSizeGbSettingError as l, listWorktrees as lt, iosSigningIdentitySetting as m, repoRoot as mt, PATH_SETTINGS as n, gitCommonDir as nt, androidAvdConfigSettingError as o, hasUncommittedWork as ot, iosLanHostSettingError as p, removeWorktree as pt, detectProviders as q, SETTINGS_WITH_RESOLVE_TIME_FALLBACK as r, gitCommonDirOnDisk as rt, androidDataPartitionSizeBytes as s, isPodInstallChurn as st, ANDROID_AVD_CONFIG_HELP as t, dirtyPaths as tt, cacheProviderSettingError as u, locallyKnownUpstream as ut, iosSimSlimProfileSetting as v, sourceCheckoutOf as vt, ngrokUrlSetting as w, temporaryRoot as wt, metroPortSetting as x, filesystemDevice as xt, mergeSettingsLayers as y, unpushedCommits as yt, settingOriginScope as z };
1503
+ export { settingValueAt as $, metroIdleStopMinutesSetting as A, sourceCheckoutOf as At, remoteAndroidSetting as B, iosSigningIdentitySha1Setting as C, podsOutOfSync as Ct, mergeSettingsLayers as D, resolveFullRef as Dt, maintenanceKeepSetting as E, repoRoot as Et, parseIosRemote as F, removeTemporaryEntry as Ft, resolveIosProjectDir as G, remoteIosSetting as H, projectSettingsContext as I, temporaryRoot as It, runnableMetroCommand as J, resolveProjectSettings as K, publicUrlSetting as L, metroTunnelSettingError as M, warmWorktreePaths as Mt, metroWarmupUrlSetting as N, filesystemDevice as Nt, metroCommandSetting as O, resolveSourceCheckout as Ot, ngrokUrlSetting as P, makeTemporaryDirectory as Pt, settingShapeErrors as Q, readCommittedSettings as R, iosSigningIdentitySettingError as S, locallyKnownUpstream as St, iosSimSlimProfileSetting as T, removeWorktree as Tt, resolveAndroidLayout as U, remoteEasFallbackSetting as V, resolveCacheProviderConfig as W, settingOrigin as X, settingFile as Y, settingOriginScope as Z, iosLanHostSetting as _, hasRemote as _t, SETTING_SHAPE_REMEDY as a, detectProviders as at, iosProjectDirSettingError as b, linkedWorktreesOnDisk as bt, androidDataPartitionSizeBytes as c, branchExists as ct, androidLayoutSetting as d, depsOutOfSync as dt, settingsLayers as et, androidLayoutSettingError as f, dirtyFingerprintFiles as ft, deviceReclaimIdleMinutesSetting as g, hasPopulatedSubmodules as gt, deviceIdleShutdownMinutesSetting as h, gitCommonDirOnDisk as ht, SETTINGS_WITH_RESOLVE_TIME_FALLBACK as i, PUBLIC_METRO_ENV as it, metroPortSetting as j, unpushedCommits as jt, metroCommandSettingError as k, restoreFile as kt, androidDataPartitionSizeGbSetting as l, cloneIgnoredEntries as lt, defaultAndroidLayout as m, gitCommonDir as mt, METRO_COMMAND_PORT as n, unknownSettingKeys as nt, androidAvdConfigSetting as o, planMetroReach as ot, cacheProviderSettingError as p, dirtyPaths as pt, resolveSettings as q, PATH_SETTINGS as r, webSettings as rt, androidAvdConfigSettingError as s, resolveTailscaleBinary as st, ANDROID_AVD_CONFIG_HELP as t, tunnelModeSetting as tt, androidDataPartitionSizeGbSettingError as u, deleteBranch as ut, iosLanHostSettingError as v, hasUncommittedWork as vt, iosSigningIdentitySha1SettingError as w, readWorktreeExclude as wt, iosSigningIdentitySetting as x, listWorktrees as xt, iosProjectDirSetting as y, isPodInstallChurn as yt, realpathInside as z };