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.
Files changed (178) hide show
  1. package/README.md +21 -4
  2. package/dist/{activity-BxnkDZDi.mjs → activity-BrGh9EAI.mjs} +3 -3
  3. package/dist/{agent-device-usage-output-CZ66YA4T.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-DC3ZLuw0.mjs → android-BsYNR81W.mjs} +103 -45
  7. package/dist/{android-CBwCNicO.mjs → android-CK3fpEyZ.mjs} +2 -2
  8. package/dist/{android-cas-BBFyr9CA.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-D0VtFhqk.mjs → api-zuX3ynYN.mjs} +9 -7
  12. package/dist/api.d.mts +104 -4
  13. package/dist/api.mjs +1 -1
  14. package/dist/{app-install-D-PUENug.mjs → app-install-0LgaQRT7.mjs} +39 -18
  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/{web-DTU0uiWj.mjs → browser-web-CzBmhmgs.mjs} +136 -174
  19. package/dist/{budget-DIhL2BKx.mjs → budget-BONq9vXY.mjs} +3096 -3244
  20. package/dist/build-BixRI6sB.mjs +462 -0
  21. package/dist/build-plan-CP0tckNU.mjs +553 -0
  22. package/dist/{cache-manifest-BI7ozfmH.mjs → cache-manifest-qA3dT2x2.mjs} +1 -1
  23. package/dist/cache-manifest.mjs +1 -1
  24. package/dist/ccache-CjHQ5F8g.mjs +142 -0
  25. package/dist/{chrome-DJnmnS5q.mjs → chrome-Dp2lMLBe.mjs} +1 -1
  26. package/dist/{cli-DNJMwADx.mjs → cli-B2R8wH1F.mjs} +20 -20
  27. package/dist/cli.mjs +1 -1
  28. package/dist/{client-DzhapoMB.mjs → client-BLy1Pw2V.mjs} +172 -165
  29. package/dist/collector-run.d.mts +10 -0
  30. package/dist/collector-run.mjs +7 -7
  31. package/dist/{command-output-CBtzyIgF.mjs → command-output-BjPN99ZB.mjs} +2 -2
  32. package/dist/{config-CgYMahz7.mjs → config-D0LhTY3S.mjs} +13 -13
  33. package/dist/{created-devices-DKbeWsiK.mjs → created-devices-HU_DAb_Q.mjs} +7 -6
  34. package/dist/{dependency-state-xKoOypBm.mjs → dependency-state-B2m8eqsS.mjs} +2 -2
  35. package/dist/{detached-entry-BLfcgc4O.mjs → detached-entry-BXq4fIl-.mjs} +1 -1
  36. package/dist/{dev-client-CLnIH02x.mjs → dev-client-ClATaJoH.mjs} +2 -2
  37. package/dist/{device-D7oCHGWi.mjs → device-B7lsC0K1.mjs} +8 -8
  38. package/dist/device-DVCwAFlX.mjs +727 -0
  39. package/dist/{device-capacity-EUOOYr8T.mjs → device-capacity-Ca_ke1LE.mjs} +25 -15
  40. package/dist/device-host-worker.mjs +38 -18
  41. package/dist/{device-ios-6MewKKnc.mjs → device-ios-CRUqPxKw.mjs} +12 -11
  42. package/dist/{device-lease-Co-EwifR.mjs → device-lease-DHOBXGgS.mjs} +135 -22
  43. package/dist/{device-lease-run-w1xhCqIW.mjs → device-lease-run-CvDeHTkG.mjs} +4 -4
  44. package/dist/{device-pool-F5WvRPtD.mjs → device-pool-L2-5GxMy.mjs} +3 -3
  45. package/dist/{device-remote-tHbd1fKy.mjs → device-remote-Bmg42063.mjs} +17 -69
  46. package/dist/doctor-C2MSuMMg.mjs +515 -0
  47. package/dist/{doctor-B9ckYUoU.mjs → doctor-DPAqhVCK.mjs} +334 -721
  48. package/dist/doctor-watchman-CB4NnVZm.mjs +260 -0
  49. package/dist/{simslim-EHBQ2R4G.mjs → eas-build-Dw_ec2Km.mjs} +6 -152
  50. package/dist/{stim-installations-aR_WsNOA.mjs → eas-session-ledger-DZlC-Fq3.mjs} +57 -4
  51. package/dist/{error-diagnostics-BRXOqDzG.mjs → error-diagnostics-Du7I_GuY.mjs} +17 -12
  52. package/dist/errors-DzkMYh8Q.mjs +205 -0
  53. package/dist/{exec-DWhCZDGm.mjs → exec-B81Qodjn.mjs} +69 -15
  54. package/dist/{gc-Dh6mXOKC.mjs → gc-PWp34zGb.mjs} +23 -22
  55. package/dist/{gradle-C6GKnunQ.mjs → gradle-C2OEKCCI.mjs} +50 -125
  56. package/dist/{guide-tm6igAaa.mjs → guide-Dai9lphv.mjs} +862 -538
  57. package/dist/{hosted-android-Bv285t_t.mjs → hosted-android-DQ-Agsd8.mjs} +5 -5
  58. package/dist/{hosted-client-C-rMtppF.mjs → hosted-client-Io01s5By.mjs} +9 -3
  59. package/dist/{hosted-ios-D5mNmtj4.mjs → hosted-ios-DIxFnYp-.mjs} +3 -3
  60. package/dist/{hosted-logs-DN8Iz4mJ.mjs → hosted-logs-DP-ABU_M.mjs} +8 -8
  61. package/dist/{hosted-macos-B-wEjlwG.mjs → hosted-macos-Aziw9T_9.mjs} +7 -6
  62. package/dist/{hosted-native-DeeOG8xZ.mjs → hosted-native-BdAQi21M.mjs} +23 -16
  63. package/dist/{idle-shutdown-jjtxAn5C.mjs → idle-shutdown-DdMqoSUB.mjs} +7 -7
  64. package/dist/ios-BZ9dg2Fr.mjs +9 -0
  65. package/dist/{ios-B3SbOWdw.mjs → ios-CXLIUxXJ.mjs} +180 -24
  66. package/dist/ios-CZuv6gRv.mjs +1112 -0
  67. package/dist/{ios-Bvbf0SGY.mjs → ios-VGvNdsv2.mjs} +1 -1
  68. package/dist/{ios-device-Bn7jWDB1.mjs → ios-device-B1B14xND.mjs} +1 -1
  69. package/dist/{ios-device-vOaByMhw.mjs → ios-device-BreZOpM0.mjs} +3 -3
  70. package/dist/ios-project-B9drt_aY.mjs +197 -0
  71. package/dist/{ios-state-B2pES6Vs.mjs → ios-state-CjrL2qv-.mjs} +1 -1
  72. package/dist/js-swap-C8icNYIY.mjs +432 -0
  73. package/dist/launch-DxH0THLP.mjs +745 -0
  74. package/dist/launch-YrbqaIFj.mjs +1530 -0
  75. package/dist/{launch-verify-hskNcWkZ.mjs → launch-verify-BzRdbuO1.mjs} +26 -7
  76. package/dist/{logs-1Ylc1Ri6.mjs → logs-DHaRNfg7.mjs} +11 -11
  77. package/dist/macos-C0iljhsN.mjs +2 -0
  78. package/dist/{macos-DF6C7wUU.mjs → macos-V_3Ui7g4.mjs} +125 -155
  79. package/dist/macos-run.mjs +1 -1
  80. package/dist/maintenance-run.mjs +13 -10
  81. package/dist/{metro-Dh8T4Ytd.mjs → metro-D_j9sN-B.mjs} +209 -43
  82. package/dist/{metro-gateway-CEUw9RMH.mjs → metro-gateway-BBJldOdi.mjs} +2 -2
  83. package/dist/miss-reason-BPAzZS0r.mjs +238 -0
  84. package/dist/{named-ports-BFIz1Shz.mjs → named-ports-C2lvoPsb.mjs} +12 -5
  85. package/dist/native-android-lHzaew7r.mjs +226 -0
  86. package/dist/native-gradle-inputs-BQnIdAlL.mjs +356 -0
  87. package/dist/{native-runtime-D_603JiC.mjs → native-runtime-BRmWL7ru.mjs} +9 -8
  88. package/dist/native-xcode-inputs-eutS19jU.mjs +307 -0
  89. package/dist/native-xcode-ios-DP3cHLwu.mjs +434 -0
  90. package/dist/{ndjson-LGZAMsqa.mjs → ndjson-GOnbyvso.mjs} +6 -6
  91. package/dist/offload-worker.d.mts +12 -6
  92. package/dist/offload-worker.mjs +409 -73
  93. package/dist/{ownership-D6Se7gg_.mjs → ownership-DXzMMSx6.mjs} +2 -2
  94. package/dist/{placement-log-BcPhRwR4.mjs → placement-log-reFbjqEr.mjs} +7 -3
  95. package/dist/plan-BDk2AeY_.mjs +260 -0
  96. package/dist/plan-placement-D_YYlE6N.mjs +73 -0
  97. package/dist/{ports-BLTtfBq-.mjs → ports-jzvAJXXL.mjs} +3 -3
  98. package/dist/{prebuild-juCyreaR.mjs → prebuild-Bw44QxVh.mjs} +4 -4
  99. package/dist/{preview-D5V07Nrl.mjs → preview-BaBJ72Al.mjs} +51 -34
  100. package/dist/project-B8_duAzW.mjs +1067 -0
  101. package/dist/project-plan-BPwbLwQu.mjs +10 -0
  102. package/dist/{pull-request-u4vp7zmK.mjs → pull-request-vOhLNmIb.mjs} +1 -1
  103. package/dist/pull-requests.mjs +1 -1
  104. package/dist/react-native-android-chm1DMlA.mjs +921 -0
  105. package/dist/react-native-build-oAw6gQeW.mjs +239 -0
  106. package/dist/react-native-doctor-DYXoh-6I.mjs +78 -0
  107. package/dist/react-native-ios-SCTK_CT5.mjs +595 -0
  108. package/dist/{recordings-BAWltsVt.mjs → recordings-Bk-VivUV.mjs} +1 -1
  109. package/dist/{reload-DvyWzVjJ.mjs → reload-D34GkY78.mjs} +17 -14
  110. package/dist/remote-BU9UyhCJ.mjs +192 -0
  111. package/dist/{remote-cache-BGg7aGIZ.mjs → remote-cache-Bsts6f6Y.mjs} +5 -5
  112. package/dist/result-BDoRk1k3.mjs +186 -0
  113. package/dist/{run-vhtDgpHO.mjs → run-qvff78hv.mjs} +3 -3
  114. package/dist/{server-bare-B9E7S1aR.mjs → server-bare-dWjAarki.mjs} +6 -6
  115. package/dist/server-command-7XIKlWtO.mjs +69 -0
  116. package/dist/{server-expo-DoBHhuzK.mjs → server-expo-CsYu2aKL.mjs} +107 -82
  117. package/dist/server-expo-DV43vep4.mjs +2 -0
  118. package/dist/settings-Bbztm4oV.mjs +1503 -0
  119. package/dist/{settings-Byp2AQ_E.mjs → settings-CT3E71-i.mjs} +6 -7
  120. package/dist/{state-lKh8shBp.d.mts → settings-vk3mJGSm.d.mts} +4 -2
  121. package/dist/settings.schema.json +156 -6
  122. package/dist/simslim-ChMfe8rg.mjs +149 -0
  123. package/dist/{slot-launch-C1nqh4mu.mjs → slot-launch-B9hn96wd.mjs} +6 -6
  124. package/dist/{stage-g_WqWeaE.mjs → stage-BNKKdDNc.mjs} +6 -3
  125. package/dist/start-CbQCSnXQ.mjs +2 -0
  126. package/dist/{start-D-IeOyoK.mjs → start-CdvcTaXr.mjs} +113 -77
  127. package/dist/{state-xnqpx3AD.mjs → state-Boo0IMJq.mjs} +4 -2
  128. package/dist/{state-BG-aAk4k.mjs → state-DI84A-uu.mjs} +29 -10
  129. package/dist/{stats-DY6ekz8F.mjs → stats-BIIDrjA8.mjs} +6 -6
  130. package/dist/{status-BEeL6IKz.mjs → status-BDGHwkCs.mjs} +3 -1
  131. package/dist/{status-DCjZeE-K.mjs → status-DARMkc9q.mjs} +80 -294
  132. package/dist/status-watch-DvmI3SmN.mjs +296 -0
  133. package/dist/{stim-desktop-Behws_fi.mjs → stim-desktop-8GQX2OV8.mjs} +1 -1
  134. package/dist/{stop-DIJtD2GL.mjs → stop-CCZS-C9m.mjs} +2 -2
  135. package/dist/stop-WQPQHLuy.mjs +72 -0
  136. package/dist/{stop-BTjs3xbc.mjs → stop-iNemHsJC.mjs} +81 -33
  137. package/dist/supervisor-run.d.mts +7 -2
  138. package/dist/supervisor-run.mjs +32 -20
  139. package/dist/{support-Bwvbrq5-.mjs → support-CnE_SIjt.mjs} +17 -7
  140. package/dist/{support-BaayeYCf.mjs → support-DCco5fut.mjs} +61 -8
  141. package/dist/swiftpm-macos-afeNUExW.mjs +119 -0
  142. package/dist/{native-run-B06FkmZx.mjs → tailnet-CoJUb2tB.mjs} +113 -6
  143. package/dist/toolchain-BIupIro1.mjs +2 -0
  144. package/dist/{toolchain-Bnyd_K3A.mjs → toolchain-DqyDXs30.mjs} +225 -315
  145. package/dist/{trigger-CJMhwXe8.mjs → trigger-C0MWBi8O.mjs} +3 -3
  146. package/dist/trigger-SHLOWJ4O.mjs +2 -0
  147. package/dist/{warm-progress-CjB-MP6H.mjs → warm-progress-C3ASD6z5.mjs} +134 -37
  148. package/dist/{status-Dcr0Qexd.mjs → watchman-CQl51YZu.mjs} +75 -7
  149. package/dist/web-B3eldHFH.mjs +123 -0
  150. package/dist/web-BLXpOe6b.mjs +2 -0
  151. package/dist/web-run.mjs +10 -10
  152. package/dist/{workspace-state-C59dqG-Z.mjs → workspace-state-CSIFuuDa.mjs} +33 -33
  153. package/dist/{workspaces-xXItD8Dj.mjs → workspaces-D2xSHBDY.mjs} +1297 -1211
  154. package/dist/{worktree-BSYlofkq.mjs → worktree-BxDqaTev.mjs} +102 -63
  155. package/dist/worktree-q7_OZNY0.mjs +2 -0
  156. package/package.json +8 -6
  157. package/shim/bundle-response.cjs +2 -1
  158. package/shim/bundle-response.d.cts +1 -1
  159. package/shim/expo-metro-config.cjs +1 -1
  160. package/shim/native-android.gradle +101 -0
  161. package/dist/android-BKA16DTf.mjs +0 -4
  162. package/dist/android-BvwuMUJ9.mjs +0 -3572
  163. package/dist/guide-status-Bhf9SJXc.mjs +0 -125
  164. package/dist/ios-CPdkoKaa.mjs +0 -3953
  165. package/dist/ios-DNpdygiv.mjs +0 -7
  166. package/dist/macos-Ck7OBMNY.mjs +0 -2
  167. package/dist/metro-store-BAHFcJm6.mjs +0 -106
  168. package/dist/plan-placement-CtxkCO54.mjs +0 -1859
  169. package/dist/project-C3ChRLZy.mjs +0 -212
  170. package/dist/projects-DD7dBTCj.mjs +0 -92
  171. package/dist/server-expo-Db9HBUoa.mjs +0 -2
  172. package/dist/settings-CQyMElEP.mjs +0 -701
  173. package/dist/start-NTc1Ifqs.mjs +0 -2
  174. package/dist/stop-BGhKMOuH.mjs +0 -48
  175. package/dist/trigger-DWAq9hkw.mjs +0 -2
  176. package/dist/web-B6_bFDn7.mjs +0 -2
  177. package/dist/worktree-BJfnU6r5.mjs +0 -2
  178. package/dist/worktree-CsKYexJM.mjs +0 -753
