stim 1.8.0 → 1.10.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 (91) hide show
  1. package/README.md +23 -155
  2. package/dist/{android-DWssqHQ8.mjs → android-787HhP5w.mjs} +168 -39
  3. package/dist/{android-BRgBYzU0.mjs → android-DBkeHwpm.mjs} +250 -200
  4. package/dist/{android-DeYdjjBP.mjs → android-DPov-fv2.mjs} +2 -2
  5. package/dist/{android-cas-SWIjzeuL.mjs → android-cas-CITmIeWY.mjs} +22 -14
  6. package/dist/android-cas-compiler.mjs +2 -2
  7. package/dist/app-install-CrJ6t_bv.mjs +819 -0
  8. package/dist/budget-BP4jZZL4.mjs +620 -0
  9. package/dist/build-progress-BXx-ZBe4.mjs +512 -0
  10. package/dist/{cache-manifest-CYJi8-Hx.mjs → cache-manifest-Pubfp8wV.mjs} +4 -4
  11. package/dist/cache-manifest.mjs +1 -1
  12. package/dist/cli.mjs +22 -19
  13. package/dist/collector-run.d.mts +7 -1
  14. package/dist/collector-run.mjs +10 -9
  15. package/dist/{command-output-B3ycpsYv.mjs → command-output-D5z2_bcF.mjs} +11 -2
  16. package/dist/{config-B7iyKWkL.mjs → config-BEq0Qw_a.mjs} +134 -121
  17. package/dist/{dependency-state-D0M7oKA7.mjs → dependency-state-r9UesA9a.mjs} +2 -2
  18. package/dist/{deps-oCXVNEqQ.mjs → deps-DP4eWqea.mjs} +8 -5
  19. package/dist/{detached-entry-Bpo6ZY_I.mjs → detached-entry-DJHoca49.mjs} +1 -1
  20. package/dist/{device-BUoZyMoR.mjs → dev-client-92U593KT.mjs} +1090 -660
  21. package/dist/{device-RdujjHLB.mjs → device-DhTZe7L4.mjs} +13 -20
  22. package/dist/{build-lock-CgtsB6TV.mjs → device-lease-CbEaIk7b.mjs} +481 -27
  23. package/dist/{device-pool-BxRwdufO.mjs → device-pool-Di8Q-9Ro.mjs} +7 -7
  24. package/dist/{device-remote-ByN8P6CB.mjs → device-remote-Dy1jGj3U.mjs} +645 -119
  25. package/dist/{doctor-BMt156qe.mjs → doctor-B-inAJmt.mjs} +137 -113
  26. package/dist/{error-diagnostics-D1Kaf-eT.mjs → error-diagnostics-xxWKNO3-.mjs} +22 -17
  27. package/dist/exec-DK8KaVHK.mjs +133 -0
  28. package/dist/{gc-v2YcoJvm.mjs → gc-ChPFZMRC.mjs} +836 -813
  29. package/dist/{guide-BTAlD1_y.mjs → guide-DwmaD_Wa.mjs} +953 -209
  30. package/dist/guide-status-CWvti2Lg.mjs +125 -0
  31. package/dist/idle-DpRLp5lj.mjs +157 -0
  32. package/dist/{build-slots-C-oAHRT1.mjs → in-use-CjUluqIX.mjs} +102 -8
  33. package/dist/{ios-BcCiI4TS.mjs → ios-B2Fpqvlb.mjs} +60 -71
  34. package/dist/{ios-BP04pY5f.mjs → ios-Dd-hYJBh.mjs} +241 -234
  35. package/dist/{ios-device-CFXA46ez.mjs → ios-device-BiOsSVzd.mjs} +3 -2
  36. package/dist/{ios-device-CauFJiYx.mjs → ios-device-CtRdEVjx.mjs} +149 -52
  37. package/dist/launch-verify-CQS_Kg-C.mjs +365 -0
  38. package/dist/{logs-CakdBTtm.mjs → logs-MARIy6m9.mjs} +20 -15
  39. package/dist/metro-BWLOJ_Wr.mjs +409 -0
  40. package/dist/{named-ports-DzMOlmaX.mjs → named-ports-DiTfSz6p.mjs} +4 -4
  41. package/dist/{ndjson-BZwI1NBv.mjs → ndjson-CDY9IkVC.mjs} +23 -43
  42. package/dist/{ndjson-DvHSsNU9.d.mts → ndjson-Du3j0AQ9.d.mts} +1 -0
  43. package/dist/{ownership-Cp52QAK1.mjs → ownership-D2xbLVoz.mjs} +2 -3
  44. package/dist/{teardown-BJqEzPo_.mjs → ownership-DZjdx8Gq.mjs} +664 -612
  45. package/dist/{ports-BIbJaKNW.mjs → ports-CLSSFOuz.mjs} +3 -3
  46. package/dist/{process-output-Dl5uKE03.mjs → process-output-Fpl8PYi8.mjs} +1 -1
  47. package/dist/{project-DDZWf_16.mjs → project-BXZzOABt.mjs} +8 -3
  48. package/dist/{reload-BdypPbjM.mjs → reload-DoETJ3rl.mjs} +11 -13
  49. package/dist/{server-bare-BQkyxa_b.mjs → server-bare-DVbb7kXH.mjs} +2 -2
  50. package/dist/{server-expo-BVAA1OqJ.mjs → server-expo-BvOCeakp.mjs} +8 -7
  51. package/dist/server-expo-DBVTdult.mjs +2 -0
  52. package/dist/{settings-BXXorVM2.mjs → settings-DOugPlPz.mjs} +59 -159
  53. package/dist/settings-EWYNwe72.mjs +378 -0
  54. package/dist/settings.schema.json +1064 -0
  55. package/dist/simslim-Ca3487H6.mjs +436 -0
  56. package/dist/{slot-launch-ZUUe0TQP.mjs → slot-launch-C5cg0fmk.mjs} +4 -4
  57. package/dist/spawn-claims-CUleC2cM.mjs +49 -0
  58. package/dist/{start-CmxtOKjU.mjs → start-F9gv5dcC.mjs} +159 -114
  59. package/dist/start-VoFsPSes.mjs +2 -0
  60. package/dist/{state-DZobzxLW.mjs → state-WGdIbi_d.mjs} +5 -3
  61. package/dist/{stats-BYcZ6n1h.mjs → stats-BAgJaLA4.mjs} +66 -5
  62. package/dist/{stats-CLg5RuDS.mjs → stats-DBYJ5bxL.mjs} +4 -4
  63. package/dist/status-DLCD-o3s.mjs +764 -0
  64. package/dist/status-Dr4fRkym.mjs +216 -0
  65. package/dist/{guide-status-Craq9z0t.mjs → stim-installations-CINXy_su.mjs} +3 -122
  66. package/dist/{stop-C8JW6kKp.mjs → stop-Dj4pfmyv.mjs} +111 -31
  67. package/dist/{stop-B1kOpYei.mjs → stop-ZvOeTmKb.mjs} +2 -2
  68. package/dist/supervisor-run.d.mts +11 -2
  69. package/dist/supervisor-run.mjs +228 -42
  70. package/dist/{workspace-process-lock-BYfZz_S-.mjs → workspace-process-lock-BXA96myD.mjs} +14 -9
  71. package/dist/workspaces-BTUyFfkv.mjs +1003 -0
  72. package/dist/worktree-4aZg2NnI.mjs +2 -0
  73. package/dist/{worktree-CFLA-6MX.mjs → worktree-BMS7GeqB.mjs} +523 -61
  74. package/dist/{worktree-FG_ZKKdb.mjs → worktree-BlizKDpZ.mjs} +200 -54
  75. package/dist/{xcode-B6YaWeg0.mjs → xcode-B2tBkf0m.mjs} +66 -57
  76. package/package.json +6 -6
  77. package/skill/SKILL.md +2 -0
  78. package/dist/app-install-BDbsSgCl.mjs +0 -1175
  79. package/dist/dev-client-BmT-ioFI.mjs +0 -797
  80. package/dist/device-lease-Ffz_M5t0.mjs +0 -359
  81. package/dist/exec-B29XNuhP.mjs +0 -77
  82. package/dist/logs-query-BGL-Pl12.mjs +0 -290
  83. package/dist/metro-C7bcJ0uo.mjs +0 -233
  84. package/dist/ownership-wolE-0Do.mjs +0 -470
  85. package/dist/public-http-probe-CyMDrQ3c.mjs +0 -61
  86. package/dist/reclaim-KJ-0uWEF.mjs +0 -361
  87. package/dist/server-expo-YDDG3eP7.mjs +0 -2
  88. package/dist/spawn-entry-nCE6m770.mjs +0 -14
  89. package/dist/status-DdkLki47.mjs +0 -426
  90. package/dist/workspace-state-BT5E3SA2.mjs +0 -146
  91. package/dist/{metro-store-CDDZHtPl.mjs → metro-store-DzeWkfqW.mjs} +1 -1
