stim 1.8.0 → 1.9.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 (80) hide show
  1. package/README.md +23 -155
  2. package/dist/{android-BRgBYzU0.mjs → android-BPGizezh.mjs} +112 -135
  3. package/dist/{android-DeYdjjBP.mjs → android-DEgo4oCo.mjs} +2 -2
  4. package/dist/{android-DWssqHQ8.mjs → android-_y4Ohi5z.mjs} +54 -15
  5. package/dist/android-cas-compiler.mjs +2 -2
  6. package/dist/{android-cas-SWIjzeuL.mjs → android-cas-eiExNLf7.mjs} +21 -14
  7. package/dist/app-install-Uf3a83Tv.mjs +819 -0
  8. package/dist/{build-lock-CgtsB6TV.mjs → build-lock-DPXPLgTc.mjs} +120 -24
  9. package/dist/build-progress-CVqkfPC6.mjs +188 -0
  10. package/dist/{cache-manifest-CYJi8-Hx.mjs → cache-manifest-Dg7OnMrp.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 +2 -0
  14. package/dist/collector-run.mjs +10 -9
  15. package/dist/{command-output-B3ycpsYv.mjs → command-output-gByVmIjy.mjs} +11 -2
  16. package/dist/{config-B7iyKWkL.mjs → config-Cp9lM3wX.mjs} +187 -57
  17. package/dist/{dependency-state-D0M7oKA7.mjs → dependency-state-Bq5DcLGn.mjs} +2 -2
  18. package/dist/{deps-oCXVNEqQ.mjs → deps-Cip5vUm1.mjs} +8 -5
  19. package/dist/{detached-entry-Bpo6ZY_I.mjs → detached-entry-KEu0_AXL.mjs} +1 -1
  20. package/dist/{device-BUoZyMoR.mjs → dev-client-HyqISM0h.mjs} +1178 -635
  21. package/dist/{device-RdujjHLB.mjs → device-D4U-xLty.mjs} +13 -20
  22. package/dist/{device-lease-Ffz_M5t0.mjs → device-lease-Crs-HkJY.mjs} +16 -9
  23. package/dist/{device-pool-BxRwdufO.mjs → device-pool-CnTAz8X1.mjs} +7 -7
  24. package/dist/{device-remote-ByN8P6CB.mjs → device-remote-AQ5rQPqr.mjs} +482 -35
  25. package/dist/{doctor-BMt156qe.mjs → doctor-B_Ei3bZu.mjs} +95 -107
  26. package/dist/{error-diagnostics-D1Kaf-eT.mjs → error-diagnostics-Vfz83GCF.mjs} +22 -16
  27. package/dist/exec-jIAlgilk.mjs +132 -0
  28. package/dist/{gc-v2YcoJvm.mjs → gc-Ckw8RQHi.mjs} +763 -126
  29. package/dist/{guide-BTAlD1_y.mjs → guide-BiAbhs1J.mjs} +891 -501
  30. package/dist/{guide-status-Craq9z0t.mjs → guide-status-BAMVU0ZA.mjs} +5 -3
  31. package/dist/{ios-BcCiI4TS.mjs → ios-BL2NFckr.mjs} +43 -20
  32. package/dist/{ios-BP04pY5f.mjs → ios-BzUJWk1K.mjs} +141 -175
  33. package/dist/{ios-device-CauFJiYx.mjs → ios-device-D972i9wf.mjs} +149 -52
  34. package/dist/{ios-device-CFXA46ez.mjs → ios-device-DPsSm0v_.mjs} +3 -2
  35. package/dist/launch-verify-Q_OtW1Rf.mjs +365 -0
  36. package/dist/{logs-CakdBTtm.mjs → logs-CI9NQSK7.mjs} +18 -13
  37. package/dist/{logs-query-BGL-Pl12.mjs → logs-query-Cqma3WIu.mjs} +36 -28
  38. package/dist/{metro-C7bcJ0uo.mjs → metro-DrLHaMn2.mjs} +78 -8
  39. package/dist/{named-ports-DzMOlmaX.mjs → named-ports-CmLQSIRj.mjs} +4 -4
  40. package/dist/{ndjson-BZwI1NBv.mjs → ndjson-9uFNRwfe.mjs} +36 -3
  41. package/dist/{ownership-Cp52QAK1.mjs → ownership-D15Op8vN.mjs} +3 -3
  42. package/dist/{ownership-wolE-0Do.mjs → ownership-DCQPuWEc.mjs} +26 -7
  43. package/dist/{ports-BIbJaKNW.mjs → ports-BNKoWVWZ.mjs} +3 -3
  44. package/dist/{process-output-Dl5uKE03.mjs → process-output-Fpl8PYi8.mjs} +1 -1
  45. package/dist/{project-DDZWf_16.mjs → project-DS112DYB.mjs} +8 -3
  46. package/dist/{reload-BdypPbjM.mjs → reload-ZNJUo1pl.mjs} +12 -13
  47. package/dist/{server-bare-BQkyxa_b.mjs → server-bare-DWkkcIxV.mjs} +2 -2
  48. package/dist/{server-expo-BVAA1OqJ.mjs → server-expo-B_lctBR3.mjs} +8 -7
  49. package/dist/server-expo-CeILEu86.mjs +2 -0
  50. package/dist/settings-BKNh5rl4.mjs +376 -0
  51. package/dist/{settings-BXXorVM2.mjs → settings-D030k0t4.mjs} +464 -152
  52. package/dist/settings.schema.json +989 -0
  53. package/dist/{teardown-BJqEzPo_.mjs → sim-pool-KaIsp2Ft.mjs} +14 -419
  54. package/dist/simslim-B_LsGLuJ.mjs +259 -0
  55. package/dist/{slot-launch-ZUUe0TQP.mjs → slot-launch-DU9A-gYw.mjs} +4 -4
  56. package/dist/spawn-claims-Dn3C_N6w.mjs +49 -0
  57. package/dist/{start-CmxtOKjU.mjs → start-DYkkKF0s.mjs} +27 -31
  58. package/dist/{state-DZobzxLW.mjs → state-NcJzMEBF.mjs} +5 -3
  59. package/dist/{stats-CLg5RuDS.mjs → stats-B63-JVAY.mjs} +4 -4
  60. package/dist/{stats-BYcZ6n1h.mjs → stats-DG9e66Rz.mjs} +66 -5
  61. package/dist/status-S6ehL4Ui.mjs +932 -0
  62. package/dist/{stop-C8JW6kKp.mjs → stop-BO28HGqF.mjs} +101 -25
  63. package/dist/{stop-B1kOpYei.mjs → stop-T1R-cZRz.mjs} +2 -2
  64. package/dist/supervisor-run.mjs +52 -34
  65. package/dist/{workspace-process-lock-BYfZz_S-.mjs → workspace-process-lock-DWaiRD0e.mjs} +14 -9
  66. package/dist/{workspace-state-BT5E3SA2.mjs → workspace-state-DF2gCFhx.mjs} +52 -29
  67. package/dist/worktree-DKhSF0UX.mjs +2 -0
  68. package/dist/{worktree-CFLA-6MX.mjs → worktree-LGB-UwgG.mjs} +603 -59
  69. package/dist/{worktree-FG_ZKKdb.mjs → worktree-R_cUlE7s.mjs} +62 -41
  70. package/dist/{xcode-B6YaWeg0.mjs → xcode-BZqyxh3e.mjs} +216 -58
  71. package/package.json +5 -6
  72. package/skill/SKILL.md +2 -0
  73. package/dist/app-install-BDbsSgCl.mjs +0 -1175
  74. package/dist/build-slots-C-oAHRT1.mjs +0 -151
  75. package/dist/dev-client-BmT-ioFI.mjs +0 -797
  76. package/dist/exec-B29XNuhP.mjs +0 -77
  77. package/dist/reclaim-KJ-0uWEF.mjs +0 -361
  78. package/dist/server-expo-YDDG3eP7.mjs +0 -2
  79. package/dist/status-DdkLki47.mjs +0 -426
  80. package/dist/{metro-store-CDDZHtPl.mjs → metro-store-DzeWkfqW.mjs} +1 -1
@@ -1,28 +1,21 @@
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, g as loadConfig, m as getRepoSettings, p as getProjectSettings } from "./config-Cp9lM3wX.mjs";
2
+ import { _ as repoRoot, s as gitCommonDir } from "./worktree-R_cUlE7s.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 { isAbsolute as isAbsolute$1 } from "node:path";
6
+ import { createHash } from "node:crypto";
7
7
  //#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