@@ -1,701 +0,0 @@
1
- import { _ as loadConfig$1, f as getConfigPath$1, h as getRepoSettings, m as getProjectSettings } from "./config-CgYMahz7.mjs";
2
- import { s as gitCommonDir, y as repoRoot } from "./worktree-CsKYexJM.mjs";
3
- import { isAbsolute } from "node:path";
4
- import { isAbsolute as isAbsolute$1, join as join$1, relative as relative$1, resolve as resolve$1, sep as sep$1 } from "path";
5
- 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";
6
- import { existsSync, readFileSync, realpathSync, statSync } from "fs";
7
- import { createHash } from "node:crypto";
8
- //#region src/optimizations.ts
9
- function compilerCacheFallbackMessage({ fallback, compilerCache, file = null }) {
10
- const origin = fallback.fromEnvironment ? "the environment" : file;
11
- 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.";
12
- return `${fallback.key}${origin ? ` in ${origin}` : ""} ${fallback.reason}. ${backend}`;
13
- }
14
- function optimizationBuildProfile(platform, options) {
15
- const selected = platform === "ios" ? {
16
- ...options.ios,
17
- swiftCompilationCache: options.ios.swiftCompilationCache ?? false
18
- } : { pch: options.android.pch };
19
- const defaults = platform === "ios" ? {
20
- compilationCache: true,
21
- swiftCompilationCache: false,
22
- prefixMapping: true
23
- } : { pch: "auto" };
24
- if (JSON.stringify(selected) === JSON.stringify(defaults)) return void 0;
25
- return `opt-${createHash("sha256").update(JSON.stringify(selected)).digest("hex").slice(0, 16)}`;
26
- }
27
- function resolveOptimizations(settings = {}, env = process.env) {
28
- function choice(path, choices, fallback) {
29
- const raw = optimizationValue(settings, path);
30
- if (raw === void 0) return fallback;
31
- if (typeof raw !== "string" || !choices.includes(raw)) throw new Error(`Invalid optimizations.${path}. Expected ${choices.join(", ")}.`);
32
- return raw;
33
- }
34
- const compiler = choice("android.compilerCache", ANDROID_COMPILER_CACHE_CHOICES, "auto");
35
- const fromEnvironment = Boolean(env.STIM_ANDROID_CAS_TOOLCHAIN);
36
- const manifest = env.STIM_ANDROID_CAS_TOOLCHAIN || optimizationValue(settings, "android.casToolchain");
37
- let fallback = null;
38
- let toolchain = null;
39
- if (manifest !== void 0) {
40
- if (typeof manifest === "string" && isAbsolute(manifest) && !/[\r\n\0]/.test(manifest)) toolchain = manifest;
41
- else fallback = {
42
- key: fromEnvironment ? "STIM_ANDROID_CAS_TOOLCHAIN" : "optimizations.android.casToolchain",
43
- reason: `is ${JSON.stringify(manifest)}, which is not an absolute path to a toolchain JSON manifest`,
44
- fromEnvironment,
45
- manifest: null
46
- };
47
- }
48
- let compilerCache = compiler === "auto" ? toolchain ? "cas" : "ccache" : compiler;
49
- if (compilerCache === "cas" && !toolchain) {
50
- fallback ??= {
51
- key: "optimizations.android.compilerCache",
52
- reason: "is \"cas\", but no optimizations.android.casToolchain or STIM_ANDROID_CAS_TOOLCHAIN names the toolchain manifest",
53
- fromEnvironment: false,
54
- manifest: null
55
- };
56
- compilerCache = "ccache";
57
- }
58
- return {
59
- buildCache: optimizationBoolean(settings, "buildCache"),
60
- remoteBuildCache: optimizationBoolean(settings, "remoteBuildCache"),
61
- releaseBundleSwap: optimizationBoolean(settings, "releaseBundleSwap"),
62
- metroSharedCache: optimizationBoolean(settings, "metroSharedCache"),
63
- metroWarmup: optimizationBoolean(settings, "metroWarmup"),
64
- ios: {
65
- compilationCache: optimizationBoolean(settings, "ios.compilationCache"),
66
- swiftCompilationCache: optimizationValue(settings, "ios.swiftCompilationCache") === void 0 ? null : optimizationBoolean(settings, "ios.swiftCompilationCache"),
67
- prefixMapping: optimizationBoolean(settings, "ios.prefixMapping")
68
- },
69
- android: {
70
- compilerCache,
71
- casToolchain: toolchain,
72
- compilerCacheFallback: fallback,
73
- pch: choice("android.pch", ANDROID_PCH_CHOICES, "auto"),
74
- gradleBuildCache: optimizationBoolean(settings, "android.gradleBuildCache"),
75
- targetAbiOnly: optimizationBoolean(settings, "android.targetAbiOnly")
76
- }
77
- };
78
- }
79
- function artifactCachePolicy(options, reuse, release) {
80
- return {
81
- read: reuse && options.buildCache && (!release || options.releaseBundleSwap),
82
- write: options.buildCache,
83
- remote: options.buildCache && options.remoteBuildCache
84
- };
85
- }
86
- function optimizationValue(settings, path) {
87
- let node = settings;
88
- for (const key of ["optimizations", ...path.split(".")]) {
89
- if (node === void 0) return void 0;
90
- if (!node || typeof node !== "object" || Array.isArray(node)) throw new Error("Invalid optimizations setting. Expected nested objects.");
91
- node = node[key];
92
- }
93
- return node;
94
- }
95
- function optimizationBoolean(settings, path, fallback = true) {
96
- const raw = optimizationValue(settings, path);
97
- if (raw === void 0) return fallback;
98
- if (typeof raw !== "boolean") throw new Error(`Invalid optimizations.${path}. Expected true or false.`);
99
- return raw;
100
- }
101
- function resolveMetroSharedCache(settings) {
102
- return optimizationBoolean(settings, "metroSharedCache");
103
- }
104
- //#endregion
105
- //#region src/engine/metro-reach.ts
106
- const PUBLIC_METRO_ENV = "STIM_METRO_PUBLIC_URL";
107
- const MANAGED_PROVIDERS = ["ngrok", "cloudflared"];
108
- const NAMED = {
109
- cloudflared: "`cloudflared` (brew install cloudflared)",
110
- ngrok: "`ngrok` (brew install ngrok, then `ngrok config add-authtoken <token>`)",
111
- tailscale: "Tailscale and sign in to your tailnet"
112
- };
113
- function planMetroReach({ mode, metroPort, publicUrl = null, isExpo, available = [] }) {
114
- const named = publicUrl?.trim().replace(/\/+$/, "") || null;
115
- if (mode === "off") return {
116
- origin: `http://localhost:${metroPort}`,
117
- gate: false
118
- };
119
- if (named) return {
120
- origin: named,
121
- gate: true
122
- };
123
- if (mode === "expo") {
124
- if (!isExpo) return {
125
- failed: "metro.tunnel is \"expo\", but this workspace does not run an Expo dev server.",
126
- remedy: "Use \"auto\" to let stim start a tunnel, or \"off\" if the device shares this machine."
127
- };
128
- return { expoTunnel: true };
129
- }
130
- if (mode === "cloudflared" || mode === "ngrok" || mode === "tailscale") {
131
- if (!available.includes(mode)) return {
132
- failed: `metro.tunnel is "${mode}", but ${mode} is not on PATH.`,
133
- remedy: `Install ${NAMED[mode] ?? mode}, or set metro.tunnel to "auto".`
134
- };
135
- return { start: mode };
136
- }
137
- const provider = available.find((candidate) => candidate !== "tailscale");
138
- if (provider) return { start: provider };
139
- return {
140
- failed: `A remote device cannot reach this workspace's Metro on port ${metroPort}, and no tunnel is available to give it one.`,
141
- remedy: `Install ${NAMED.ngrok} or ${NAMED.cloudflared} and Stim will manage the tunnel for you. If you already have one, set metro.publicUrl to its URL. If the device shares this machine (a local \`agent-device proxy\`), set metro.tunnel to "off".`
142
- };
143
- }
144
- function resolveTailscaleBinary(findExecutable) {
145
- return findExecutable("tailscale") ?? findExecutable("/Applications/Tailscale.app/Contents/MacOS/Tailscale");
146
- }
147
- function detectProviders(onPath, mode = "auto") {
148
- if (mode === "tailscale") return resolveTailscaleBinary((bin) => onPath(bin) ? bin : null) ? ["tailscale"] : [];
149
- return MANAGED_PROVIDERS.filter((p) => onPath(p));
150
- }
151
- //#endregion
152
- //#region src/workspace/settings.ts
153
- function isPlainObject(v) {
154
- return v !== null && typeof v === "object" && !Array.isArray(v);
155
- }
156
- function mergeSettingsLayers(layers) {
157
- const out = {};
158
- for (const layer of layers) {
159
- if (!isPlainObject(layer)) continue;
160
- for (const [key, value] of Object.entries(layer)) if (isPlainObject(value) && isPlainObject(out[key])) out[key] = mergeSettingsLayers([out[key], value]);
161
- else if (!(key in out)) out[key] = value;
162
- }
163
- return out;
164
- }
165
- const LAYERED_SETTINGS = SETTINGS.filter(isLayeredSetting);
166
- const KNOWN_SETTINGS = /* @__PURE__ */ new Set([...LAYERED_SETTINGS.map((setting) => setting.key), ...SETTING_GROUPS]);
167
- const BOUNDS_CHECKED_WHEN_USED = /* @__PURE__ */ new Set(["android.dataPartitionSizeGb"]);
168
- const SETTINGS_WITH_RESOLVE_TIME_FALLBACK = /* @__PURE__ */ new Set(["optimizations.android.casToolchain"]);
169
- const PATH_SETTINGS = Object.freeze(LAYERED_SETTINGS.filter((setting) => setting.type.kind === "path").map((setting) => setting.key));
170
- function settingValueAt(settings, path) {
171
- let node = settings;
172
- for (const segment of path.split(".")) {
173
- if (!isPlainObject(node)) return void 0;
174
- node = node[segment];
175
- }
176
- return node;
177
- }
178
- function settingShapeErrors(settings) {
179
- const errors = [];
180
- for (const group of SETTING_GROUPS) {
181
- const value = settingValueAt(settings, group);
182
- if (value === void 0 || isPlainObject(value)) continue;
183
- errors.push(`Invalid ${group} setting ${JSON.stringify(value)}. Expected an object.`);
184
- }
185
- for (const setting of LAYERED_SETTINGS) {
186
- if (SETTINGS_WITH_RESOLVE_TIME_FALLBACK.has(setting.key)) continue;
187
- const value = settingValueAt(settings, setting.key);
188
- if (value === void 0) continue;
189
- 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);
190
- if (expected) errors.push(`Invalid ${setting.key} setting ${JSON.stringify(value)}. Expected ${expected}.`);
191
- }
192
- return errors;
193
- }
194
- const SETTING_SHAPE_REMEDY = "Run `stim guide settings` for the shape each setting takes.";
195
- const MIN_ANDROID_DATA_PARTITION_SIZE_GB = 6;
196
- const DEFAULT_ANDROID_DATA_PARTITION_SIZE_GB = 8;
197
- const MAX_ANDROID_DATA_PARTITION_SIZE_GB = 16384;
198
- function validateAndroidDataPartitionSizeGb(raw) {
199
- if (typeof raw !== "number" || !Number.isSafeInteger(raw) || raw < MIN_ANDROID_DATA_PARTITION_SIZE_GB || raw > MAX_ANDROID_DATA_PARTITION_SIZE_GB) throw new Error(`Invalid android.dataPartitionSizeGb setting ${JSON.stringify(raw)}. Expected an integer from ${MIN_ANDROID_DATA_PARTITION_SIZE_GB} through ${MAX_ANDROID_DATA_PARTITION_SIZE_GB} GiB.`);
200
- return raw;
201
- }
202
- function androidDataPartitionSizeBytes(sizeGb) {
203
- return validateAndroidDataPartitionSizeGb(sizeGb) * 1024 ** 3;
204
- }
205
- function androidDataPartitionSizeGbRaw(settings) {
206
- if (!isPlainObject(settings) || !isPlainObject(settings.android)) return void 0;
207
- return settings.android.dataPartitionSizeGb;
208
- }
209
- function androidDataPartitionSizeGbSetting(settings) {
210
- const raw = androidDataPartitionSizeGbRaw(settings);
211
- if (raw === void 0) return DEFAULT_ANDROID_DATA_PARTITION_SIZE_GB;
212
- return validateAndroidDataPartitionSizeGb(raw);
213
- }
214
- function androidDataPartitionSizeGbSettingError(settings) {
215
- try {
216
- androidDataPartitionSizeGbSetting(settings);
217
- return null;
218
- } catch (error) {
219
- return String(error?.message || error);
220
- }
221
- }
222
- function yesNoAvdValue(value) {
223
- if (value === true || value === "true" || value === "yes") return "yes";
224
- if (value === false || value === "false" || value === "no") return "no";
225
- throw new Error("expected true, false, \"yes\", or \"no\"");
226
- }
227
- function boundedIntegerAvdRule(min, max, unit) {
228
- return {
229
- help: `integer ${min}-${max}${unit ? ` ${unit}` : ""}`,
230
- parse: (value) => {
231
- const parsed = typeof value === "number" ? value : typeof value === "string" && /^(0|[1-9]\d*)$/.test(value) ? Number(value) : NaN;
232
- if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) throw new Error(`expected an integer from ${min} through ${max}${unit ? ` ${unit}` : ""}`);
233
- return String(parsed);
234
- }
235
- };
236
- }
237
- function enumAvdRule(values) {
238
- return {
239
- help: values.join("|"),
240
- parse: (value) => {
241
- if (typeof value !== "string" || !values.includes(value)) throw new Error(`expected one of: ${values.join(", ")}`);
242
- return value;
243
- }
244
- };
245
- }
246
- const ANDROID_AVD_CONFIG_RULES = {
247
- "hw.cpu.ncore": boundedIntegerAvdRule(1, 64),
248
- "hw.gpu.mode": enumAvdRule([
249
- "auto",
250
- "host",
251
- "software",
252
- "lavapipe",
253
- "swiftshader",
254
- "swangle"
255
- ]),
256
- "hw.initialOrientation": enumAvdRule(["portrait", "landscape"]),
257
- "hw.lcd.density": boundedIntegerAvdRule(72, 1e3, "dpi"),
258
- "hw.lcd.vsync": boundedIntegerAvdRule(1, 1e3, "Hz"),
259
- "hw.ramSize": boundedIntegerAvdRule(1536, 8192, "MB"),
260
- "hw.screen": enumAvdRule([
261
- "no-touch",
262
- "touch",
263
- "multi-touch"
264
- ]),
265
- "runtime.network.latency": enumAvdRule([
266
- "none",
267
- "gsm",
268
- "hscsd",
269
- "gprs",
270
- "edge",
271
- "umts",
272
- "hsdpa",
273
- "lte",
274
- "evdo",
275
- "5g"
276
- ]),
277
- "runtime.network.speed": enumAvdRule([
278
- "gsm",
279
- "hscsd",
280
- "gprs",
281
- "edge",
282
- "umts",
283
- "hsdpa",
284
- "lte",
285
- "evdo",
286
- "5g",
287
- "full"
288
- ]),
289
- "vm.heapSize": boundedIntegerAvdRule(16, 4096, "MB")
290
- };
291
- const YES_NO_AVD_RULE = {
292
- help: "true|false|yes|no",
293
- parse: yesNoAvdValue
294
- };
295
- for (const key of [
296
- "hw.accelerometer",
297
- "hw.accelerometer_uncalibrated",
298
- "hw.audioInput",
299
- "hw.audioOutput",
300
- "hw.battery",
301
- "hw.dPad",
302
- "hw.gps",
303
- "hw.gpu.enabled",
304
- "hw.gyroscope",
305
- "hw.keyboard",
306
- "hw.mainKeys",
307
- "hw.rotaryInput",
308
- "hw.trackBall",
309
- "showDeviceFrame"
310
- ]) ANDROID_AVD_CONFIG_RULES[key] = YES_NO_AVD_RULE;
311
- const ANDROID_AVD_CONFIG_KEYS = Object.freeze(Object.keys(ANDROID_AVD_CONFIG_RULES).toSorted());
312
- const ANDROID_AVD_CONFIG_HELP = Object.freeze(ANDROID_AVD_CONFIG_KEYS.map((key) => `${key}: ${ANDROID_AVD_CONFIG_RULES[key].help}`));
313
- function isSafeAndroidAvdConfigKey(key) {
314
- return Object.hasOwn(ANDROID_AVD_CONFIG_RULES, key);
315
- }
316
- function normalizedAndroidAvdConfigEntry(key, value) {
317
- if (typeof key !== "string" || key !== key.trim() || !isSafeAndroidAvdConfigKey(key)) throw new Error(`Unsupported android.avdConfig key ${JSON.stringify(key)}. Use a documented safe hardware override; identity, path, storage, image, snapshot, and boot-lifecycle keys are protected.`);
318
- if (typeof value === "string" && /[\r\n\0]/.test(value)) throw new Error(`Invalid android.avdConfig value for ${key}: expected one line.`);
319
- try {
320
- return [key, ANDROID_AVD_CONFIG_RULES[key].parse(value)];
321
- } catch (error) {
322
- throw new Error(`Invalid android.avdConfig value for ${key}: ${String(error?.message || error)}.`, { cause: error });
323
- }
324
- }
325
- function parseAndroidAvdConfigIni(contents) {
326
- const entries = {};
327
- const lines = contents.replace(/^\uFEFF/, "").split(/\r?\n/);
328
- for (let index = 0; index < lines.length; index++) {
329
- const line = String(lines[index] ?? "").trim();
330
- if (!line || line.startsWith("#") || line.startsWith(";")) continue;
331
- const separator = line.indexOf("=");
332
- if (separator <= 0) throw new Error(`Invalid android.avdConfigFile line ${index + 1}: expected key=value.`);
333
- const [key, value] = normalizedAndroidAvdConfigEntry(line.slice(0, separator), line.slice(separator + 1).trim());
334
- if (Object.hasOwn(entries, key)) throw new Error(`Invalid android.avdConfigFile line ${index + 1}: duplicate key ${key}.`);
335
- entries[key] = value;
336
- }
337
- return entries;
338
- }
339
- function pathEscapesRoot(root, path) {
340
- const child = relative$1(root, path);
341
- return child === ".." || child.startsWith(`..${sep$1}`) || isAbsolute$1(child);
342
- }
343
- function androidAvdConfigSetting(settings, settingsRoot) {
344
- if (!isPlainObject(settings) || !isPlainObject(settings.android)) return {};
345
- const android = settings.android;
346
- let entries = {};
347
- if ("avdConfigFile" in android) {
348
- if (typeof android.avdConfigFile !== "string" || !android.avdConfigFile.trim() || android.avdConfigFile !== android.avdConfigFile.trim() || /[\r\n\0]/.test(android.avdConfigFile) || isAbsolute$1(android.avdConfigFile)) throw new Error("Invalid android.avdConfigFile setting. Expected a relative file path inside the app directory.");
349
- try {
350
- const root = realpathSync(settingsRoot);
351
- const candidate = resolve$1(root, android.avdConfigFile);
352
- if (pathEscapesRoot(root, candidate)) throw new Error("path escapes the settings root");
353
- const path = realpathSync(candidate);
354
- if (pathEscapesRoot(root, path)) throw new Error("symlink target escapes the settings root");
355
- const stat = statSync(path);
356
- if (!stat.isFile()) throw new Error("path is not a regular file");
357
- if (stat.size > 65536) throw new Error("file exceeds 64 KiB");
358
- const contents = readFileSync(path, "utf8");
359
- if (Buffer.byteLength(contents) > 65536) throw new Error("file exceeds 64 KiB");
360
- entries = parseAndroidAvdConfigIni(contents);
361
- } catch (error) {
362
- throw new Error(`Could not read valid android.avdConfigFile ${android.avdConfigFile}: ${String(error?.message || error)}`, { cause: error });
363
- }
364
- }
365
- if ("avdConfig" in android) {
366
- if (!isPlainObject(android.avdConfig)) throw new Error("Invalid android.avdConfig setting. Expected an object of native AVD key/value pairs.");
367
- for (const [key, value] of Object.entries(android.avdConfig)) {
368
- const [normalizedKey, normalizedValue] = normalizedAndroidAvdConfigEntry(key, value);
369
- entries[normalizedKey] = normalizedValue;
370
- }
371
- }
372
- return entries;
373
- }
374
- function androidAvdConfigSettingError(settings, projectPath) {
375
- try {
376
- androidAvdConfigSetting(settings, projectPath);
377
- return null;
378
- } catch (error) {
379
- return String(error?.message || error);
380
- }
381
- }
382
- function iosSimSlimProfileSetting(settings, settingsRoot) {
383
- if (!isPlainObject(settings) || !isPlainObject(settings.ios) || !("simslimProfile" in settings.ios)) return null;
384
- const value = settings.ios.simslimProfile;
385
- if (typeof value !== "string" || !value.trim() || value !== value.trim() || /[\r\n\0]/.test(value) || isAbsolute$1(value)) throw new Error("Invalid ios.simslimProfile setting. Expected a relative JSON file path inside the app directory.");
386
- try {
387
- const root = realpathSync(settingsRoot);
388
- const candidate = resolve$1(root, value);
389
- if (pathEscapesRoot(root, candidate)) throw new Error("path escapes the settings root");
390
- const path = realpathSync(candidate);
391
- if (pathEscapesRoot(root, path)) throw new Error("symlink target escapes the settings root");
392
- const stat = statSync(path);
393
- if (!stat.isFile()) throw new Error("path is not a regular file");
394
- if (stat.size > 65536) throw new Error("file exceeds 64 KiB");
395
- return path;
396
- } catch (error) {
397
- throw new Error(`Could not read ios.simslimProfile ${value}: ${String(error?.message || error)}`, { cause: error });
398
- }
399
- }
400
- function iosString(settings, key) {
401
- if (!isPlainObject(settings) || !isPlainObject(settings.ios)) return void 0;
402
- return settings.ios[key];
403
- }
404
- function iosSigningIdentitySetting(settings) {
405
- const raw = iosString(settings, "signingIdentity");
406
- return typeof raw === "string" && raw.trim() !== "" ? raw.trim() : null;
407
- }
408
- function iosSigningIdentitySettingError(settings) {
409
- const raw = iosString(settings, "signingIdentity");
410
- if (raw === void 0) return null;
411
- if (typeof raw !== "string" || raw.trim() === "" || /[\r\n\0]/.test(raw)) return `Invalid ios.signingIdentity setting ${JSON.stringify(raw)}. Expected the identity name \`security find-identity -v -p codesigning\` prints, such as "Apple Development: Jane (TEAMID5678)".`;
412
- return null;
413
- }
414
- const SIGNING_IDENTITY_SHA1 = /^[0-9A-Fa-f]{40}$/;
415
- function iosSigningIdentitySha1Setting(settings) {
416
- const raw = iosString(settings, "signingIdentitySha1");
417
- return typeof raw === "string" && SIGNING_IDENTITY_SHA1.test(raw.trim()) ? raw.trim().toUpperCase() : null;
418
- }
419
- function iosSigningIdentitySha1SettingError(settings) {
420
- const raw = iosString(settings, "signingIdentitySha1");
421
- if (raw === void 0) return null;
422
- if (typeof raw !== "string" || !SIGNING_IDENTITY_SHA1.test(raw.trim())) return `Invalid ios.signingIdentitySha1 setting ${JSON.stringify(raw)}. Expected the 40-character hex SHA-1 hash \`security find-identity -v -p codesigning\` prints beside the identity name.`;
423
- return null;
424
- }
425
- const LAN_HOST = /^(?!-)[A-Za-z0-9-]+(?:\.(?!-)[A-Za-z0-9-]+)*$/;
426
- function iosLanHostSetting(settings) {
427
- const raw = iosString(settings, "lanHost");
428
- return typeof raw === "string" && LAN_HOST.test(raw.trim()) ? raw.trim() : null;
429
- }
430
- function iosLanHostSettingError(settings) {
431
- const raw = iosString(settings, "lanHost");
432
- if (raw === void 0) return null;
433
- if (typeof raw !== "string" || !LAN_HOST.test(raw.trim())) return `Invalid ios.lanHost setting ${JSON.stringify(raw)}. Expected a bare address or hostname the phone can reach, such as "192.168.1.42" -- never a URL, a scheme, or a port.`;
434
- return null;
435
- }
436
- function unknownSettingKeys(settings, prefix = "") {
437
- if (!isPlainObject(settings)) return [];
438
- const unknown = [];
439
- for (const [key, value] of Object.entries(settings)) {
440
- const path = prefix ? `${prefix}.${key}` : key;
441
- if (path === "$schema") continue;
442
- if (KNOWN_SETTINGS.has(path) && (!isPlainObject(value) || ![...KNOWN_SETTINGS].some((k) => k.startsWith(`${path}.`)))) continue;
443
- if (isPlainObject(value) && [...KNOWN_SETTINGS].some((k) => k.startsWith(`${path}.`))) {
444
- unknown.push(...unknownSettingKeys(value, path));
445
- continue;
446
- }
447
- unknown.push(path);
448
- }
449
- return unknown;
450
- }
451
- function readCommittedSettings(directory) {
452
- if (!directory) return {};
453
- const p = join$1(directory, ".stim.json");
454
- if (!existsSync(p)) return {};
455
- try {
456
- const parsed = JSON.parse(readFileSync(p, "utf-8"));
457
- return isPlainObject(parsed) ? parsed : {};
458
- } catch {
459
- return {};
460
- }
461
- }
462
- function setSettingValueAt(settings, path, value) {
463
- const segments = path.split(".");
464
- let node = settings;
465
- for (const segment of segments.slice(0, -1)) {
466
- const next = node[segment];
467
- if (!isPlainObject(next)) node[segment] = {};
468
- node = node[segment];
469
- }
470
- node[segments[segments.length - 1]] = value;
471
- }
472
- const MACHINE_READABLE_SETTINGS = SETTINGS.filter((setting) => isLayeredSetting(setting) && setting.scopes.includes("machine") && !setting.key.startsWith("optimizations."));
473
- function machineLayerSettings(machine) {
474
- const settings = {};
475
- const optimizations = settingValueAt(machine, "optimizations");
476
- if (optimizations !== void 0) settings.optimizations = optimizations;
477
- for (const setting of MACHINE_READABLE_SETTINGS) {
478
- const value = settingValueAt(machine, setting.key);
479
- if (value !== void 0) setSettingValueAt(settings, setting.key, value);
480
- }
481
- return Object.keys(settings).length > 0 ? settings : null;
482
- }
483
- function settingsLayers({ projectPath, gitCommonDir, repoRoot }) {
484
- const machine = loadConfig$1();
485
- const machineFile = getConfigPath$1();
486
- const committedDir = projectPath ?? repoRoot;
487
- const machineSettings = machineLayerSettings(machine);
488
- return [
489
- projectPath ? {
490
- scope: "workspace",
491
- file: `${machineFile} (projects["${projectPath}"].settings)`,
492
- settings: getProjectSettings(projectPath)
493
- } : null,
494
- gitCommonDir ? {
495
- scope: "repo",
496
- file: `${machineFile} (repos["${gitCommonDir}"].settings)`,
497
- settings: getRepoSettings(gitCommonDir)
498
- } : null,
499
- committedDir ? {
500
- scope: "committed",
501
- file: join$1(committedDir, ".stim.json"),
502
- settings: readCommittedSettings(committedDir)
503
- } : null,
504
- machineSettings ? {
505
- scope: "machine",
506
- file: machineFile,
507
- settings: machineSettings
508
- } : null
509
- ].filter((layer) => layer !== null);
510
- }
511
- function settingOrigin(layers, dottedKey) {
512
- for (const layer of layers) {
513
- const value = settingValueAt(layer.settings, dottedKey);
514
- if (value !== void 0) return {
515
- file: layer.file,
516
- value
517
- };
518
- }
519
- return null;
520
- }
521
- /** The layer a resolved setting's value came from, or null when no layer set it (a default or a flag). */
522
- function settingOriginScope(layers, dottedKey) {
523
- for (const layer of layers) if (settingValueAt(layer.settings, dottedKey) !== void 0) return layer.scope;
524
- return null;
525
- }
526
- function settingFile(context, dottedKey) {
527
- return settingOrigin(settingsLayers(context), dottedKey)?.file ?? null;
528
- }
529
- function resolveSettings(context) {
530
- return mergeSettingsLayers(settingsLayers(context).map((layer) => layer.settings));
531
- }
532
- function settingsForProject(root) {
533
- return resolveSettings({
534
- projectPath: root,
535
- gitCommonDir: gitCommonDir(root),
536
- repoRoot: repoRoot(root) ?? root
537
- });
538
- }
539
- function projectOptimizations(root) {
540
- return resolveOptimizations(settingsForProject(root));
541
- }
542
- function projectMetroSharedCache(root) {
543
- return resolveMetroSharedCache(settingsForProject(root));
544
- }
545
- function cacheBlock(layer) {
546
- if (!isPlainObject(layer) || !isPlainObject(layer.cache)) return null;
547
- return layer.cache;
548
- }
549
- function resolveCacheProviderConfig({ projectPath, gitCommonDir, repoRoot }) {
550
- const layers = [
551
- {
552
- settings: projectPath ? getProjectSettings(projectPath) : {},
553
- baseDir: projectPath ?? null
554
- },
555
- {
556
- settings: gitCommonDir ? getRepoSettings(gitCommonDir) : {},
557
- baseDir: repoRoot ?? projectPath ?? null
558
- },
559
- {
560
- settings: readCommittedSettings(projectPath),
561
- baseDir: projectPath ?? null
562
- }
563
- ];
564
- const references = layers.map((layer) => {
565
- const reference = cacheBlock(layer.settings)?.provider;
566
- return typeof reference === "string" && reference.trim() !== "" ? reference.trim() : null;
567
- });
568
- const selected = layers.findIndex((layer, index) => references[index] !== null && layer.baseDir !== null);
569
- if (selected === -1) return null;
570
- const provider = references[selected];
571
- const baseDir = layers[selected].baseDir;
572
- return {
573
- provider,
574
- options: mergeSettingsLayers(layers.map((layer, index) => {
575
- const namesProvider = references[index] === provider && layer.baseDir === baseDir;
576
- const overridesAbove = index < selected && references[index] === null;
577
- if (!namesProvider && !overridesAbove) return null;
578
- const block = cacheBlock(layer.settings)?.options;
579
- return isPlainObject(block) ? block : null;
580
- })),
581
- baseDir
582
- };
583
- }
584
- function cacheProviderSettingError(settings) {
585
- const block = settings.cache;
586
- if (!isPlainObject(block)) return block === void 0 ? null : `Invalid cache setting ${JSON.stringify(block)}. Expected an object with provider and options.`;
587
- if ("provider" in block && (typeof block.provider !== "string" || block.provider.trim() === "")) return `Invalid cache.provider setting ${JSON.stringify(block.provider)}. Expected a module path or package name.`;
588
- if ("options" in block && !isPlainObject(block.options)) return `Invalid cache.options setting ${JSON.stringify(block.options)}. Expected an object.`;
589
- return null;
590
- }
591
- function parseIosRemote(remote) {
592
- const selected = remote.trim();
593
- const reserved = selected.toLowerCase();
594
- return REMOTE_DEVICE_BACKENDS.includes(reserved) ? {
595
- kind: "backend",
596
- backend: reserved
597
- } : {
598
- kind: "machine",
599
- machine: reserved === "auto" ? "auto" : selected
600
- };
601
- }
602
- function remoteIosSetting(settings) {
603
- const ios = settings.ios;
604
- return isPlainObject(ios) && typeof ios.remote === "string" ? parseIosRemote(ios.remote) : null;
605
- }
606
- function remoteAndroidSetting(settings) {
607
- const android = settings.android;
608
- return isPlainObject(android) && typeof android.remote === "string" ? parseIosRemote(android.remote) : null;
609
- }
610
- function minutesSetting(settings, block, name) {
611
- const parent = settings[block];
612
- const value = isPlainObject(parent) ? parent[name] : void 0;
613
- const setting = settingDefinition(`${block}.${name}`);
614
- return setting && value !== void 0 && settingValueError(setting, value) === null ? value : Number(setting?.default);
615
- }
616
- function metroIdleStopMinutesSetting(settings) {
617
- return minutesSetting(settings, "metro", "idleStopMinutes");
618
- }
619
- function deviceIdleShutdownMinutesSetting(settings) {
620
- return minutesSetting(settings, "devices", "idleShutdownMinutes");
621
- }
622
- function deviceReclaimIdleMinutesSetting(settings) {
623
- return minutesSetting(settings, "devices", "reclaimIdleMinutes");
624
- }
625
- function projectMaintenancePinned(root) {
626
- try {
627
- const value = settingValueAt(settingsForProject(root), "maintenance.keep");
628
- return value !== void 0 && value !== false;
629
- } catch {
630
- return true;
631
- }
632
- }
633
- function projectDeviceReclaimIdleMinutes(root) {
634
- return deviceReclaimIdleMinutesSetting(settingsForProject(root));
635
- }
636
- function tunnelModeSetting(settings) {
637
- const block = settings.metro;
638
- if (typeof block !== "object" || block === null) return null;
639
- const mode = block.tunnel;
640
- return typeof mode === "string" && TUNNEL_MODES.includes(mode) ? mode : null;
641
- }
642
- function metroTunnelSettingError(settings) {
643
- const block = settings.metro;
644
- if (!isPlainObject(block) || !("ngrokUrl" in block)) return null;
645
- if (block.tunnel !== "ngrok") return "metro.ngrokUrl requires metro.tunnel to be \"ngrok\".";
646
- if (normalizedHttpsUrl(block.ngrokUrl) === null) return "metro.ngrokUrl must be a valid HTTPS URL.";
647
- return null;
648
- }
649
- function metroPortSetting(projectPath, env = process.env) {
650
- const setting = settingDefinition("metro.port");
651
- const fromEnv = env.STIM_METRO_PORT?.trim();
652
- const value = fromEnv ? coerceSettingText(setting, fromEnv) : settingValueAt(settingsForProject(projectPath), "metro.port");
653
- if (value === void 0) return {
654
- port: null,
655
- error: null
656
- };
657
- const problem = settingValueError(setting, value);
658
- if (problem === null) return {
659
- port: value,
660
- error: null
661
- };
662
- return {
663
- port: null,
664
- error: `Invalid ${fromEnv ? `STIM_METRO_PORT value ${JSON.stringify(env.STIM_METRO_PORT)}` : `metro.port setting ${JSON.stringify(value)}`}. Expected ${problem}.`
665
- };
666
- }
667
- function publicUrlSetting(settings) {
668
- const block = settings.metro;
669
- if (typeof block !== "object" || block === null) return null;
670
- const url = block.publicUrl;
671
- return typeof url === "string" && url.trim() ? url : null;
672
- }
673
- function metroWarmupUrlSetting(settings, platform) {
674
- const value = settingValueAt(settings, `metro.warmupUrl.${platform}`);
675
- return typeof value === "string" ? value : null;
676
- }
677
- function ngrokUrlSetting(settings) {
678
- const block = settings.metro;
679
- if (typeof block !== "object" || block === null) return null;
680
- if (block.tunnel !== "ngrok") return null;
681
- return normalizedHttpsUrl(block.ngrokUrl);
682
- }
683
- function normalizedHttpsUrl(raw) {
684
- if (typeof raw !== "string" || !raw.trim()) return null;
685
- try {
686
- return new URL(raw.trim()).protocol === "https:" ? raw.trim().replace(/\/+$/, "") : null;
687
- } catch {
688
- return null;
689
- }
690
- }
691
- function webSettings(settings) {
692
- const url = settingValueAt(settings, "web.url");
693
- const viewport = settingValueAt(settings, "web.viewport");
694
- return {
695
- url: typeof url === "string" ? url : null,
696
- ignoreCertificateErrors: settingValueAt(settings, "web.ignoreCertificateErrors") === true,
697
- viewport: WEB_VIEWPORTS.includes(viewport) ? viewport : "desktop"
698
- };
699
- }
700
- //#endregion
701
- export { publicUrlSetting as A, settingValueAt as B, metroWarmupUrlSetting as C, projectMaintenancePinned as D, projectDeviceReclaimIdleMinutes as E, resolveSettings as F, PUBLIC_METRO_ENV as G, tunnelModeSetting as H, settingFile as I, resolveTailscaleBinary as J, detectProviders as K, settingOrigin as L, remoteAndroidSetting as M, remoteIosSetting as N, projectMetroSharedCache as O, resolveCacheProviderConfig as P, resolveOptimizations as Q, settingOriginScope as R, metroTunnelSettingError as S, parseIosRemote as T, unknownSettingKeys as U, settingsLayers as V, webSettings as W, compilerCacheFallbackMessage as X, artifactCachePolicy as Y, optimizationBuildProfile as Z, iosSigningIdentitySha1SettingError as _, androidAvdConfigSetting as a, metroIdleStopMinutesSetting as b, androidDataPartitionSizeGbSetting as c, deviceIdleShutdownMinutesSetting as d, iosLanHostSetting as f, iosSigningIdentitySha1Setting as g, iosSigningIdentitySettingError as h, SETTING_SHAPE_REMEDY as i, readCommittedSettings as j, projectOptimizations as k, androidDataPartitionSizeGbSettingError as l, iosSigningIdentitySetting as m, PATH_SETTINGS as n, androidAvdConfigSettingError as o, iosLanHostSettingError as p, planMetroReach as q, SETTINGS_WITH_RESOLVE_TIME_FALLBACK as r, androidDataPartitionSizeBytes as s, ANDROID_AVD_CONFIG_HELP as t, cacheProviderSettingError as u, iosSimSlimProfileSetting as v, ngrokUrlSetting as w, metroPortSetting as x, mergeSettingsLayers as y, settingShapeErrors as z };