@@ -1,6 +1,6 @@
1
- import { f as getProject } from "./config-B7iyKWkL.mjs";
2
- import { a as findProjectRoot } from "./project-DDZWf_16.mjs";
3
- import { n as getNamedPort, t as clearNamedPorts } from "./named-ports-DzMOlmaX.mjs";
1
+ import { f as getProject } from "./config-BEq0Qw_a.mjs";
2
+ import { o as findProjectRoot } from "./project-BXZzOABt.mjs";
3
+ import { n as getNamedPort, t as clearNamedPorts } from "./named-ports-DiTfSz6p.mjs";
4
4
  //#region src/commands/ports.ts
5
5
  async function inProject(action) {
6
6
  try {
@@ -16,7 +16,7 @@ function createLineReader(onLine) {
16
16
  if (!buffered) return;
17
17
  const rest = buffered;
18
18
  buffered = "";
19
- onLine(rest);
19
+ onLine(rest.endsWith("\r") ? rest.slice(0, -1) : rest);
20
20
  }
21
21
  };
22
22
  }
@@ -1,5 +1,5 @@
1
- import { c as findEnclosingWorktreeRoot, f as getProject } from "./config-B7iyKWkL.mjs";
2
- import { g as repoRoot } from "./worktree-FG_ZKKdb.mjs";
1
+ import { c as findEnclosingWorktreeRoot, f as getProject } from "./config-BEq0Qw_a.mjs";
2
+ import { y as repoRoot } from "./worktree-BlizKDpZ.mjs";
3
3
  import { existsSync, readFileSync, readdirSync, realpathSync } from "fs";
4
4
  import { basename, dirname, join, resolve } from "path";
5
5
  import { createRequire } from "module";