- };
8
+ const ANDROID_COMPILER_CACHE_CHOICES = [
9
+ "auto",
10
+ "ccache",
11
+ "cas",
12
+ "none"
13
+ ];
14
+ const ANDROID_PCH_CHOICES = [
15
+ "auto",
16
+ "on",
17
+ "off"
18
+ ];
26
19
  function compilerCacheFallbackMessage({ fallback, compilerCache, file = null }) {
27
20
  const origin = fallback.fromEnvironment ? "the environment" : file;
28
21
  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 +41,13 @@ function resolveOptimizations(settings = {}, env = process.env) {
48
41
  if (typeof raw !== "string" || !choices.includes(raw)) throw new Error(`Invalid optimizations.${path}. Expected ${choices.join(", ")}.`);
49
42
  return raw;
50
43
  }
51
- const compiler = choice("android.compilerCache", [
52
- "auto",
53
- "ccache",
54
- "cas",
55
- "none"
56
- ], "auto");
44
+ const compiler = choice("android.compilerCache", ANDROID_COMPILER_CACHE_CHOICES, "auto");
57
45
  const fromEnvironment = Boolean(env.STIM_ANDROID_CAS_TOOLCHAIN);
58
46
  const manifest = env.STIM_ANDROID_CAS_TOOLCHAIN || optimizationValue(settings, "android.casToolchain");
59
47
  let fallback = null;
60
48
  let toolchain = null;
61
49
  if (manifest !== void 0) {
62
- if (typeof manifest === "string" && isAbsolute(manifest) && !/[\r\n\0]/.test(manifest)) toolchain = manifest;
50
+ if (typeof manifest === "string" && isAbsolute$1(manifest) && !/[\r\n\0]/.test(manifest)) toolchain = manifest;
63
51
  else fallback = {
64
52
  key: fromEnvironment ? "STIM_ANDROID_CAS_TOOLCHAIN" : "optimizations.android.casToolchain",
65
53
  reason: `is ${JSON.stringify(manifest)}, which is not an absolute path to a toolchain JSON manifest`,
@@ -92,11 +80,7 @@ function resolveOptimizations(settings = {}, env = process.env) {
92
80
  compilerCache,
93
81
  casToolchain: toolchain,
94
82
  compilerCacheFallback: fallback,
95
- pch: choice("android.pch", [
96
- "auto",
97
- "on",
98
- "off"
99
- ], "auto"),
83
+ pch: choice("android.pch", ANDROID_PCH_CHOICES, "auto"),
100
84
  gradleBuildCache: optimizationBoolean(settings, "android.gradleBuildCache"),
101
85
  targetAbiOnly: optimizationBoolean(settings, "android.targetAbiOnly")
102
86
  }
@@ -177,6 +161,409 @@ function detectProviders(onPath) {
177
161
  return MANAGED_PROVIDERS.filter((p) => onPath(p));
178
162
  }
179
163
  //#endregion
164
+ //#region src/workspace/settings-registry.ts
165
+ const SETTING_SCOPES = [
166
+ "machine",
167
+ "workspace",
168
+ "repo",
169
+ "committed"
170
+ ];
171
+ const REMOTE_DEVICE_BACKENDS = ["proxy", "eas"];
172
+ const IOS_SIMULATOR_APPS = [
173
+ "xcode",
174
+ "siniulator",
175
+ "stim-desktop"
176
+ ];
177
+ const PROJECT = [
178
+ "workspace",
179
+ "repo",
180
+ "committed"
181
+ ];
182
+ const EVERY = [
183
+ "machine",
184
+ "workspace",
185
+ "repo",
186
+ "committed"
187
+ ];
188
+ const MACHINE = ["machine"];
189
+ const STRING = { kind: "string" };
190
+ const PATH = { kind: "path" };
191
+ const ABSOLUTE_PATH = {
192
+ kind: "path",
193
+ absolute: true
194
+ };
195
+ const RELATIVE_PATH = {
196
+ kind: "path",
197
+ relative: true
198
+ };
199
+ const BOOLEAN = { kind: "boolean" };
200
+ const OBJECT = { kind: "object" };
201
+ const REMOTE = {
202
+ kind: "choice",
203
+ choices: REMOTE_DEVICE_BACKENDS
204
+ };
205
+ const PARKED_MAX = {
206
+ kind: "number",
207
+ integer: true,
208
+ minimum: 0
209
+ };
210
+ const CAPACITY = {
211
+ kind: "number",
212
+ integer: true,
213
+ minimum: 0
214
+ };
215
+ function optimization(key, description, type = BOOLEAN) {
216
+ return {
217
+ key: `optimizations.${key}`,
218
+ type,
219
+ scopes: EVERY,
220
+ description,
221
+ ...type.kind === "boolean" ? { default: true } : {}
222
+ };
223
+ }
224
+ const SETTINGS = [
225
+ optimization("buildCache", "Read and write native build artifacts"),
226
+ optimization("remoteBuildCache", "Use remote artifact providers"),
227
+ optimization("releaseBundleSwap", "Swap current JS into cached release builds"),
228
+ optimization("metroSharedCache", "Append Stim's shared Metro cache store"),
229
+ optimization("metroWarmup", "Prefetch the development bundle during ios and android"),
230
+ optimization("ios.compilationCache", "Xcode compilation caching"),
231
+ {
232
+ key: "optimizations.ios.swiftCompilationCache",
233
+ type: BOOLEAN,
234
+ scopes: EVERY,
235
+ description: "Swift compilation caching; unset detects toolchain support"
236
+ },
237
+ optimization("ios.prefixMapping", "Clang source and DerivedData prefix mapping"),
238
+ {
239
+ ...optimization("android.compilerCache", "Android compiler cache backend", {
240
+ kind: "choice",
241
+ choices: ANDROID_COMPILER_CACHE_CHOICES
242
+ }),
243
+ default: "auto"
244
+ },
245
+ {
246
+ ...optimization("android.casToolchain", "Absolute path to the Android CAS toolchain manifest", ABSOLUTE_PATH),
247
+ env: "STIM_ANDROID_CAS_TOOLCHAIN"
248
+ },
249
+ {
250
+ ...optimization("android.pch", "Android precompiled headers", {
251
+ kind: "choice",
252
+ choices: ANDROID_PCH_CHOICES
253
+ }),
254
+ default: "auto"
255
+ },
256
+ optimization("android.gradleBuildCache", "Gradle build cache"),
257
+ optimization("android.targetAbiOnly", "Narrow Debug builds to the device ABI"),
258
+ {
259
+ key: "ios.deviceType",
260
+ type: STRING,
261
+ scopes: PROJECT,
262
+ description: "Simulator model for owned simulators"
263
+ },
264
+ {
265
+ key: "ios.runtime",
266
+ type: STRING,
267
+ scopes: PROJECT,
268
+ description: "iOS runtime owned simulators are created on"
269
+ },
270
+ {
271
+ key: "ios.configuration",
272
+ type: STRING,
273
+ scopes: PROJECT,
274
+ default: "Debug",
275
+ description: "Xcode configuration to build, such as Debug or Release"
276
+ },
277
+ {
278
+ key: "ios.remote",
279
+ type: REMOTE,
280
+ scopes: PROJECT,
281
+ description: "Default remote backend for iOS"
282
+ },
283
+ {
284
+ key: "ios.simslimProfile",
285
+ type: RELATIVE_PATH,
286
+ scopes: PROJECT,
287
+ description: "SimSlim JSON profile under the app"
288
+ },
289
+ {
290
+ key: "ios.signingIdentity",
291
+ type: STRING,
292
+ scopes: PROJECT,
293
+ description: "Keychain identity used to re-seal a device build"
294
+ },
295
+ {
296
+ key: "ios.signingIdentitySha1",
297
+ type: {
298
+ kind: "string",
299
+ pattern: "^[0-9A-Fa-f]{40}$",
300
+ patternHelp: "a 40-character hex SHA-1 hash"
301
+ },
302
+ scopes: PROJECT,
303
+ description: "SHA-1 of the signing identity, when two share a name"
304
+ },
305
+ {
306
+ key: "ios.lanHost",
307
+ type: {
308
+ kind: "string",
309
+ pattern: "^(?!-)[A-Za-z0-9-]+(?:\\.(?!-)[A-Za-z0-9-]+)*$",
310
+ patternHelp: "a bare address or hostname, never a URL, scheme, or port"
311
+ },
312
+ scopes: PROJECT,
313
+ description: "Address a phone uses to reach this workspace's Metro"
314
+ },
315
+ {
316
+ key: "android.systemImage",
317
+ type: STRING,
318
+ scopes: PROJECT,
319
+ description: "SDK system image for owned AVDs"
320
+ },
321
+ {
322
+ key: "android.dataPartitionSizeGb",
323
+ type: {
324
+ kind: "number",
325
+ integer: true,
326
+ minimum: 6,
327
+ maximum: 16384
328
+ },
329
+ scopes: PROJECT,
330
+ default: 8,
331
+ description: "Data partition size of a new owned AVD, in GiB"
332
+ },
333
+ {
334
+ key: "android.avdConfigFile",
335
+ type: RELATIVE_PATH,
336
+ scopes: PROJECT,
337
+ description: "AVD config.ini fragment under the app"
338
+ },
339
+ {
340
+ key: "android.avdConfig",
341
+ type: OBJECT,
342
+ scopes: PROJECT,
343
+ description: "Validated AVD config values"
344
+ },
345
+ {
346
+ key: "android.variant",
347
+ type: STRING,
348
+ scopes: PROJECT,
349
+ description: "Gradle build variant"
350
+ },
351
+ {
352
+ key: "android.keystore",
353
+ type: PATH,
354
+ scopes: PROJECT,
355
+ description: "Keystore a re-packed release APK is signed with"
356
+ },
357
+ {
358
+ key: "android.keystorePassword",
359
+ type: STRING,
360
+ scopes: PROJECT,
361
+ sensitive: true,
362
+ description: "Keystore password, or an apksigner env: or file: reference"
363
+ },
364
+ {
365
+ key: "android.remote",
366
+ type: REMOTE,
367
+ scopes: PROJECT,
368
+ description: "Default remote backend for Android"
369
+ },
370
+ {
371
+ key: "metro.tunnel",
372
+ type: {
373
+ kind: "choice",
374
+ choices: TUNNEL_MODES
375
+ },
376
+ scopes: PROJECT,
377
+ default: "auto",
378
+ description: "How a remote device reaches Metro"
379
+ },
380
+ {
381
+ key: "metro.ngrokUrl",
382
+ type: STRING,
383
+ scopes: PROJECT,
384
+ description: "Stable ngrok URL for the managed tunnel"
385
+ },
386
+ {
387
+ key: "metro.publicUrl",
388
+ type: STRING,
389
+ scopes: PROJECT,
390
+ description: "Existing public Metro URL"
391
+ },
392
+ {
393
+ key: "metro.warmupUrl.ios",
394
+ type: { kind: "bundle-url" },
395
+ scopes: PROJECT,
396
+ description: "Bundle URL `stim ios` prefetches to warm Metro"
397
+ },
398
+ {
399
+ key: "metro.warmupUrl.android",
400
+ type: { kind: "bundle-url" },
401
+ scopes: PROJECT,
402
+ description: "Bundle URL `stim android` prefetches to warm Metro"
403
+ },
404
+ {
405
+ key: "worktree.exclude",
406
+ type: { kind: "strings" },
407
+ scopes: ["repo", "committed"],
408
+ committedAt: "repository",
409
+ description: "Ignored paths `worktree warm` skips"
410
+ },
411
+ {
412
+ key: "worktree.defaultBranch",
413
+ type: STRING,
414
+ scopes: ["repo", "committed"],
415
+ committedAt: "repository",
416
+ description: "Branch `worktree warm --refresh` expects the source checkout on"
417
+ },
418
+ {
419
+ key: "cache.provider",
420
+ type: STRING,
421
+ scopes: PROJECT,
422
+ description: "Second-tier cache provider module"
423
+ },
424
+ {
425
+ key: "cache.options",
426
+ type: OBJECT,
427
+ scopes: PROJECT,
428
+ description: "Options passed to the cache provider"
429
+ },
430
+ {
431
+ key: "iosSimulatorApp",
432
+ type: {
433
+ kind: "choice",
434
+ choices: IOS_SIMULATOR_APPS
435
+ },
436
+ scopes: MACHINE,
437
+ default: "xcode",
438
+ description: "App that displays an owned iOS simulator"
439
+ },
440
+ {
441
+ key: "concurrency.maxBuilds",
442
+ type: CAPACITY,
443
+ scopes: MACHINE,
444
+ default: 0,
445
+ env: "STIM_MAX_BUILDS",
446
+ description: "Concurrent native builds; 0 means no limit"
447
+ },
448
+ {
449
+ key: "concurrency.maxDevices",
450
+ type: CAPACITY,
451
+ scopes: MACHINE,
452
+ default: 0,
453
+ env: "STIM_MAX_DEVICES",
454
+ description: "Booted owned devices; 0 means no limit"
455
+ },
456
+ {
457
+ key: "pool.iosParkedMax",
458
+ type: PARKED_MAX,
459
+ scopes: MACHINE,
460
+ default: 3,
461
+ env: "STIM_POOL_IOS_PARKED_MAX",
462
+ scopedHomeValue: 0,
463
+ description: "Parked simulators kept for adoption; 0 turns parking off"
464
+ },
465
+ {
466
+ key: "pool.androidParkedMax",
467
+ type: PARKED_MAX,
468
+ scopes: MACHINE,
469
+ default: 3,
470
+ env: "STIM_POOL_ANDROID_PARKED_MAX",
471
+ scopedHomeValue: 0,
472
+ description: "Parked emulators kept for adoption; 0 turns parking off"
473
+ },
474
+ {
475
+ key: "caches.buildCache",
476
+ type: ABSOLUTE_PATH,
477
+ scopes: MACHINE,
478
+ env: "STIM_BUILD_CACHE",
479
+ description: "Absolute path of the shared build cache"
480
+ },
481
+ {
482
+ key: "caches.metroCache",
483
+ type: ABSOLUTE_PATH,
484
+ scopes: MACHINE,
485
+ env: "STIM_METRO_CACHE",
486
+ description: "Absolute parent path of the Metro transform caches"
487
+ },
488
+ {
489
+ key: "tempDir",
490
+ type: ABSOLUTE_PATH,
491
+ scopes: MACHINE,
492
+ env: "STIM_TMPDIR",
493
+ description: "Absolute directory for large temporary copies"
494
+ }
495
+ ];
496
+ const BY_KEY = new Map(SETTINGS.map((setting) => [setting.key, setting]));
497
+ function settingDefinition(key) {
498
+ return BY_KEY.get(key) ?? null;
499
+ }
500
+ function isLayeredSetting(setting) {
501
+ return setting.scopes.some((scope) => scope !== "machine");
502
+ }
503
+ const SETTING_GROUPS = [...new Set(SETTINGS.filter(isLayeredSetting).flatMap((setting) => setting.key.split(".").slice(0, -1).map((_, index, parts) => parts.slice(0, index + 1).join("."))))];
504
+ function isPlainObject$1(value) {
505
+ return value !== null && typeof value === "object" && !Array.isArray(value);
506
+ }
507
+ function bundleUrlAccepts(value, key) {
508
+ if (typeof value !== "string" || !/^(https?:\/\/|\/(?!\/))/.test(value) || /[\s\\#]/.test(value)) return false;
509
+ try {
510
+ const url = new URL(value, "http://localhost");
511
+ return /\.bundle\/*$/.test(url.pathname) && url.searchParams.get("platform") === key.split(".").at(-1);
512
+ } catch {
513
+ return false;
514
+ }
515
+ }
516
+ function expectedShape(type) {
517
+ switch (type.kind) {
518
+ case "boolean": return "true or false";
519
+ case "string": return "a string";
520
+ case "path": return "a string path";
521
+ case "strings": return "an array of strings";
522
+ case "number": return "a number";
523
+ case "choice": return `one of: ${type.choices.join(", ")}`;
524
+ case "object": return "an object";
525
+ case "bundle-url": return "an HTTP(S) URL or /path ending in .bundle with a matching platform query and no fragment";
526
+ }
527
+ }
528
+ function acceptsShape(setting, value) {
529
+ const { type } = setting;
530
+ switch (type.kind) {
531
+ case "boolean": return typeof value === "boolean";
532
+ case "string":
533
+ case "path": return typeof value === "string";
534
+ case "strings": return Array.isArray(value) && value.every((entry) => typeof entry === "string");
535
+ case "number": return typeof value === "number";
536
+ case "choice": return typeof value === "string" && type.choices.includes(value);
537
+ case "object": return isPlainObject$1(value);
538
+ case "bundle-url": return bundleUrlAccepts(value, setting.key);
539
+ }
540
+ }
541
+ function numberBounds(type) {
542
+ const noun = type.integer ? "a whole number" : "a number";
543
+ if (type.minimum !== void 0 && type.maximum !== void 0) return `${noun} from ${type.minimum} through ${type.maximum}`;
544
+ if (type.minimum !== void 0) return `${noun}, ${type.minimum} or more`;
545
+ return noun;
546
+ }
547
+ function settingValueError(setting, value) {
548
+ const { type } = setting;
549
+ if (!acceptsShape(setting, value)) return type.kind === "number" ? numberBounds(type) : expectedShape(type);
550
+ if (type.kind === "number") {
551
+ const number = value;
552
+ if (!Number.isFinite(number) || type.integer && !Number.isSafeInteger(number) || type.minimum !== void 0 && number < type.minimum || type.maximum !== void 0 && number > type.maximum) return numberBounds(type);
553
+ }
554
+ if (type.kind === "path") {
555
+ const path = value;
556
+ if (type.absolute && !isAbsolute$1(path)) return "an absolute path";
557
+ if (type.relative && (path.trim() === "" || isAbsolute$1(path))) return "a relative path inside the app directory";
558
+ }
559
+ if (type.kind === "string" && type.pattern && !new RegExp(type.pattern).test(value)) return type.patternHelp ?? `a string matching ${type.pattern}`;
560
+ return null;
561
+ }
562
+ const SENSITIVE_REFERENCE = /^(env|file):\S/;
563
+ function isSensitiveReference(value) {
564
+ return typeof value === "string" && SENSITIVE_REFERENCE.test(value);
565
+ }
566
+ //#endregion
180
567
  //#region src/workspace/settings.ts
181
568
  function isPlainObject(v) {
182
569
  return v !== null && typeof v === "object" && !Array.isArray(v);
@@ -190,77 +577,10 @@ function mergeSettingsLayers(layers) {
190
577
  }
191
578
  return out;
192
579
  }
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));
580
+ const LAYERED_SETTINGS = SETTINGS.filter(isLayeredSetting);
581
+ const KNOWN_SETTINGS = /* @__PURE__ */ new Set([...LAYERED_SETTINGS.map((setting) => setting.key), ...SETTING_GROUPS]);
262
582
  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));
583
+ const PATH_SETTINGS = Object.freeze(LAYERED_SETTINGS.filter((setting) => setting.type.kind === "path").map((setting) => setting.key));
264
584
  function settingValueAt(settings, path) {
265
585
  let node = settings;
266
586
  for (const segment of path.split(".")) {
@@ -271,20 +591,25 @@ function settingValueAt(settings, path) {
271
591
  }
272
592
  function settingShapeErrors(settings) {
273
593
  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);
277
- 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}.`);
594
+ for (const group of SETTING_GROUPS) {
595
+ const value = settingValueAt(settings, group);
596
+ if (value === void 0 || isPlainObject(value)) continue;
597
+ errors.push(`Invalid ${group} setting ${JSON.stringify(value)}. Expected an object.`);
598
+ }
599
+ for (const setting of LAYERED_SETTINGS) {
600
+ if (SETTINGS_WITH_RESOLVE_TIME_FALLBACK.has(setting.key)) continue;
601
+ const value = settingValueAt(settings, setting.key);
602
+ if (value === void 0 || acceptsShape(setting, value)) continue;
603
+ errors.push(`Invalid ${setting.key} setting ${JSON.stringify(value)}. Expected ${expectedShape(setting.type)}.`);
281
604
  }
282
605
  return errors;
283
606
  }
284
607
  const SETTING_SHAPE_REMEDY = "Run `stim guide settings` for the shape each setting takes.";
608
+ const MIN_ANDROID_DATA_PARTITION_SIZE_GB = 6;
609
+ const DEFAULT_ANDROID_DATA_PARTITION_SIZE_GB = 8;
285
610
  const MAX_ANDROID_DATA_PARTITION_SIZE_GB = 16384;
286
611
  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.`);
612
+ 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
613
  return raw;
289
614
  }
290
615
  function androidDataPartitionSizeBytes(sizeGb) {
@@ -296,7 +621,7 @@ function androidDataPartitionSizeGbRaw(settings) {
296
621
  }
297
622
  function androidDataPartitionSizeGbSetting(settings) {
298
623
  const raw = androidDataPartitionSizeGbRaw(settings);
299
- if (raw === void 0) return 8;
624
+ if (raw === void 0) return DEFAULT_ANDROID_DATA_PARTITION_SIZE_GB;
300
625
  return validateAndroidDataPartitionSizeGb(raw);
301
626
  }
302
627
  function androidDataPartitionSizeGbSettingError(settings) {
@@ -425,18 +750,18 @@ function parseAndroidAvdConfigIni(contents) {
425
750
  return entries;
426
751
  }
427
752
  function pathEscapesRoot(root, path) {
428
- const child = relative$1(root, path);
429
- return child === ".." || child.startsWith(`..${sep$1}`) || isAbsolute$1(child);
753
+ const child = relative(root, path);
754
+ return child === ".." || child.startsWith(`..${sep}`) || isAbsolute(child);
430
755
  }
431
756
  function androidAvdConfigSetting(settings, settingsRoot) {
432
757
  if (!isPlainObject(settings) || !isPlainObject(settings.android)) return {};
433
758
  const android = settings.android;
434
759
  let entries = {};
435
760
  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.");
761
+ 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
762
  try {
438
763
  const root = realpathSync(settingsRoot);
439
- const candidate = resolve$1(root, android.avdConfigFile);
764
+ const candidate = resolve(root, android.avdConfigFile);
440
765
  if (pathEscapesRoot(root, candidate)) throw new Error("path escapes the settings root");
441
766
  const path = realpathSync(candidate);
442
767
  if (pathEscapesRoot(root, path)) throw new Error("symlink target escapes the settings root");
@@ -470,10 +795,10 @@ function androidAvdConfigSettingError(settings, projectPath) {
470
795
  function iosSimSlimProfileSetting(settings, settingsRoot) {
471
796
  if (!isPlainObject(settings) || !isPlainObject(settings.ios) || !("simslimProfile" in settings.ios)) return null;
472
797
  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.");
798
+ 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
799
  try {
475
800
  const root = realpathSync(settingsRoot);
476
- const candidate = resolve$1(root, value);
801
+ const candidate = resolve(root, value);
477
802
  if (pathEscapesRoot(root, candidate)) throw new Error("path escapes the settings root");
478
803
  const path = realpathSync(candidate);
479
804
  if (pathEscapesRoot(root, path)) throw new Error("symlink target escapes the settings root");
@@ -526,6 +851,7 @@ function unknownSettingKeys(settings, prefix = "") {
526
851
  const unknown = [];
527
852
  for (const [key, value] of Object.entries(settings)) {
528
853
  const path = prefix ? `${prefix}.${key}` : key;
854
+ if (path === "$schema") continue;
529
855
  if (KNOWN_SETTINGS.has(path) && (!isPlainObject(value) || ![...KNOWN_SETTINGS].some((k) => k.startsWith(`${path}.`)))) continue;
530
856
  if (isPlainObject(value) && [...KNOWN_SETTINGS].some((k) => k.startsWith(`${path}.`))) {
531
857
  unknown.push(...unknownSettingKeys(value, path));
@@ -537,7 +863,7 @@ function unknownSettingKeys(settings, prefix = "") {
537
863
  }
538
864
  function readCommittedSettings(directory) {
539
865
  if (!directory) return {};
540
- const p = join$1(directory, ".stim.json");
866
+ const p = join(directory, ".stim.json");
541
867
  if (!existsSync(p)) return {};
542
868
  try {
543
869
  const parsed = JSON.parse(readFileSync(p, "utf-8"));
@@ -560,7 +886,7 @@ function settingsLayers({ projectPath, gitCommonDir, repoRoot }) {
560
886
  settings: getRepoSettings(gitCommonDir)
561
887
  } : null,
562
888
  committedDir ? {
563
- file: join$1(committedDir, ".stim.json"),
889
+ file: join(committedDir, ".stim.json"),
564
890
  settings: readCommittedSettings(committedDir)
565
891
  } : null,
566
892
  machine?.optimizations === void 0 ? null : {
@@ -617,23 +943,23 @@ function resolveCacheProviderConfig({ projectPath, gitCommonDir, repoRoot }) {
617
943
  baseDir: projectPath ?? null
618
944
  }
619
945
  ];
620
- let provider = null;
621
- let baseDir = null;
622
- for (const layer of layers) {
946
+ const references = layers.map((layer) => {
623
947
  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
- }));
948
+ return typeof reference === "string" && reference.trim() !== "" ? reference.trim() : null;
949
+ });
950
+ const selected = layers.findIndex((layer, index) => references[index] !== null && layer.baseDir !== null);
951
+ if (selected === -1) return null;
952
+ const provider = references[selected];
953
+ const baseDir = layers[selected].baseDir;
634
954
  return {
635
955
  provider,
636
- options,
956
+ options: mergeSettingsLayers(layers.map((layer, index) => {
957
+ const namesProvider = references[index] === provider && layer.baseDir === baseDir;
958
+ const overridesAbove = index < selected && references[index] === null;
959
+ if (!namesProvider && !overridesAbove) return null;
960
+ const block = cacheBlock(layer.settings)?.options;
961
+ return isPlainObject(block) ? block : null;
962
+ })),
637
963
  baseDir
638
964
  };
639
965
  }
@@ -644,7 +970,6 @@ function cacheProviderSettingError(settings) {
644
970
  if ("options" in block && !isPlainObject(block.options)) return `Invalid cache.options setting ${JSON.stringify(block.options)}. Expected an object.`;
645
971
  return null;
646
972
  }
647
- const REMOTE_DEVICE_BACKENDS = ["proxy", "eas"];
648
973
  function remoteIosSetting(settings) {
649
974
  return remoteSetting(settings, "ios");
650
975
  }
@@ -657,14 +982,6 @@ function remoteSetting(settings, platform) {
657
982
  const remote = block.remote;
658
983
  return typeof remote === "string" && REMOTE_DEVICE_BACKENDS.includes(remote) ? remote : null;
659
984
  }
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;
667
- }
668
985
  function tunnelModeSetting(settings) {
669
986
  const block = settings.metro;
670
987
  if (typeof block !== "object" || block === null) return null;
@@ -673,12 +990,7 @@ function tunnelModeSetting(settings) {
673
990
  }
674
991
  function metroTunnelSettingError(settings) {
675
992
  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;
993
+ if (!isPlainObject(block) || !("ngrokUrl" in block)) return null;
682
994
  if (block.tunnel !== "ngrok") return "metro.ngrokUrl requires metro.tunnel to be \"ngrok\".";
683
995
  if (normalizedHttpsUrl(block.ngrokUrl) === null) return "metro.ngrokUrl must be a valid HTTPS URL.";
684
996
  return null;
@@ -708,4 +1020,4 @@ function normalizedHttpsUrl(raw) {
708
1020
  }
709
1021
  }
710
1022
  //#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 };
1023
+ export { settingFile as A, isSensitiveReference as B, projectOptimizations as C, remoteIosSetting as D, remoteAndroidSetting as E, unknownSettingKeys as F, planMetroReach as G, settingValueError as H, IOS_SIMULATOR_APPS as I, optimizationBuildProfile as J, artifactCachePolicy as K, REMOTE_DEVICE_BACKENDS as L, settingShapeErrors as M, settingsLayers as N, resolveCacheProviderConfig as O, tunnelModeSetting as P, SETTINGS as R, projectMetroSharedCache as S, readCommittedSettings as T, PUBLIC_METRO_ENV as U, settingDefinition as V, detectProviders as W, resolveOptimizations as Y, iosSimSlimProfileSetting as _, androidAvdConfigSetting as a, metroWarmupUrlSetting as b, androidDataPartitionSizeGbSetting as c, iosLanHostSetting as d, iosLanHostSettingError as f, iosSigningIdentitySha1SettingError as g, iosSigningIdentitySha1Setting as h, SETTING_SHAPE_REMEDY as i, settingOrigin as j, resolveSettings as k, androidDataPartitionSizeGbSettingError as l, iosSigningIdentitySettingError as m, PATH_SETTINGS as n, androidAvdConfigSettingError as o, iosSigningIdentitySetting as p, compilerCacheFallbackMessage as q, SETTINGS_WITH_RESOLVE_TIME_FALLBACK as r, androidDataPartitionSizeBytes as s, ANDROID_AVD_CONFIG_HELP as t, cacheProviderSettingError as u, mergeSettingsLayers as v, publicUrlSetting as w, ngrokUrlSetting as x, metroTunnelSettingError as y, SETTING_SCOPES as z };