stim 1.13.0 → 1.15.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 (133) hide show
  1. package/README.md +2 -0
  2. package/dist/{activity-DfVKZTEB.mjs → activity-B2-kb7k0.mjs} +281 -41
  3. package/dist/{android-BHfAu2mf.mjs → android-CYmq0jNi.mjs} +2 -2
  4. package/dist/{android-CD8pr8Q2.mjs → android-Cu_L4RpL.mjs} +524 -412
  5. package/dist/{android-qx_31wJo.mjs → android-Db2eEMaf.mjs} +152 -65
  6. package/dist/android-cas-PnKfAdB2.mjs +167 -0
  7. package/dist/android-cas-compiler.mjs +2 -2
  8. package/dist/{app-install-CDFY2nUL.mjs → app-install-CAAplXAt.mjs} +162 -173
  9. package/dist/{budget-E5o91NR1.mjs → budget-CMiqghig.mjs} +1048 -77
  10. package/dist/{build-plan-BTa93o_r.mjs → build-plan-aeGMFX7e.mjs} +193 -650
  11. package/dist/build-progress-C2Jv1AtA.mjs +872 -0
  12. package/dist/build-slots-Df1ApSuH.mjs +127 -0
  13. package/dist/{cache-manifest-DWPF4RKJ.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
  14. package/dist/cache-manifest.mjs +1 -1
  15. package/dist/{chrome-B5hWgQJS.mjs → chrome-BrcVWQyD.mjs} +4 -4
  16. package/dist/cli-0cX5s4NM.mjs +45 -0
  17. package/dist/cli.mjs +6 -42
  18. package/dist/client-C2IS36Qf.mjs +1333 -0
  19. package/dist/collector-run.mjs +8 -7
  20. package/dist/{command-output-BIL7rlX7.mjs → command-output-CliHHn7a.mjs} +1 -1
  21. package/dist/{config-0jCcMM0E.mjs → config-D0DbpZtM.mjs} +29 -29
  22. package/dist/{created-devices-Cd558h31.mjs → created-devices-BwVb3liH.mjs} +2 -2
  23. package/dist/{dependency-state-DNN0yfx6.mjs → dependency-state-BmsjKZ2H.mjs} +2 -2
  24. package/dist/{deps-2Wo81np9.mjs → deps-BFlKbOIp.mjs} +3 -3
  25. package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
  26. package/dist/dev-client-CjzLoNs0.mjs +80 -0
  27. package/dist/{device-Cf7vl-so.mjs → device-BZ1IyS5A.mjs} +7 -7
  28. package/dist/device-capacity-DMuKC7nx.mjs +136 -0
  29. package/dist/device-host-worker.d.mts +1 -0
  30. package/dist/device-host-worker.mjs +769 -0
  31. package/dist/device-ios-DMQBUPus.mjs +381 -0
  32. package/dist/{device-lease-CL9dZf73.mjs → device-lease-W5Pi1q38.mjs} +43 -36
  33. package/dist/{device-pool-MuoiyRcu.mjs → device-pool-B2r38YjS.mjs} +4 -4
  34. package/dist/{device-remote-DT9ngZiD.mjs → device-remote-9ulPsdhY.mjs} +68 -162
  35. package/dist/{doctor-D2iysNNH.mjs → doctor-BcE5xsBn.mjs} +191 -99
  36. package/dist/{error-diagnostics-BoiOhck8.mjs → error-diagnostics-Cb0P1WLS.mjs} +12 -11
  37. package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
  38. package/dist/{gc-eA-PRBX8.mjs → gc-BuL1Genr.mjs} +430 -102
  39. package/dist/{android-cas-B9Kv9aIy.mjs → gradle-CmdOBq1d.mjs} +8 -294
  40. package/dist/{guide-BfZ0OmiP.mjs → guide-cCSGRFd3.mjs} +2052 -240
  41. package/dist/{guide-status-BaMYu55M.mjs → guide-status-CIoWK6mG.mjs} +3 -3
  42. package/dist/hosted-macos-DwowNU7W.mjs +447 -0
  43. package/dist/idle-shutdown-B5LPaNyY.mjs +122 -0
  44. package/dist/in-use-CndPMilN.mjs +110 -0
  45. package/dist/{ios-CmEzZiOj.mjs → ios-3CtCRhwr.mjs} +201 -10
  46. package/dist/{ios-CcfCeZ19.mjs → ios-Chob8ny7.mjs} +560 -259
  47. package/dist/{ios-device-BW-t_Lkv.mjs → ios-device-DERmMmk-.mjs} +12 -7
  48. package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
  49. package/dist/{launch-verify-CuJaBpEa.mjs → launch-verify-CLlofxRt.mjs} +42 -11
  50. package/dist/{logs-DcSRbC2t.mjs → logs-h9h6MUvE.mjs} +95 -13
  51. package/dist/machine-usage-D_YYMzfR.mjs +283 -0
  52. package/dist/machines-CYtOCqcj.mjs +259 -0
  53. package/dist/macos-BwtbUtTN.mjs +468 -0
  54. package/dist/macos-run.d.mts +5 -0
  55. package/dist/macos-run.mjs +2 -0
  56. package/dist/{merge-state-ytFQCeh4.mjs → merge-state-BnxLNQiM.mjs} +22 -12
  57. package/dist/{metro-BPeY1YMh.mjs → metro-Di8EN2Tb.mjs} +20 -120
  58. package/dist/{named-ports-n7tnJsI8.mjs → named-ports-Dj_XBNaL.mjs} +8 -64
  59. package/dist/native-run-DFDoIRbY.mjs +147 -0
  60. package/dist/{native-runtime-CiiFHt0e.mjs → native-runtime-i6OkVg-w.mjs} +11 -6
  61. package/dist/offload-worker.d.mts +121 -0
  62. package/dist/offload-worker.mjs +601 -0
  63. package/dist/{ownership-Bz2kETAS.mjs → ownership-BkYxUtuj.mjs} +3 -2
  64. package/dist/{page-BTu7I60K.mjs → page-QF_Ke10s.mjs} +7 -2
  65. package/dist/{ports-C5UutqpJ.mjs → ports-DddwttKM.mjs} +3 -3
  66. package/dist/prebuild-BvxDlD6C.mjs +213 -0
  67. package/dist/process-identity-BmRm1qV0.mjs +12 -0
  68. package/dist/{project-DGeclhvO.mjs → project-CV3tDFHl.mjs} +9 -18
  69. package/dist/pull-request-pxnDCQzn.mjs +322 -0
  70. package/dist/pull-requests.d.mts +120 -0
  71. package/dist/pull-requests.mjs +2 -0
  72. package/dist/recordings-GEXp4iBT.mjs +37 -0
  73. package/dist/{reload-C-cV5yy_.mjs → reload-ChPiwvot.mjs} +70 -22
  74. package/dist/{remote-cache--aehr4Pr.mjs → remote-cache-CO2elm_b.mjs} +4 -4
  75. package/dist/run-B-xqRrsg.mjs +119 -0
  76. package/dist/{server-bare-Bq8BpSIo.mjs → server-bare-X7PUgizL.mjs} +60 -13
  77. package/dist/{server-expo-DUCg52-K.mjs → server-expo-DhtX6bom.mjs} +6 -10
  78. package/dist/server-expo-KJeD109I.mjs +2 -0
  79. package/dist/{settings-xB0Frkg6.mjs → settings-CduVcoxj.mjs} +77 -26
  80. package/dist/{settings-ssVBToDR.mjs → settings-DbL6uhH8.mjs} +16 -10
  81. package/dist/settings.schema.json +357 -2
  82. package/dist/{simslim-Bbg9UWrt.mjs → simslim-Ciluxlq4.mjs} +8 -132
  83. package/dist/slot-launch-BY0BrOLh.mjs +65 -0
  84. package/dist/{spawn-claims-PT8aQpCa.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
  85. package/dist/spawn-entry-DncpJvWL.mjs +16 -0
  86. package/dist/stage-CzaiwFsI.mjs +143 -0
  87. package/dist/start-CPO4wRg7.mjs +2 -0
  88. package/dist/{start-BZhItkiL.mjs → start-CUD9nasA.mjs} +108 -61
  89. package/dist/state-B4PQFXMW.d.mts +12 -0
  90. package/dist/state-BxCgeTQu.mjs +41 -0
  91. package/dist/{state-DwAU2_-D.mjs → state-CaeaHbDA.mjs} +7 -4
  92. package/dist/{state-l1pYwOre.mjs → state-DFm77wRn.mjs} +67 -5
  93. package/dist/stats-DBLLFmKd.mjs +66 -0
  94. package/dist/{status-TJlL0HWo.mjs → status-DYXxNOT0.mjs} +425 -260
  95. package/dist/{status-sszP4XKU.mjs → status-l84Gbvp2.mjs} +122 -20
  96. package/dist/stdout-reader-C8yjre1x.mjs +23 -0
  97. package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
  98. package/dist/stim-footprint.swift +32 -0
  99. package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
  100. package/dist/{stop-BZ2qMiBP.mjs → stop-Bad61z_2.mjs} +128 -19
  101. package/dist/stop-Bvf1MflS.mjs +48 -0
  102. package/dist/{stop-DskDKNTU.mjs → stop-CceYh1yG.mjs} +2 -2
  103. package/dist/stop-cause-CZ_Mr-v9.mjs +665 -0
  104. package/dist/supervisor-run.d.mts +44 -12
  105. package/dist/supervisor-run.mjs +276 -33
  106. package/dist/support-CDQbDkL5.mjs +315 -0
  107. package/dist/support-WLOzVKUv.mjs +176 -0
  108. package/dist/teardown-BYuQafiA.mjs +718 -0
  109. package/dist/{xcode-LM6A8cNZ.mjs → toolchain-DmeYjXD9.mjs} +363 -13
  110. package/dist/warm-progress-DVHLN4uC.mjs +463 -0
  111. package/dist/{idle-DxLi5OkN.mjs → watchman-Bx0wceoF.mjs} +55 -8
  112. package/dist/{web-sWXd5iek.mjs → web-k6w8-HnE.mjs} +39 -22
  113. package/dist/web-run.mjs +326 -13
  114. package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
  115. package/dist/workspace-state-CL2V2Ons.mjs +371 -0
  116. package/dist/worktree-BT_uULFx.mjs +2 -0
  117. package/dist/{worktree-DeBQSVtR.mjs → worktree-C0c5Udwa.mjs} +55 -54
  118. package/dist/{worktree-D4Do_XiQ.mjs → worktree-qXKzADSC.mjs} +161 -255
  119. package/package.json +10 -5
  120. package/shim/bundle-response.cjs +55 -10
  121. package/shim/bundle-response.d.cts +1 -0
  122. package/shim/expo-metro-config.cjs +10 -2
  123. package/dist/build-progress-l7d-X-ZP.mjs +0 -247
  124. package/dist/in-use-wYEaKQM9.mjs +0 -249
  125. package/dist/ownership-PUP4Dwai.mjs +0 -1138
  126. package/dist/server-expo-BeYwP_Xy.mjs +0 -2
  127. package/dist/slot-launch-BIWEW-wI.mjs +0 -19
  128. package/dist/start-Rw2GD3nV.mjs +0 -2
  129. package/dist/stats-CaSwmsSZ.mjs +0 -62
  130. package/dist/stats-D8rUTPHd.mjs +0 -368
  131. package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
  132. package/dist/worktree-3cBa-bQt.mjs +0 -2
  133. package/dist/{metro-store-DzeWkfqW.mjs → metro-store-CDDZHtPl.mjs} +1 -1
@@ -1,10 +1,10 @@
1
- import { _ as loadConfig$1, f as getConfigPath$1, h as getRepoSettings, m as getProjectSettings } from "./config-0jCcMM0E.mjs";
2
- import { s as gitCommonDir, y as repoRoot } from "./worktree-DeBQSVtR.mjs";
3
- import { existsSync, readFileSync, realpathSync, statSync } from "fs";
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, WEB_VIEWPORTS, acceptsShape, expectedShape, isLayeredSetting, settingDefinition, settingValueError } from "@stim-cli/core/state";
6
- import { isAbsolute as isAbsolute$1 } from "node:path";
1
+ import { _ as loadConfig$1, f as getConfigPath$1, h as getRepoSettings, m as getProjectSettings } from "./config-D0DbpZtM.mjs";
2
+ import { s as gitCommonDir, y as repoRoot } from "./worktree-C0c5Udwa.mjs";
7
3
  import { createHash } from "node:crypto";
4
+ import { isAbsolute } from "node:path";
5
+ 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";
7
+ 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";
8
8
  //#region src/optimizations.ts
9
9
  function compilerCacheFallbackMessage({ fallback, compilerCache, file = null }) {
10
10
  const origin = fallback.fromEnvironment ? "the environment" : file;
@@ -37,7 +37,7 @@ function resolveOptimizations(settings = {}, env = process.env) {
37
37
  let fallback = null;
38
38
  let toolchain = null;
39
39
  if (manifest !== void 0) {
40
- if (typeof manifest === "string" && isAbsolute$1(manifest) && !/[\r\n\0]/.test(manifest)) toolchain = manifest;
40
+ if (typeof manifest === "string" && isAbsolute(manifest) && !/[\r\n\0]/.test(manifest)) toolchain = manifest;
41
41
  else fallback = {
42
42
  key: fromEnvironment ? "STIM_ANDROID_CAS_TOOLCHAIN" : "optimizations.android.casToolchain",
43
43
  reason: `is ${JSON.stringify(manifest)}, which is not an absolute path to a toolchain JSON manifest`,
@@ -107,7 +107,8 @@ const PUBLIC_METRO_ENV = "STIM_METRO_PUBLIC_URL";
107
107
  const MANAGED_PROVIDERS = ["ngrok", "cloudflared"];
108
108
  const NAMED = {
109
109
  cloudflared: "`cloudflared` (brew install cloudflared)",
110
- ngrok: "`ngrok` (brew install ngrok, then `ngrok config add-authtoken <token>`)"
110
+ ngrok: "`ngrok` (brew install ngrok, then `ngrok config add-authtoken <token>`)",
111
+ tailscale: "Tailscale and sign in to your tailnet"
111
112
  };
112
113
  function planMetroReach({ mode, metroPort, publicUrl = null, isExpo, available = [] }) {
113
114
  const named = publicUrl?.trim().replace(/\/+$/, "") || null;
@@ -126,21 +127,25 @@ function planMetroReach({ mode, metroPort, publicUrl = null, isExpo, available =
126
127
  };
127
128
  return { expoTunnel: true };
128
129
  }
129
- if (mode === "cloudflared" || mode === "ngrok") {
130
+ if (mode === "cloudflared" || mode === "ngrok" || mode === "tailscale") {
130
131
  if (!available.includes(mode)) return {
131
132
  failed: `metro.tunnel is "${mode}", but ${mode} is not on PATH.`,
132
133
  remedy: `Install ${NAMED[mode] ?? mode}, or set metro.tunnel to "auto".`
133
134
  };
134
135
  return { start: mode };
135
136
  }
136
- const provider = available[0];
137
+ const provider = available.find((candidate) => candidate !== "tailscale");
137
138
  if (provider) return { start: provider };
138
139
  return {
139
140
  failed: `A remote device cannot reach this workspace's Metro on port ${metroPort}, and no tunnel is available to give it one.`,
140
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".`
141
142
  };
142
143
  }
143
- function detectProviders(onPath) {
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"] : [];
144
149
  return MANAGED_PROVIDERS.filter((p) => onPath(p));
145
150
  }
146
151
  //#endregion
@@ -332,18 +337,18 @@ function parseAndroidAvdConfigIni(contents) {
332
337
  return entries;
333
338
  }
334
339
  function pathEscapesRoot(root, path) {
335
- const child = relative(root, path);
336
- return child === ".." || child.startsWith(`..${sep}`) || isAbsolute(child);
340
+ const child = relative$1(root, path);
341
+ return child === ".." || child.startsWith(`..${sep$1}`) || isAbsolute$1(child);
337
342
  }
338
343
  function androidAvdConfigSetting(settings, settingsRoot) {
339
344
  if (!isPlainObject(settings) || !isPlainObject(settings.android)) return {};
340
345
  const android = settings.android;
341
346
  let entries = {};
342
347
  if ("avdConfigFile" in android) {
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.");
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.");
344
349
  try {
345
350
  const root = realpathSync(settingsRoot);
346
- const candidate = resolve(root, android.avdConfigFile);
351
+ const candidate = resolve$1(root, android.avdConfigFile);
347
352
  if (pathEscapesRoot(root, candidate)) throw new Error("path escapes the settings root");
348
353
  const path = realpathSync(candidate);
349
354
  if (pathEscapesRoot(root, path)) throw new Error("symlink target escapes the settings root");
@@ -377,10 +382,10 @@ function androidAvdConfigSettingError(settings, projectPath) {
377
382
  function iosSimSlimProfileSetting(settings, settingsRoot) {
378
383
  if (!isPlainObject(settings) || !isPlainObject(settings.ios) || !("simslimProfile" in settings.ios)) return null;
379
384
  const value = settings.ios.simslimProfile;
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.");
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.");
381
386
  try {
382
387
  const root = realpathSync(settingsRoot);
383
- const candidate = resolve(root, value);
388
+ const candidate = resolve$1(root, value);
384
389
  if (pathEscapesRoot(root, candidate)) throw new Error("path escapes the settings root");
385
390
  const path = realpathSync(candidate);
386
391
  if (pathEscapesRoot(root, path)) throw new Error("symlink target escapes the settings root");
@@ -445,7 +450,7 @@ function unknownSettingKeys(settings, prefix = "") {
445
450
  }
446
451
  function readCommittedSettings(directory) {
447
452
  if (!directory) return {};
448
- const p = join(directory, ".stim.json");
453
+ const p = join$1(directory, ".stim.json");
449
454
  if (!existsSync(p)) return {};
450
455
  try {
451
456
  const parsed = JSON.parse(readFileSync(p, "utf-8"));
@@ -454,12 +459,25 @@ function readCommittedSettings(directory) {
454
459
  return {};
455
460
  }
456
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."));
457
473
  function machineLayerSettings(machine) {
458
474
  const settings = {};
459
475
  const optimizations = settingValueAt(machine, "optimizations");
460
476
  if (optimizations !== void 0) settings.optimizations = optimizations;
461
- const deviceProfile = settingValueAt(machine, "android.deviceProfile");
462
- if (deviceProfile !== void 0) settings.android = { deviceProfile };
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
+ }
463
481
  return Object.keys(settings).length > 0 ? settings : null;
464
482
  }
465
483
  function settingsLayers({ projectPath, gitCommonDir, repoRoot }) {
@@ -469,18 +487,22 @@ function settingsLayers({ projectPath, gitCommonDir, repoRoot }) {
469
487
  const machineSettings = machineLayerSettings(machine);
470
488
  return [
471
489
  projectPath ? {
490
+ scope: "workspace",
472
491
  file: `${machineFile} (projects["${projectPath}"].settings)`,
473
492
  settings: getProjectSettings(projectPath)
474
493
  } : null,
475
494
  gitCommonDir ? {
495
+ scope: "repo",
476
496
  file: `${machineFile} (repos["${gitCommonDir}"].settings)`,
477
497
  settings: getRepoSettings(gitCommonDir)
478
498
  } : null,
479
499
  committedDir ? {
480
- file: join(committedDir, ".stim.json"),
500
+ scope: "committed",
501
+ file: join$1(committedDir, ".stim.json"),
481
502
  settings: readCommittedSettings(committedDir)
482
503
  } : null,
483
504
  machineSettings ? {
505
+ scope: "machine",
484
506
  file: machineFile,
485
507
  settings: machineSettings
486
508
  } : null
@@ -496,6 +518,11 @@ function settingOrigin(layers, dottedKey) {
496
518
  }
497
519
  return null;
498
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
+ }
499
526
  function settingFile(context, dottedKey) {
500
527
  return settingOrigin(settingsLayers(context), dottedKey)?.file ?? null;
501
528
  }
@@ -573,12 +600,18 @@ function remoteSetting(settings, platform) {
573
600
  const remote = block.remote;
574
601
  return typeof remote === "string" && REMOTE_DEVICE_BACKENDS.includes(remote) ? remote : null;
575
602
  }
576
- function metroIdleStopMinutesSetting(settings) {
577
- const block = settings.metro;
578
- const value = typeof block === "object" && block !== null ? block.idleStopMinutes : void 0;
579
- const setting = settingDefinition("metro.idleStopMinutes");
603
+ function minutesSetting(settings, block, name) {
604
+ const parent = settings[block];
605
+ const value = isPlainObject(parent) ? parent[name] : void 0;
606
+ const setting = settingDefinition(`${block}.${name}`);
580
607
  return setting && value !== void 0 && settingValueError(setting, value) === null ? value : Number(setting?.default);
581
608
  }
609
+ function metroIdleStopMinutesSetting(settings) {
610
+ return minutesSetting(settings, "metro", "idleStopMinutes");
611
+ }
612
+ function deviceIdleShutdownMinutesSetting(settings) {
613
+ return minutesSetting(settings, "devices", "idleShutdownMinutes");
614
+ }
582
615
  function tunnelModeSetting(settings) {
583
616
  const block = settings.metro;
584
617
  if (typeof block !== "object" || block === null) return null;
@@ -592,6 +625,24 @@ function metroTunnelSettingError(settings) {
592
625
  if (normalizedHttpsUrl(block.ngrokUrl) === null) return "metro.ngrokUrl must be a valid HTTPS URL.";
593
626
  return null;
594
627
  }
628
+ function metroPortSetting(projectPath, env = process.env) {
629
+ const setting = settingDefinition("metro.port");
630
+ const fromEnv = env.STIM_METRO_PORT?.trim();
631
+ const value = fromEnv ? coerceSettingText(setting, fromEnv) : settingValueAt(settingsForProject(projectPath), "metro.port");
632
+ if (value === void 0) return {
633
+ port: null,
634
+ error: null
635
+ };
636
+ const problem = settingValueError(setting, value);
637
+ if (problem === null) return {
638
+ port: value,
639
+ error: null
640
+ };
641
+ return {
642
+ port: null,
643
+ error: `Invalid ${fromEnv ? `STIM_METRO_PORT value ${JSON.stringify(env.STIM_METRO_PORT)}` : `metro.port setting ${JSON.stringify(value)}`}. Expected ${problem}.`
644
+ };
645
+ }
595
646
  function publicUrlSetting(settings) {
596
647
  const block = settings.metro;
597
648
  if (typeof block !== "object" || block === null) return null;
@@ -626,4 +677,4 @@ function webSettings(settings) {
626
677
  };
627
678
  }
628
679
  //#endregion
629
- export { resolveCacheProviderConfig as A, detectProviders as B, ngrokUrlSetting as C, readCommittedSettings as D, publicUrlSetting as E, settingsLayers as F, resolveOptimizations as G, artifactCachePolicy as H, tunnelModeSetting as I, unknownSettingKeys as L, settingFile as M, settingOrigin as N, remoteAndroidSetting as O, settingShapeErrors as P, webSettings as R, metroWarmupUrlSetting as S, projectOptimizations as T, compilerCacheFallbackMessage as U, planMetroReach as V, optimizationBuildProfile 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, PUBLIC_METRO_ENV as z };
680
+ export { remoteAndroidSetting as A, unknownSettingKeys as B, metroTunnelSettingError as C, projectOptimizations as D, projectMetroSharedCache as E, settingOrigin as F, resolveTailscaleBinary as G, PUBLIC_METRO_ENV as H, settingOriginScope as I, optimizationBuildProfile as J, artifactCachePolicy as K, settingShapeErrors as L, resolveCacheProviderConfig as M, resolveSettings as N, publicUrlSetting as O, settingFile as P, settingsLayers as R, metroPortSetting as S, ngrokUrlSetting as T, detectProviders as U, webSettings as V, planMetroReach as W, resolveOptimizations as Y, iosSigningIdentitySha1Setting as _, SETTING_SHAPE_REMEDY as a, mergeSettingsLayers as b, androidDataPartitionSizeBytes as c, cacheProviderSettingError as d, deviceIdleShutdownMinutesSetting as f, iosSigningIdentitySettingError as g, iosSigningIdentitySetting as h, SETTINGS_WITH_RESOLVE_TIME_FALLBACK as i, remoteIosSetting as j, readCommittedSettings as k, androidDataPartitionSizeGbSetting as l, iosLanHostSettingError as m, PATH_SETTINGS as n, androidAvdConfigSetting as o, iosLanHostSetting as p, compilerCacheFallbackMessage as q, REMOTE_DEVICE_BACKENDS as r, androidAvdConfigSettingError as s, ANDROID_AVD_CONFIG_HELP as t, androidDataPartitionSizeGbSettingError as u, iosSigningIdentitySha1SettingError as v, metroWarmupUrlSetting as w, metroIdleStopMinutesSetting as x, iosSimSlimProfileSetting as y, tunnelModeSetting as z };
@@ -1,11 +1,12 @@
1
- import { D as writeConfigSetting, E as withConfigLock$1, _ as loadConfig$1, f as getConfigPath$1, h as getRepoSettings, m as getProjectSettings } from "./config-0jCcMM0E.mjs";
2
- import { s as gitCommonDir, y as repoRoot } from "./worktree-DeBQSVtR.mjs";
3
- import { i as findCommandWorkspace, t as NO_PROJECT_REFUSAL } from "./project-DGeclhvO.mjs";
4
- import { n as settingDefault, r as stimDesktopInstalled } from "./stim-desktop-CXt8-Z3J.mjs";
5
- import { D as readCommittedSettings, L as unknownSettingKeys } from "./settings-xB0Frkg6.mjs";
1
+ import { D as withConfigLock$1, O as writeConfigSetting, _ as loadConfig$1, f as getConfigPath$1, h as getRepoSettings, m as getProjectSettings } from "./config-D0DbpZtM.mjs";
2
+ import { s as gitCommonDir, y as repoRoot } from "./worktree-C0c5Udwa.mjs";
3
+ import { i as findCommandWorkspace, t as NO_PROJECT_REFUSAL } from "./project-CV3tDFHl.mjs";
4
+ import { n as settingDefault, r as stimDesktopInstalled } from "./stim-desktop-Cnz8CPnl.mjs";
5
+ import { B as unknownSettingKeys, k as readCommittedSettings } from "./settings-CduVcoxj.mjs";
6
+ import { n as recordingLayers, t as deleteTurnedOffRecordings } from "./recordings-GEXp4iBT.mjs";
6
7
  import { existsSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
7
- import { SETTINGS, SETTING_SCOPES, coerceSettingText, isJsonObject, isSensitiveReference, settingDefinition, settingValueError } from "@stim-cli/core/state";
8
8
  import { join } from "node:path";
9
+ import { SETTINGS, SETTING_SCOPES, coerceSettingText, isJsonObject, isSensitiveReference, settingDefinition, settingValueError } from "@stim-cli/core/state";
9
10
  import chalk from "chalk";
10
11
  //#region src/commands/settings.ts
11
12
  const MASK = "********";
@@ -344,37 +345,42 @@ function registerSettings(program, io = CONSOLE, env = process.env) {
344
345
  if (setting.sensitive && scope === "committed" && !isSensitiveReference(value)) throw new Refusal("STIM_BAD_ARG", `${key} is sensitive, so a committed .stim.json takes only an env: or file: reference, never the secret.`, `Set the secret with --scope workspace or --scope repo, or commit a reference such as env:MY_KEYSTORE_PASSWORD.`);
345
346
  const context = readContext(env, io.note);
346
347
  const file = requireLayer(context, scope, setting);
348
+ const recording = setting.key === "recording.enabled" ? recordingLayers() : null;
347
349
  const changed = writeSetting(context, setting, scope, value);
348
350
  report(io, wantsJson(opts, command), {
349
351
  key,
350
352
  scope,
351
353
  file,
352
354
  changed
353
- }, setting, readContext(env), "set");
355
+ }, setting, readContext(env), "set", recording);
354
356
  }));
355
357
  settings.command("unset <key>").description("Remove a setting from one layer").option("--scope <scope>", `the layer to clear: ${SETTING_SCOPES.join(", ")}`).option("--json", "print the setting after removal as JSON").action((key, opts, command) => run(wantsJson(opts, command), io, () => {
356
358
  const setting = requireSetting(key);
357
359
  const scope = readScope(opts.scope, setting);
358
360
  const context = readContext(env, io.note);
359
361
  const file = requireLayer(context, scope, setting);
362
+ const recording = setting.key === "recording.enabled" ? recordingLayers() : null;
360
363
  const changed = writeSetting(context, setting, scope, void 0);
361
364
  report(io, wantsJson(opts, command), {
362
365
  key,
363
366
  scope,
364
367
  file,
365
368
  changed
366
- }, setting, readContext(env), "unset");
369
+ }, setting, readContext(env), "unset", recording);
367
370
  }));
368
371
  }
369
- function report(io, json, write, setting, context, verb) {
372
+ function report(io, json, write, setting, context, verb, recordingBefore) {
370
373
  const entry = settingEntry(context, setting);
374
+ const recordingsDeleted = recordingBefore ? deleteTurnedOffRecordings(recordingBefore) : null;
371
375
  if (json) {
372
376
  io.out(JSON.stringify({
373
377
  ...write,
374
- setting: entry
378
+ setting: entry,
379
+ ...recordingsDeleted ? { recordingsDeleted } : {}
375
380
  }));
376
381
  return;
377
382
  }
383
+ for (const workspace of recordingsDeleted ?? []) io.note(`Deleted the device recordings of ${workspace}.`);
378
384
  const effective = entry.origin === null ? "unset" : `${formatValue(entry.value)} (${formatOrigin(entry)})`;
379
385
  if (!write.changed) {
380
386
  io.out(`${write.key} was not set in ${write.file}; effective value: ${effective}`);