@@ -38,6 +38,11 @@ function ownedDeviceLabel(projectPath) {
38
38
  const worktreeLabel = basename(realpathSync(root));
39
39
  return worktreeLabel === appLabel ? appLabel : `${worktreeLabel}-${appLabel}`;
40
40
  }
41
+ const NO_PROJECT_REFUSAL = {
42
+ code: "STIM_NO_PROJECT",
43
+ message: "Not in a React Native project (no package.json found).",
44
+ remedy: "Run this from the app directory -- the one holding package.json."
45
+ };
41
46
  function findProjectRoot(startDir) {
42
47
  let dir;
43
48
  try {
@@ -240,4 +245,4 @@ function detectAndroidPackageFromGradle(projectRoot) {
240
245
  return null;
241
246
  }
242
247
  //#endregion
243
- export { findProjectRoot as a, projectShortcut as c, detectIsExpo as i, resolvePackageJson as l, detectAndroidPackage as n, isPackageResolvable as o, detectBundleId as r, ownedDeviceLabel as s, appProjectProblem as t };
248
+ export { detectIsExpo as a, ownedDeviceLabel as c, detectBundleId as i, projectShortcut as l, appProjectProblem as n, findProjectRoot as o, detectAndroidPackage as r, isPackageResolvable as s, NO_PROJECT_REFUSAL as t, resolvePackageJson as u };
@@ -1,12 +1,12 @@
1
- import { I as deviceSlotPlatforms, L as parseDeviceSlotKey, f as getProject } from "./config-B7iyKWkL.mjs";
2
- import { a as findProjectRoot } from "./project-DDZWf_16.mjs";
3
- import { d as readWorkspaceLaunches } from "./state-DZobzxLW.mjs";
4
- import { a as resolveProjectMetro } from "./metro-C7bcJ0uo.mjs";
5
- import { I as resolveOwnedAvdSerial } from "./android-DWssqHQ8.mjs";
6
- import { l as phaseLine } from "./command-output-B3ycpsYv.mjs";
7
- import { u as androidAppProcess, y as iosAppProcess } from "./app-install-BDbsSgCl.mjs";
8
- import { x as resolveOwnedIosSim } from "./ios-BcCiI4TS.mjs";
9
- import { n as nativeRunCommand } from "./slot-launch-ZUUe0TQP.mjs";
1
+ import { B as parseDeviceSlotKey, f as getProject, z as deviceSlotPlatforms } from "./config-BEq0Qw_a.mjs";
2
+ import { h as recordWorkspaceUse, s as resolveProjectMetro } from "./metro-BWLOJ_Wr.mjs";
3
+ import { o as findProjectRoot } from "./project-BXZzOABt.mjs";
4
+ import { d as readWorkspaceLaunches } from "./state-WGdIbi_d.mjs";
5
+ import { f as refuseNoProject, l as phaseLine } from "./command-output-D5z2_bcF.mjs";
6
+ import { z as resolveOwnedAvdSerial } from "./android-787HhP5w.mjs";
7
+ import { S as resolveOwnedIosSim } from "./ios-B2Fpqvlb.mjs";
8
+ import { h as iosAppProcess, o as androidAppProcess } from "./app-install-CrJ6t_bv.mjs";
9
+ import { n as nativeRunCommand } from "./slot-launch-C5cg0fmk.mjs";
10
10
  import chalk from "chalk";
11
11
  import { WebSocket } from "ws";
12
12
  //#region src/engine/reload.ts
@@ -305,12 +305,10 @@ function registerReload(program, deps = {}) {
305
305
  }
306
306
  const root = (deps.findProjectRoot ?? DEFAULT_DEPS.findProjectRoot)(process.cwd());
307
307
  if (!root) {
308
- const error = failure("STIM_NO_PROJECT", "Not inside a project (no package.json found above the current directory).", null);
309
- if (opts.json) console.log(JSON.stringify(error));
310
- else console.error(chalk.red(phaseLine("error", `${error.code}: ${error.message}`)));
311
- process.exitCode = 1;
308
+ refuseNoProject({ json: Boolean(opts.json) });
312
309
  return;
313
310
  }
311
+ recordWorkspaceUse(root);
314
312
  const result = await runReload({
315
313
  root,
316
314
  platform: value ?? null,
@@ -1,7 +1,7 @@
1
1
  import { i as __require, t as __commonJSMin } from "./rolldown-runtime-BsOwBuO_.mjs";
2
2
  import { n as supervisorError } from "./errors-B6W7FkxQ.mjs";
3
- import { C as projectMetroSharedCache } from "./settings-BXXorVM2.mjs";
4
- import { a as metroStoreRoot, o as registerMetroStore, t as appendCacheStore } from "./metro-store-CDDZHtPl.mjs";
3
+ import { w as projectMetroSharedCache } from "./settings-DOugPlPz.mjs";
4
+ import { a as metroStoreRoot, o as registerMetroStore, t as appendCacheStore } from "./metro-store-DzeWkfqW.mjs";
5
5
  import { createRequire } from "node:module";
6
6
  import { isAbsolute, join, relative, sep } from "node:path";
7
7
  //#endregion
@@ -1,11 +1,12 @@
1
- import { t as getExecutor } from "./exec-B29XNuhP.mjs";
2
- import { r as createNdjsonWriter } from "./ndjson-BZwI1NBv.mjs";
3
- import { l as resolvePackageJson } from "./project-DDZWf_16.mjs";
1
+ import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
2
+ import { r as createNdjsonWriter } from "./ndjson-CDY9IkVC.mjs";
3
+ import { u as resolvePackageJson } from "./project-BXZzOABt.mjs";
4
4
  import { n as supervisorError } from "./errors-B6W7FkxQ.mjs";
5
- import { n as stripAnsi, t as createLineReader } from "./process-output-Dl5uKE03.mjs";
6
- import { C as projectMetroSharedCache } from "./settings-BXXorVM2.mjs";
7
- import { a as metroStoreRoot, i as metroStoreConfirmedRoot, n as expoMetroConfigPath, o as registerMetroStore, r as expoMetroStoreEnv } from "./metro-store-CDDZHtPl.mjs";
5
+ import { n as stripAnsi, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
6
+ import { w as projectMetroSharedCache } from "./settings-DOugPlPz.mjs";
7
+ import { a as metroStoreRoot, i as metroStoreConfirmedRoot, n as expoMetroConfigPath, o as registerMetroStore, r as expoMetroStoreEnv } from "./metro-store-DzeWkfqW.mjs";
8
8
  import { accessSync, constants, existsSync, readFileSync } from "node:fs";
9
+ import { LOG_ROTATE_BYTES } from "@stim-cli/core";
9
10
  import { dirname, join, parse } from "node:path";
10
11
  //#region src/supervisor/server-expo.ts
11
12
  function delay(ms) {
@@ -209,7 +210,7 @@ async function startExpoServer({ root, port, logsDir, writer = null, spawnFn = n
209
210
  throw supervisorError("STIM_EXPO_BIN", refusal.message, refusal.remedy);
210
211
  }
211
212
  const bin = resolved.path;
212
- const log = writer || createNdjsonWriter(join(logsDir, "metro.ndjson"));
213
+ const log = writer || createNdjsonWriter(join(logsDir, "metro.ndjson"), { maxBytes: LOG_ROTATE_BYTES });
213
214
  const spawn = spawnFn || ((cmd, args, opts) => getExecutor().spawn(cmd, args, opts));
214
215
  const args = [
215
216
  "start",
@@ -0,0 +1,2 @@
1
+ import { p as startExpoServer } from "./server-expo-BvOCeakp.mjs";
2
+ export { startExpoServer };
@@ -1,28 +1,11 @@
1
- import { d as getConfigPath, g as loadConfig, m as getRepoSettings, p as getProjectSettings } from "./config-B7iyKWkL.mjs";
2
- import { g as repoRoot, s as gitCommonDir } from "./worktree-FG_ZKKdb.mjs";
3
- import { createHash } from "node:crypto";
4
- import { isAbsolute } from "node:path";
1
+ import { d as getConfigPath$1, g as loadConfig$1, m as getRepoSettings, p as getProjectSettings } from "./config-BEq0Qw_a.mjs";
2
+ import { s as gitCommonDir, y as repoRoot } from "./worktree-BlizKDpZ.mjs";
5
3
  import { existsSync, readFileSync, realpathSync, statSync } from "fs";
6
- import { isAbsolute as isAbsolute$1, join as join$1, relative as relative$1, resolve as resolve$1, sep as sep$1 } from "path";
4
+ import { isAbsolute, join, relative, resolve, sep } from "path";
5
+ import { ANDROID_COMPILER_CACHE_CHOICES, ANDROID_PCH_CHOICES, REMOTE_DEVICE_BACKENDS, SETTINGS, SETTING_GROUPS, TUNNEL_MODES, acceptsShape, expectedShape, isLayeredSetting, settingDefinition, settingValueError } from "@stim-cli/core/state";
6
+ import { isAbsolute as isAbsolute$1 } from "node:path";
7
+ import { createHash } from "node:crypto";
7
8
  //#region src/optimizations.ts
8
- const OPTIMIZATION_SHAPES = {
9
- optimizations: "object",
10
- "optimizations.buildCache": "boolean",
11
- "optimizations.remoteBuildCache": "boolean",
12
- "optimizations.releaseBundleSwap": "boolean",
13
- "optimizations.metroSharedCache": "boolean",
14
- "optimizations.metroWarmup": "boolean",
15
- "optimizations.ios": "object",
16
- "optimizations.ios.compilationCache": "boolean",
17
- "optimizations.ios.swiftCompilationCache": "boolean",
18
- "optimizations.ios.prefixMapping": "boolean",
19
- "optimizations.android": "object",
20
- "optimizations.android.compilerCache": "string",
21
- "optimizations.android.casToolchain": "path",
22
- "optimizations.android.pch": "string",
23
- "optimizations.android.gradleBuildCache": "boolean",
24
- "optimizations.android.targetAbiOnly": "boolean"
25
- };
26
9
  function compilerCacheFallbackMessage({ fallback, compilerCache, file = null }) {
27
10
  const origin = fallback.fromEnvironment ? "the environment" : file;
28
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.";
@@ -48,18 +31,13 @@ function resolveOptimizations(settings = {}, env = process.env) {
48
31
  if (typeof raw !== "string" || !choices.includes(raw)) throw new Error(`Invalid optimizations.${path}. Expected ${choices.join(", ")}.`);
49
32
  return raw;
50
33
  }
51
- const compiler = choice("android.compilerCache", [
52
- "auto",
53
- "ccache",
54
- "cas",
55
- "none"
56
- ], "auto");
34
+ const compiler = choice("android.compilerCache", ANDROID_COMPILER_CACHE_CHOICES, "auto");
57
35
  const fromEnvironment = Boolean(env.STIM_ANDROID_CAS_TOOLCHAIN);
58
36
  const manifest = env.STIM_ANDROID_CAS_TOOLCHAIN || optimizationValue(settings, "android.casToolchain");
59
37
  let fallback = null;
60
38
  let toolchain = null;
61
39
  if (manifest !== void 0) {
62
- if (typeof manifest === "string" && isAbsolute(manifest) && !/[\r\n\0]/.test(manifest)) toolchain = manifest;
40
+ if (typeof manifest === "string" && isAbsolute$1(manifest) && !/[\r\n\0]/.test(manifest)) toolchain = manifest;
63
41
  else fallback = {
64
42
  key: fromEnvironment ? "STIM_ANDROID_CAS_TOOLCHAIN" : "optimizations.android.casToolchain",
65
43
  reason: `is ${JSON.stringify(manifest)}, which is not an absolute path to a toolchain JSON manifest`,
@@ -92,11 +70,7 @@ function resolveOptimizations(settings = {}, env = process.env) {
92
70
  compilerCache,
93
71
  casToolchain: toolchain,
94
72
  compilerCacheFallback: fallback,
95
- pch: choice("android.pch", [
96
- "auto",
97
- "on",
98
- "off"
99
- ], "auto"),
73
+ pch: choice("android.pch", ANDROID_PCH_CHOICES, "auto"),
100
74
  gradleBuildCache: optimizationBoolean(settings, "android.gradleBuildCache"),
101
75
  targetAbiOnly: optimizationBoolean(settings, "android.targetAbiOnly")
102
76
  }
@@ -129,13 +103,6 @@ function resolveMetroSharedCache(settings) {
129
103
  }
130
104
  //#endregion
131
105
  //#region src/engine/metro-reach.ts
132
- const TUNNEL_MODES = [
133
- "auto",
134
- "off",
135
- "expo",
136
- "cloudflared",
137
- "ngrok"
138
- ];
139
106
  const PUBLIC_METRO_ENV = "STIM_METRO_PUBLIC_URL";
140
107
  const MANAGED_PROVIDERS = ["ngrok", "cloudflared"];
141
108
  const NAMED = {
@@ -190,77 +157,11 @@ function mergeSettingsLayers(layers) {
190
157
  }
191
158
  return out;
192
159
  }
193
- const SETTING_SHAPE_RULES = {
194
- boolean: {
195
- expected: "true or false",
196
- accepts: (value) => typeof value === "boolean"
197
- },
198
- string: {
199
- expected: "a string",
200
- accepts: (value) => typeof value === "string"
201
- },
202
- path: {
203
- expected: "a string path",
204
- accepts: (value) => typeof value === "string"
205
- },
206
- strings: {
207
- expected: "an array of strings",
208
- accepts: (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string")
209
- },
210
- number: {
211
- expected: "a number",
212
- accepts: (value) => typeof value === "number"
213
- },
214
- object: {
215
- expected: "an object",
216
- accepts: isPlainObject
217
- },
218
- "bundle-url": {
219
- expected: "an HTTP(S) URL or /path ending in .bundle with a matching platform query and no fragment",
220
- accepts: (value, key) => {
221
- if (typeof value !== "string" || !/^(https?:\/\/|\/(?!\/))/.test(value) || /[\s\\#]/.test(value)) return false;
222
- try {
223
- const url = new URL(value, "http://localhost");
224
- return /\.bundle\/*$/.test(url.pathname) && url.searchParams.get("platform") === key.split(".").at(-1);
225
- } catch {
226
- return false;
227
- }
228
- }
229
- }
230
- };
231
- const SETTING_SHAPES = {
232
- ...OPTIMIZATION_SHAPES,
233
- "ios.deviceType": "string",
234
- "ios.runtime": "string",
235
- "ios.configuration": "string",
236
- "ios.remote": "string",
237
- "ios.simslimProfile": "path",
238
- "ios.signingIdentity": "string",
239
- "ios.signingIdentitySha1": "string",
240
- "ios.lanHost": "string",
241
- "android.systemImage": "string",
242
- "android.dataPartitionSizeGb": "number",
243
- "android.avdConfigFile": "path",
244
- "android.avdConfig": "object",
245
- "android.variant": "string",
246
- "android.keystore": "path",
247
- "android.keystorePassword": "string",
248
- "android.remote": "string",
249
- "metro.tunnel": "string",
250
- "metro.ngrokUrl": "string",
251
- "metro.publicUrl": "string",
252
- "metro.warmupUrl": "object",
253
- "metro.warmupUrl.ios": "bundle-url",
254
- "metro.warmupUrl.android": "bundle-url",
255
- "worktree.exclude": "strings",
256
- "worktree.defaultBranch": "string",
257
- "cache.provider": "string",
258
- "cache.options": "object",
259
- caches: "strings"
260
- };
261
- const KNOWN_SETTINGS = new Set(Object.keys(SETTING_SHAPES));
160
+ const LAYERED_SETTINGS = SETTINGS.filter(isLayeredSetting);
161
+ const KNOWN_SETTINGS = /* @__PURE__ */ new Set([...LAYERED_SETTINGS.map((setting) => setting.key), ...SETTING_GROUPS]);
162
+ const BOUNDS_CHECKED_WHEN_USED = /* @__PURE__ */ new Set(["android.dataPartitionSizeGb"]);
262
163
  const SETTINGS_WITH_RESOLVE_TIME_FALLBACK = /* @__PURE__ */ new Set(["optimizations.android.casToolchain"]);
263
- const PATH_SETTINGS = Object.freeze(Object.entries(SETTING_SHAPES).filter(([, shape]) => shape === "path").map(([path]) => path));
164
+ const PATH_SETTINGS = Object.freeze(LAYERED_SETTINGS.filter((setting) => setting.type.kind === "path").map((setting) => setting.key));
264
165
  function settingValueAt(settings, path) {
265
166
  let node = settings;
266
167
  for (const segment of path.split(".")) {
@@ -271,20 +172,26 @@ function settingValueAt(settings, path) {
271
172
  }
272
173
  function settingShapeErrors(settings) {
273
174
  const errors = [];
274
- for (const [path, shape] of Object.entries(SETTING_SHAPES)) {
275
- if (SETTINGS_WITH_RESOLVE_TIME_FALLBACK.has(path)) continue;
276
- const value = settingValueAt(settings, path);
175
+ for (const group of SETTING_GROUPS) {
176
+ const value = settingValueAt(settings, group);
177
+ if (value === void 0 || isPlainObject(value)) continue;
178
+ errors.push(`Invalid ${group} setting ${JSON.stringify(value)}. Expected an object.`);
179
+ }
180
+ for (const setting of LAYERED_SETTINGS) {
181
+ if (SETTINGS_WITH_RESOLVE_TIME_FALLBACK.has(setting.key)) continue;
182
+ const value = settingValueAt(settings, setting.key);
277
183
  if (value === void 0) continue;
278
- const rule = SETTING_SHAPE_RULES[shape];
279
- if (rule.accepts(value, path)) continue;
280
- errors.push(`Invalid ${path} setting ${JSON.stringify(value)}. Expected ${rule.expected}.`);
184
+ const expected = setting.type.kind === "number" && !BOUNDS_CHECKED_WHEN_USED.has(setting.key) ? settingValueError(setting, value) : acceptsShape(setting, value) ? null : expectedShape(setting.type);
185
+ if (expected) errors.push(`Invalid ${setting.key} setting ${JSON.stringify(value)}. Expected ${expected}.`);
281
186
  }
282
187
  return errors;
283
188
  }
284
189
  const SETTING_SHAPE_REMEDY = "Run `stim guide settings` for the shape each setting takes.";
190
+ const MIN_ANDROID_DATA_PARTITION_SIZE_GB = 6;
191
+ const DEFAULT_ANDROID_DATA_PARTITION_SIZE_GB = 8;
285
192
  const MAX_ANDROID_DATA_PARTITION_SIZE_GB = 16384;
286
193
  function validateAndroidDataPartitionSizeGb(raw) {
287
- if (typeof raw !== "number" || !Number.isSafeInteger(raw) || raw < 6 || raw > 16384) throw new Error(`Invalid android.dataPartitionSizeGb setting ${JSON.stringify(raw)}. Expected an integer from 6 through ${MAX_ANDROID_DATA_PARTITION_SIZE_GB} GiB.`);
194
+ 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.`);
288
195
  return raw;
289
196
  }
290
197
  function androidDataPartitionSizeBytes(sizeGb) {
@@ -296,7 +203,7 @@ function androidDataPartitionSizeGbRaw(settings) {
296
203
  }
297
204
  function androidDataPartitionSizeGbSetting(settings) {
298
205
  const raw = androidDataPartitionSizeGbRaw(settings);
299
- if (raw === void 0) return 8;
206
+ if (raw === void 0) return DEFAULT_ANDROID_DATA_PARTITION_SIZE_GB;
300
207
  return validateAndroidDataPartitionSizeGb(raw);
301
208
  }
302
209
  function androidDataPartitionSizeGbSettingError(settings) {
@@ -425,18 +332,18 @@ function parseAndroidAvdConfigIni(contents) {
425
332
  return entries;
426
333
  }
427
334
  function pathEscapesRoot(root, path) {
428
- const child = relative$1(root, path);
429
- return child === ".." || child.startsWith(`..${sep$1}`) || isAbsolute$1(child);
335
+ const child = relative(root, path);
336
+ return child === ".." || child.startsWith(`..${sep}`) || isAbsolute(child);
430
337
  }
431
338
  function androidAvdConfigSetting(settings, settingsRoot) {
432
339
  if (!isPlainObject(settings) || !isPlainObject(settings.android)) return {};
433
340
  const android = settings.android;
434
341
  let entries = {};
435
342
  if ("avdConfigFile" in android) {
436
- 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.");
343
+ if (typeof android.avdConfigFile !== "string" || !android.avdConfigFile.trim() || android.avdConfigFile !== android.avdConfigFile.trim() || /[\r\n\0]/.test(android.avdConfigFile) || isAbsolute(android.avdConfigFile)) throw new Error("Invalid android.avdConfigFile setting. Expected a relative file path inside the app directory.");
437
344
  try {
438
345
  const root = realpathSync(settingsRoot);
439
- const candidate = resolve$1(root, android.avdConfigFile);
346
+ const candidate = resolve(root, android.avdConfigFile);
440
347
  if (pathEscapesRoot(root, candidate)) throw new Error("path escapes the settings root");
441
348
  const path = realpathSync(candidate);
442
349
  if (pathEscapesRoot(root, path)) throw new Error("symlink target escapes the settings root");
@@ -470,10 +377,10 @@ function androidAvdConfigSettingError(settings, projectPath) {
470
377
  function iosSimSlimProfileSetting(settings, settingsRoot) {
471
378
  if (!isPlainObject(settings) || !isPlainObject(settings.ios) || !("simslimProfile" in settings.ios)) return null;
472
379
  const value = settings.ios.simslimProfile;
473
- 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.");
380
+ if (typeof value !== "string" || !value.trim() || value !== value.trim() || /[\r\n\0]/.test(value) || isAbsolute(value)) throw new Error("Invalid ios.simslimProfile setting. Expected a relative JSON file path inside the app directory.");
474
381
  try {
475
382
  const root = realpathSync(settingsRoot);
476
- const candidate = resolve$1(root, value);
383
+ const candidate = resolve(root, value);
477
384
  if (pathEscapesRoot(root, candidate)) throw new Error("path escapes the settings root");
478
385
  const path = realpathSync(candidate);
479
386
  if (pathEscapesRoot(root, path)) throw new Error("symlink target escapes the settings root");
@@ -526,6 +433,7 @@ function unknownSettingKeys(settings, prefix = "") {
526
433
  const unknown = [];
527
434
  for (const [key, value] of Object.entries(settings)) {
528
435
  const path = prefix ? `${prefix}.${key}` : key;
436
+ if (path === "$schema") continue;
529
437
  if (KNOWN_SETTINGS.has(path) && (!isPlainObject(value) || ![...KNOWN_SETTINGS].some((k) => k.startsWith(`${path}.`)))) continue;
530
438
  if (isPlainObject(value) && [...KNOWN_SETTINGS].some((k) => k.startsWith(`${path}.`))) {
531
439
  unknown.push(...unknownSettingKeys(value, path));
@@ -537,7 +445,7 @@ function unknownSettingKeys(settings, prefix = "") {
537
445
  }
538
446
  function readCommittedSettings(directory) {
539
447
  if (!directory) return {};
540
- const p = join$1(directory, ".stim.json");
448
+ const p = join(directory, ".stim.json");
541
449
  if (!existsSync(p)) return {};
542
450
  try {
543
451
  const parsed = JSON.parse(readFileSync(p, "utf-8"));
@@ -547,8 +455,8 @@ function readCommittedSettings(directory) {
547
455
  }
548
456
  }
549
457
  function settingsLayers({ projectPath, gitCommonDir, repoRoot }) {
550
- const machine = loadConfig();
551
- const machineFile = getConfigPath();
458
+ const machine = loadConfig$1();
459
+ const machineFile = getConfigPath$1();
552
460
  const committedDir = projectPath ?? repoRoot;
553
461
  return [
554
462
  projectPath ? {
@@ -560,7 +468,7 @@ function settingsLayers({ projectPath, gitCommonDir, repoRoot }) {
560
468
  settings: getRepoSettings(gitCommonDir)
561
469
  } : null,
562
470
  committedDir ? {
563
- file: join$1(committedDir, ".stim.json"),
471
+ file: join(committedDir, ".stim.json"),
564
472
  settings: readCommittedSettings(committedDir)
565
473
  } : null,
566
474
  machine?.optimizations === void 0 ? null : {
@@ -617,23 +525,23 @@ function resolveCacheProviderConfig({ projectPath, gitCommonDir, repoRoot }) {
617
525
  baseDir: projectPath ?? null
618
526
  }
619
527
  ];
620
- let provider = null;
621
- let baseDir = null;
622
- for (const layer of layers) {
528
+ const references = layers.map((layer) => {
623
529
  const reference = cacheBlock(layer.settings)?.provider;
624
- if (typeof reference !== "string" || reference.trim() === "" || layer.baseDir === null) continue;
625
- provider = reference.trim();
626
- baseDir = layer.baseDir;
627
- break;
628
- }
629
- if (provider === null || baseDir === null) return null;
630
- const options = mergeSettingsLayers(layers.map((layer) => {
631
- const block = cacheBlock(layer.settings)?.options;
632
- return isPlainObject(block) ? block : null;
633
- }));
530
+ return typeof reference === "string" && reference.trim() !== "" ? reference.trim() : null;
531
+ });
532
+ const selected = layers.findIndex((layer, index) => references[index] !== null && layer.baseDir !== null);
533
+ if (selected === -1) return null;
534
+ const provider = references[selected];
535
+ const baseDir = layers[selected].baseDir;
634
536
  return {
635
537
  provider,
636
- options,
538
+ options: mergeSettingsLayers(layers.map((layer, index) => {
539
+ const namesProvider = references[index] === provider && layer.baseDir === baseDir;
540
+ const overridesAbove = index < selected && references[index] === null;
541
+ if (!namesProvider && !overridesAbove) return null;
542
+ const block = cacheBlock(layer.settings)?.options;
543
+ return isPlainObject(block) ? block : null;
544
+ })),
637
545
  baseDir
638
546
  };
639
547
  }
@@ -644,7 +552,6 @@ function cacheProviderSettingError(settings) {
644
552
  if ("options" in block && !isPlainObject(block.options)) return `Invalid cache.options setting ${JSON.stringify(block.options)}. Expected an object.`;
645
553
  return null;
646
554
  }
647
- const REMOTE_DEVICE_BACKENDS = ["proxy", "eas"];
648
555
  function remoteIosSetting(settings) {
649
556
  return remoteSetting(settings, "ios");
650
557
  }
@@ -657,13 +564,11 @@ function remoteSetting(settings, platform) {
657
564
  const remote = block.remote;
658
565
  return typeof remote === "string" && REMOTE_DEVICE_BACKENDS.includes(remote) ? remote : null;
659
566
  }
660
- function remoteDeviceSettingError(settings) {
661
- for (const platform of ["ios", "android"]) {
662
- const block = settings[platform];
663
- if (!isPlainObject(block) || !("remote" in block)) continue;
664
- if (remoteSetting(settings, platform) === null) return `Invalid ${platform}.remote setting ${JSON.stringify(block.remote)}. Expected one of: ${REMOTE_DEVICE_BACKENDS.join(", ")}.`;
665
- }
666
- return null;
567
+ function metroIdleStopMinutesSetting(settings) {
568
+ const block = settings.metro;
569
+ const value = typeof block === "object" && block !== null ? block.idleStopMinutes : void 0;
570
+ const setting = settingDefinition("metro.idleStopMinutes");
571
+ return setting && value !== void 0 && settingValueError(setting, value) === null ? value : Number(setting?.default);
667
572
  }
668
573
  function tunnelModeSetting(settings) {
669
574
  const block = settings.metro;
@@ -673,12 +578,7 @@ function tunnelModeSetting(settings) {
673
578
  }
674
579
  function metroTunnelSettingError(settings) {
675
580
  const block = settings.metro;
676
- if (!isPlainObject(block)) return null;
677
- if ("tunnel" in block) {
678
- const mode = block.tunnel;
679
- if (typeof mode !== "string" || !TUNNEL_MODES.includes(mode)) return `Invalid metro.tunnel setting ${JSON.stringify(mode)}. Expected one of: ${TUNNEL_MODES.join(", ")}.`;
680
- }
681
- if (!("ngrokUrl" in block)) return null;
581
+ if (!isPlainObject(block) || !("ngrokUrl" in block)) return null;
682
582
  if (block.tunnel !== "ngrok") return "metro.ngrokUrl requires metro.tunnel to be \"ngrok\".";
683
583
  if (normalizedHttpsUrl(block.ngrokUrl) === null) return "metro.ngrokUrl must be a valid HTTPS URL.";
684
584
  return null;
@@ -708,4 +608,4 @@ function normalizedHttpsUrl(raw) {
708
608
  }
709
609
  }
710
610
  //#endregion
711
- export { resolveSettings as A, artifactCachePolicy as B, projectMetroSharedCache as C, remoteDeviceSettingError as D, remoteAndroidSetting as E, tunnelModeSetting as F, optimizationBuildProfile as H, unknownSettingKeys as I, PUBLIC_METRO_ENV as L, settingOrigin as M, settingShapeErrors as N, remoteIosSetting as O, settingsLayers as P, detectProviders as R, ngrokUrlSetting as S, publicUrlSetting as T, resolveOptimizations as U, compilerCacheFallbackMessage as V, iosSigningIdentitySha1SettingError as _, SETTING_SHAPE_REMEDY as a, metroTunnelSettingError as b, androidDataPartitionSizeBytes as c, cacheProviderSettingError as d, iosLanHostSetting as f, iosSigningIdentitySha1Setting as g, iosSigningIdentitySettingError as h, SETTINGS_WITH_RESOLVE_TIME_FALLBACK as i, settingFile as j, resolveCacheProviderConfig as k, androidDataPartitionSizeGbSetting as l, iosSigningIdentitySetting as m, PATH_SETTINGS as n, androidAvdConfigSetting as o, iosLanHostSettingError as p, REMOTE_DEVICE_BACKENDS as r, androidAvdConfigSettingError as s, ANDROID_AVD_CONFIG_HELP as t, androidDataPartitionSizeGbSettingError as u, iosSimSlimProfileSetting as v, projectOptimizations as w, metroWarmupUrlSetting as x, mergeSettingsLayers as y, planMetroReach as z };
611
+ export { resolveCacheProviderConfig as A, planMetroReach as B, ngrokUrlSetting as C, readCommittedSettings as D, publicUrlSetting as E, settingsLayers as F, compilerCacheFallbackMessage as H, tunnelModeSetting as I, unknownSettingKeys as L, settingFile as M, settingOrigin as N, remoteAndroidSetting as O, settingShapeErrors as P, PUBLIC_METRO_ENV as R, metroWarmupUrlSetting as S, projectOptimizations as T, optimizationBuildProfile as U, artifactCachePolicy as V, resolveOptimizations as W, iosSigningIdentitySha1SettingError as _, SETTING_SHAPE_REMEDY as a, metroIdleStopMinutesSetting as b, androidDataPartitionSizeBytes as c, cacheProviderSettingError as d, iosLanHostSetting as f, iosSigningIdentitySha1Setting as g, iosSigningIdentitySettingError as h, SETTINGS_WITH_RESOLVE_TIME_FALLBACK as i, resolveSettings as j, remoteIosSetting as k, androidDataPartitionSizeGbSetting as l, iosSigningIdentitySetting as m, PATH_SETTINGS as n, androidAvdConfigSetting as o, iosLanHostSettingError as p, REMOTE_DEVICE_BACKENDS as r, androidAvdConfigSettingError as s, ANDROID_AVD_CONFIG_HELP as t, androidDataPartitionSizeGbSettingError as u, iosSimSlimProfileSetting as v, projectMetroSharedCache as w, metroTunnelSettingError as x, mergeSettingsLayers as y, detectProviders as z };