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,5 +1,7 @@
1
- import { n as stripAnsi } from "./process-output-Dl5uKE03.mjs";
1
+ import { t as NO_PROJECT_REFUSAL } from "./project-BXZzOABt.mjs";
2
+ import { n as stripAnsi } from "./process-output-Fpl8PYi8.mjs";
2
3
  import { quotedPath } from "@stim-cli/core";
4
+ import chalk from "chalk";
3
5
  //#region src/diagnostics/launch-error-preview.ts
4
6
  function framePriority(frame, root) {
5
7
  const normalized = frame.replaceAll("\\", "/");
@@ -245,6 +247,13 @@ function releasedLeaseFact(lease) {
245
247
  function phaseLine(label, text) {
246
248
  return ` ${String(label).padEnd(LABEL_WIDTH)} ${text}`;
247
249
  }
250
+ function refuseNoProject({ json }) {
251
+ const { code, message, remedy } = NO_PROJECT_REFUSAL;
252
+ console.error(chalk.red(phaseLine("error", `${code}: ${message}`)));
253
+ console.error(phaseLine("remedy", remedy));
254
+ if (json) console.log(JSON.stringify(NO_PROJECT_REFUSAL));
255
+ process.exitCode = 1;
256
+ }
248
257
  function shortUdid(udid) {
249
258
  const text = String(udid ?? "");
250
259
  return text.length > 4 ? `${text.slice(0, 4)}..` : text;
@@ -283,4 +292,4 @@ function stepTimer(now = Date.now) {
283
292
  return () => `(${formatDuration(elapsed())})`;
284
293
  }
285
294
  //#endregion
286
- export { launchErrorPreview as _, formatElapsed as a, launchErrorReport as c, quotedPath as d, releasedLeaseFact as f, stepTimer as g, stepClock as h, formatDuration as i, phaseLine as l, shortUdid as m, appReadinessMessage as n, formatLongDuration as o, shortHash as p, clockTime as r, isAppLaunchError as s, SLOW_STEP_MS as t, plural as u };
295
+ export { stepTimer as _, formatElapsed as a, launchErrorReport as c, quotedPath as d, refuseNoProject as f, stepClock as g, shortUdid as h, formatDuration as i, phaseLine as l, shortHash as m, appReadinessMessage as n, formatLongDuration as o, releasedLeaseFact as p, clockTime as r, isAppLaunchError as s, SLOW_STEP_MS as t, plural as u, launchErrorPreview as v };
@@ -1,24 +1,33 @@
1
1
  import { n as __exportAll, r as __reExport } from "./rolldown-runtime-BsOwBuO_.mjs";
2
- import { t as getExecutor } from "./exec-B29XNuhP.mjs";
3
- import { join } from "node:path";
4
- import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
5
- import { homedir } from "os";
6
- import { dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1, normalize, parse as parse$1, sep as sep$1 } from "path";
7
- import { configDir, withDirLock } from "@stim-cli/core";
2
+ import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
+ import { InvalidArgumentError } from "commander";
4
+ import { existsSync, lstatSync, mkdirSync, readdirSync, readlinkSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
5
+ import { dirname, isAbsolute, join, normalize, parse, sep } from "path";
6
+ import { configDir } from "@stim-cli/core";
7
+ import { allMetroPorts, configCorruptRepair, findEnclosingWorktreeRoot, getConcurrencyLimits, getConfigPath, getConfigPath as getConfigPath$1, getProject, getProjectSettings, getRepoSettings, isPathPrefix, loadConfig, loadConfig as loadConfig$1, withConfigLock, withConfigLock as withConfigLock$1 } from "@stim-cli/core/state";
8
+ import { join as join$1 } from "node:path";
8
9
  //#region src/devices/device-slots.ts
9
10
  const DEFAULT_DEVICE_SLOT = "default";
10
- function validateDeviceSlot(slot = "default") {
11
- if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(slot) || [
11
+ const DEVICE_SLOT_RULE = "A device slot must be 1-64 letters, digits, underscores or hyphens, starting with a letter or digit.";
12
+ function isDeviceSlot(slot) {
13
+ return /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(slot) && ![
12
14
  "constructor",
13
15
  "prototype",
14
16
  "__proto__"
15
- ].includes(slot)) {
16
- const error = /* @__PURE__ */ new Error("A device slot must be 1-64 letters, digits, underscores or hyphens, starting with a letter or digit.");
17
+ ].includes(slot);
18
+ }
19
+ function validateDeviceSlot(slot = "default") {
20
+ if (!isDeviceSlot(slot)) {
21
+ const error = /* @__PURE__ */ new Error(DEVICE_SLOT_RULE);
17
22
  Object.assign(error, { code: "STIM_BAD_ARG" });
18
23
  throw error;
19
24
  }
20
25
  return slot;
21
26
  }
27
+ function parseDeviceSlotOption(slot) {
28
+ if (!isDeviceSlot(slot)) throw new InvalidArgumentError(DEVICE_SLOT_RULE);
29
+ return slot;
30
+ }
22
31
  function deviceSlotPlatforms(project, slot = DEFAULT_DEVICE_SLOT) {
23
32
  validateDeviceSlot(slot);
24
33
  if (slot === DEFAULT_DEVICE_SLOT) return project?.platforms;
@@ -98,23 +107,23 @@ const WINDOWS = process.platform === "win32";
98
107
  const DRIVE_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
99
108
  function volumeRootFor(path) {
100
109
  const normalized = normalize(String(path));
101
- if (WINDOWS) return parse$1(normalized).root.replace(/^[a-z]:/, (drive) => drive.toUpperCase());
110
+ if (WINDOWS) return parse(normalized).root.replace(/^[a-z]:/, (drive) => drive.toUpperCase());
102
111
  const m = normalized.match(/^\/volumes\/([^/]+)/i);
103
112
  return m ? `/Volumes/${m[1]}` : "/";
104
113
  }
105
114
  function resolveWorkspaceRealish(workspacePath) {
106
115
  const raw = String(workspacePath);
107
- if (!isAbsolute$1(raw)) return null;
116
+ if (!isAbsolute(raw)) return null;
108
117
  let current = raw;
109
118
  const MAX_HOPS = 40;
110
119
  for (let hops = 0; hops < MAX_HOPS; hops++) {
111
120
  const normalized = normalize(current);
112
- const root = parse$1(normalized).root;
113
- const segments = normalized.slice(root.length).split(sep$1).filter(Boolean);
114
- let prefix = root.endsWith(sep$1) ? root.slice(0, -sep$1.length) : root;
121
+ const root = parse(normalized).root;
122
+ const segments = normalized.slice(root.length).split(sep).filter(Boolean);
123
+ let prefix = root.endsWith(sep) ? root.slice(0, -sep.length) : root;
115
124
  let followedSymlink = false;
116
125
  for (const segment of segments) {
117
- prefix += `${sep$1}${segment}`;
126
+ prefix += `${sep}${segment}`;
118
127
  let st;
119
128
  try {
120
129
  st = lstatSync(prefix);
@@ -129,7 +138,7 @@ function resolveWorkspaceRealish(workspacePath) {
129
138
  } catch {
130
139
  return null;
131
140
  }
132
- current = `${isAbsolute$1(target) ? target : normalize(join$1(dirname$1(prefix), target))}${normalized.slice(prefix.length)}`;
141
+ current = `${isAbsolute(target) ? target : normalize(join(dirname(prefix), target))}${normalized.slice(prefix.length)}`;
133
142
  followedSymlink = true;
134
143
  break;
135
144
  }
@@ -144,7 +153,7 @@ function isRealMount(entryDev, rootDev) {
144
153
  function listWindowsVolumeRoots(statFn) {
145
154
  const roots = [];
146
155
  for (const letter of DRIVE_LETTERS) {
147
- const root = `${letter}:${sep$1}`;
156
+ const root = `${letter}:${sep}`;
148
157
  try {
149
158
  statFn(root);
150
159
  } catch {
@@ -165,7 +174,7 @@ function listMountedVolumes({ statFn = statSync } = {}) {
165
174
  }
166
175
  try {
167
176
  for (const name of readdirSync("/Volumes")) {
168
- const path = join$1("/Volumes", name);
177
+ const path = join("/Volumes", name);
169
178
  let entryDev;
170
179
  try {
171
180
  entryDev = statFn(path).dev;
@@ -181,26 +190,29 @@ function resolveVolumeRoot(path) {
181
190
  const realish = resolveWorkspaceRealish(path);
182
191
  return realish === null ? null : volumeRootFor(realish);
183
192
  }
184
- function uncShareIsReachable(volume) {
185
- if (!WINDOWS || !volume.startsWith(`${sep$1}${sep$1}`)) return false;
193
+ function uncShareIsReachable(volume, statFn) {
194
+ if (!WINDOWS || !volume.startsWith(`${sep}${sep}`)) return false;
186
195
  try {
187
- return statSync(volume).isDirectory();
196
+ return statFn(volume).isDirectory();
188
197
  } catch {
189
198
  return false;
190
199
  }
191
200
  }
192
- function isOnMountedVolume(path, mountedVolumes) {
201
+ function isOnMountedVolume(path, mountedVolumes, { statFn = statSync } = {}) {
193
202
  const mounted = new Set(mountedVolumes || listMountedVolumes());
194
203
  const volume = resolveVolumeRoot(path);
195
204
  if (volume === null) return false;
196
- return mounted.has(volume) || uncShareIsReachable(volume);
205
+ return mounted.has(volume) || uncShareIsReachable(volume, statFn);
197
206
  }
198
207
  function directorySize(dir, { timeoutMs } = {}) {
208
+ return measuredDirectorySize(dir, { timeoutMs }) ?? 0;
209
+ }
210
+ function measuredDirectorySize(dir, { timeoutMs } = {}) {
199
211
  let out;
200
212
  try {
201
213
  out = getExecutor().runFile("du", ["-sk", dir], { timeoutMs });
202
214
  } catch {
203
- return 0;
215
+ return null;
204
216
  }
205
217
  if (!out) return 0;
206
218
  const kb = parseInt(out.split(/\s+/)[0] ?? "", 10);
@@ -219,7 +231,7 @@ __reExport(ownership_claim_exports, import__stim_cli_core_ownership_claim);
219
231
  //#endregion
220
232
  //#region src/devices/avd-claim.ts
221
233
  function acquireAvdClaim(avdName) {
222
- const root = join(configDir(), "avd-locks", `${encodeURIComponent(avdName.toLowerCase())}.lock`);
234
+ const root = join$1(configDir(), "avd-locks", `${encodeURIComponent(avdName.toLowerCase())}.lock`);
223
235
  const label = `AVD ${avdName}`;
224
236
  const attempt = (0, ownership_claim_exports.tryAcquireClaim)({
225
237
  root,
@@ -243,57 +255,25 @@ __reExport(process_identity_exports, import__stim_cli_core_process_identity);
243
255
  //#endregion
244
256
  //#region src/workspace/config.ts
245
257
  function getConfigDir() {
246
- return process.env.STIM_HOME || join$1(homedir(), ".stim");
247
- }
248
- function getConfigPath() {
249
- return join$1(getConfigDir(), "config.json");
258
+ return configDir();
259
+ }
260
+ function refuseRelativeStimPaths(env = process.env) {
261
+ for (const name of [
262
+ "STIM_HOME",
263
+ "STIM_BUILD_CACHE",
264
+ "STIM_METRO_CACHE"
265
+ ]) {
266
+ const value = env[name];
267
+ if (!value || isAbsolute(value)) continue;
268
+ const error = /* @__PURE__ */ new Error(`${name}=${value} is not an absolute path. Set it to an absolute path, or unset it to use the default.`);
269
+ error.code = "STIM_RELATIVE_PATH";
270
+ throw error;
271
+ }
250
272
  }
251
273
  function ensureDir() {
252
274
  const dir = getConfigDir();
253
275
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
254
276
  }
255
- const LOCK_DIR_NAME = "config.lock";
256
- function lockPath() {
257
- return join$1(getConfigDir(), LOCK_DIR_NAME);
258
- }
259
- function withConfigLock(fn) {
260
- return withDirLock(lockPath(), fn, { ensureParent: ensureDir });
261
- }
262
- function configCorruptRepair(path = getConfigPath()) {
263
- return `Repair the file, or move it aside to start over: mv "${path}" "${path}.broken"`;
264
- }
265
- function configCorrupt(reason, path = getConfigPath()) {
266
- const corrupt = /* @__PURE__ */ new Error(`Stim config at ${path} ${reason}\nIt holds the records of the simulators and emulators Stim owns, so it is never reset automatically.
267
- ` + configCorruptRepair(path));
268
- corrupt.code = "STIM_CONFIG_CORRUPT";
269
- return corrupt;
270
- }
271
- function loadConfig() {
272
- const p = getConfigPath();
273
- if (!existsSync(p)) return null;
274
- const raw = readFileSync(p, "utf-8");
275
- try {
276
- return JSON.parse(raw);
277
- } catch (err) {
278
- throw configCorrupt(`is not valid JSON: ${err.message}`, p);
279
- }
280
- }
281
- function isRecord(value) {
282
- return typeof value === "object" && value !== null && !Array.isArray(value);
283
- }
284
- /**
285
- * An array or scalar container swallows every write silently: `JSON.stringify` drops a string key set on
286
- * an array, so the record is gone by the time the file is written. Absent is created; unusable is refused.
287
- */
288
- function adoptContainer(cfg, key) {
289
- const value = cfg[key];
290
- if (value === void 0 || value === null) {
291
- cfg[key] = {};
292
- return true;
293
- }
294
- if (!isRecord(value)) throw configCorrupt(`has a ${key} that is not an object: ${JSON.stringify(value)}`);
295
- return false;
296
- }
297
277
  function saveConfig(config) {
298
278
  ensureDir();
299
279
  const target = getConfigPath();
@@ -313,14 +293,10 @@ function ensureConfig() {
313
293
  return withConfigLock(() => {
314
294
  const existing = loadConfig();
315
295
  if (existing) {
316
- let changed = false;
317
- if (adoptContainer(existing, "projects")) changed = true;
318
- if (adoptContainer(existing, "repos")) changed = true;
319
296
  if (existing.version !== CONFIG_VERSION) {
320
297
  existing.version = CONFIG_VERSION;
321
- changed = true;
298
+ saveConfig(existing);
322
299
  }
323
- if (changed) saveConfig(existing);
324
300
  return existing;
325
301
  }
326
302
  const fresh = {
@@ -332,11 +308,8 @@ function ensureConfig() {
332
308
  return fresh;
333
309
  });
334
310
  }
335
- function getProject(projectPath) {
336
- return loadConfig()?.projects?.[projectPath] || null;
337
- }
338
311
  function requireAbsoluteProjectPath(projectPath) {
339
- if (isAbsolute$1(projectPath)) return;
312
+ if (isAbsolute(projectPath)) return;
340
313
  throw new Error(`Project key "${projectPath}" is not an absolute path. The registry is keyed by the realpath of a workspace root, so a relative key can never resolve.`);
341
314
  }
342
315
  function upsertProject(projectPath, fields) {
@@ -378,6 +351,15 @@ function claimMetroPort(projectPath, port) {
378
351
  return port;
379
352
  });
380
353
  }
354
+ function releaseMetroPort(projectPath, port) {
355
+ withConfigLock(() => {
356
+ const cfg = loadConfig();
357
+ const project = cfg?.projects?.[projectPath];
358
+ if (!cfg || project?.metroPort !== port) return;
359
+ project.metroPort = null;
360
+ saveConfig(cfg);
361
+ });
362
+ }
381
363
  function setDevice(projectPath, platform, deviceFields, slot = "default") {
382
364
  requireAbsoluteProjectPath(projectPath);
383
365
  withConfigLock(() => {
@@ -438,48 +420,79 @@ function clearSupervisor(projectPath, expected) {
438
420
  return true;
439
421
  });
440
422
  }
441
- function getConcurrencyLimits({ env = process.env } = {}) {
442
- const c = loadConfig()?.concurrency || {};
443
- return {
444
- maxBuilds: resolveLimit(env.STIM_MAX_BUILDS, c.maxBuilds),
445
- maxDevices: resolveLimit(env.STIM_MAX_DEVICES, c.maxDevices)
446
- };
447
- }
448
- function resolveLimit(envVal, cfgVal) {
449
- const raw = envVal !== void 0 && envVal !== null && envVal !== "" ? Number(envVal) : typeof cfgVal === "number" ? cfgVal : NaN;
450
- if (!Number.isFinite(raw) || raw <= 0) return 0;
451
- return Math.floor(raw);
452
- }
453
- function getProjectSettings(projectPath) {
454
- return getProject(projectPath)?.settings || {};
455
- }
456
- function getRepoSettings(gitCommonDir) {
457
- return loadConfig()?.repos?.[gitCommonDir]?.settings || {};
458
- }
459
- function isPathPrefix(prefix, path) {
460
- const head = comparablePath(prefix);
461
- const candidate = comparablePath(path);
462
- if (head === candidate) return true;
463
- return candidate.startsWith(head.endsWith("/") ? head : `${head}/`);
423
+ function targetSettings(cfg, target, create) {
424
+ if (target.scope === "machine") return cfg;
425
+ if (target.scope === "workspace") {
426
+ if (!cfg.projects[target.projectPath]) {
427
+ if (!create) return null;
428
+ cfg.projects[target.projectPath] = {
429
+ metroPort: null,
430
+ platforms: {}
431
+ };
432
+ }
433
+ const project = cfg.projects[target.projectPath];
434
+ if (!project.settings && create) project.settings = {};
435
+ return project.settings ?? null;
436
+ }
437
+ if (!cfg.repos[target.gitCommonDir]) {
438
+ if (!create) return null;
439
+ cfg.repos[target.gitCommonDir] = {};
440
+ }
441
+ const repo = cfg.repos[target.gitCommonDir];
442
+ if (!repo.settings && create) repo.settings = {};
443
+ return repo.settings ?? null;
464
444
  }
465
- function comparablePath(path) {
466
- return sep$1 === "/" ? path : path.replaceAll(sep$1, "/");
445
+ function writeConfigSetting(target, dottedKey, value) {
446
+ if (target.scope === "workspace") requireAbsoluteProjectPath(target.projectPath);
447
+ return withConfigLock(() => {
448
+ const cfg = value === void 0 ? loadConfig() : ensureConfig();
449
+ const settings = cfg ? targetSettings(cfg, target, value !== void 0) : null;
450
+ if (!cfg || !settings) return false;
451
+ if (value === void 0) {
452
+ if (!deleteNested(settings, dottedKey)) return false;
453
+ } else writeNested(settings, dottedKey, value);
454
+ saveConfig(cfg);
455
+ return true;
456
+ });
467
457
  }
468
- function findEnclosingWorktreeRoot(projectPath) {
469
- const cfg = loadConfig();
470
- if (!cfg?.projects) return null;
471
- let best = null;
472
- for (const [path, proj] of Object.entries(cfg.projects)) {
473
- if (!proj?.worktreeRoot) continue;
474
- if (!isPathPrefix(path, projectPath)) continue;
475
- if (!best || path.length > best.length) best = path;
458
+ function writeNested(obj, dottedKey, value) {
459
+ const keys = dottedKey.split(".");
460
+ let cur = obj;
461
+ for (let i = 0; i < keys.length - 1; i++) {
462
+ const k = keys[i];
463
+ if (k === void 0) continue;
464
+ const next = cur[k];
465
+ if (typeof next !== "object" || next === null) cur[k] = {};
466
+ cur = cur[k];
476
467
  }
477
- return best;
478
- }
479
- function allMetroPorts() {
480
- const cfg = loadConfig();
481
- if (!cfg?.projects) return [];
482
- return Object.values(cfg.projects).map((p) => p.metroPort).filter((p) => typeof p === "number");
468
+ const leaf = keys[keys.length - 1];
469
+ if (leaf !== void 0) cur[leaf] = value;
470
+ }
471
+ function deleteNested(obj, dottedKey) {
472
+ const keys = dottedKey.split(".");
473
+ const chain = [obj];
474
+ let cur = obj;
475
+ for (let i = 0; i < keys.length - 1; i++) {
476
+ const k = keys[i];
477
+ if (k === void 0) return false;
478
+ const next = cur[k];
479
+ if (next == null || typeof next !== "object") return false;
480
+ cur = next;
481
+ chain.push(cur);
482
+ }
483
+ const leaf = keys[keys.length - 1];
484
+ if (leaf === void 0) return false;
485
+ if (!(leaf in cur)) return false;
486
+ delete cur[leaf];
487
+ for (let i = chain.length - 2; i >= 0; i--) {
488
+ const parent = chain[i];
489
+ const key = keys[i];
490
+ const child = chain[i + 1];
491
+ if (parent === void 0 || key === void 0 || child === void 0) break;
492
+ if (Object.keys(child).length === 0) delete parent[key];
493
+ else break;
494
+ }
495
+ return true;
483
496
  }
484
497
  function allConsolePortsAndSerials({ isMounted = isOnMountedVolume } = {}) {
485
498
  const cfg = loadConfig();
@@ -499,4 +512,4 @@ function allConsolePortsAndSerials({ isMounted = isOnMountedVolume } = {}) {
499
512
  return result;
500
513
  }
501
514
  //#endregion
502
- export { isOnMountedVolume as A, validateDeviceSlot as B, withConfigLock as C, withDirLock as D, ownership_claim_exports as E, deviceSlotKey as F, deviceSlotPlatforms as I, parseDeviceSlotKey as L, volumeRootFor as M, assignSlotDevice as N, directorySize as O, deviceSlotFileKey as P, projectDeviceSlots as R, upsertProject as S, acquireAvdClaim as T, releaseAndroidConsolePort as _, clearSupervisor as a, setDevice as b, findEnclosingWorktreeRoot as c, getConfigPath as d, getProject as f, loadConfig as g, isPathPrefix as h, clearDevice as i, listMountedVolumes as j, formatBytes as k, getConcurrencyLimits as l, getRepoSettings as m, allMetroPorts as n, configCorruptRepair as o, getProjectSettings as p, claimMetroPort as r, ensureConfig as s, allConsolePortsAndSerials as t, getConfigDir as u, removeProject as v, process_identity_exports as w, setSupervisor as x, saveConfig as y, removeSlotDevice as z };
515
+ export { directorySize as A, parseDeviceSlotKey as B, setSupervisor as C, process_identity_exports as D, writeConfigSetting as E, volumeRootFor as F, projectDeviceSlots as H, assignSlotDevice as I, deviceSlotFileKey as L, isOnMountedVolume as M, listMountedVolumes as N, acquireAvdClaim as O, measuredDirectorySize as P, deviceSlotKey as R, setDevice as S, withConfigLock$1 as T, removeSlotDevice as U, parseDeviceSlotOption as V, validateDeviceSlot as W, refuseRelativeStimPaths as _, clearSupervisor as a, removeProject as b, findEnclosingWorktreeRoot as c, getConfigPath$1 as d, getProject as f, loadConfig$1 as g, isPathPrefix as h, clearDevice as i, formatBytes as j, ownership_claim_exports as k, getConcurrencyLimits as l, getRepoSettings as m, allMetroPorts as n, configCorruptRepair as o, getProjectSettings as p, claimMetroPort as r, ensureConfig as s, allConsolePortsAndSerials as t, getConfigDir as u, releaseAndroidConsolePort as v, upsertProject as w, saveConfig as x, releaseMetroPort as y, deviceSlotPlatforms as z };
@@ -1,5 +1,5 @@
1
- import { t as getExecutor } from "./exec-B29XNuhP.mjs";
2
- import { g as repoRoot } from "./worktree-FG_ZKKdb.mjs";
1
+ import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
2
+ import { y as repoRoot } from "./worktree-BlizKDpZ.mjs";
3
3
  import { existsSync } from "node:fs";
4
4
  import { dirname, join, relative } from "node:path";
5
5
  //#region src/dependency-state.ts
@@ -1,11 +1,13 @@
1
- import { t as getExecutor } from "./exec-B29XNuhP.mjs";
2
- import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-Dl5uKE03.mjs";
3
- import { d as startBuildHeartbeat, m as bundlerPin, n as HEARTBEAT_INTERVAL_MS } from "./xcode-B6YaWeg0.mjs";
1
+ import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
2
+ import { n as spawnDeclared } from "./spawn-claims-CUleC2cM.mjs";
3
+ import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
4
+ import { d as startBuildHeartbeat, m as bundlerPin, n as HEARTBEAT_INTERVAL_MS } from "./xcode-B2tBkf0m.mjs";
4
5
  import { existsSync, readFileSync } from "node:fs";
5
6
  import { delimiter, dirname, join, relative, resolve, sep } from "node:path";
6
7
  import { homedir } from "node:os";
7
8
  //#region src/engine/deps.ts
8
9
  const DEPS_ERROR = "STIM_DEPS_FAILED";
10
+ const TRANSCRIPT_LINES = 2e3;
9
11
  const MAX_POD_DIAGNOSTIC_LINES = 15;
10
12
  const POD_MARKER = /^\[!\]/;
11
13
  const POD_CONTINUATION = /^\s+\S/;
@@ -182,6 +184,7 @@ async function runCaptured(ctx) {
182
184
  const msg = stripAnsi(String(line)).trimEnd();
183
185
  if (!msg.trim()) return;
184
186
  transcript.push(msg);
187
+ if (transcript.length > TRANSCRIPT_LINES) transcript.splice(TRANSCRIPT_LINES / 2, 1);
185
188
  ctx.logWriter?.write?.({
186
189
  src: "build",
187
190
  level: "debug",
@@ -192,7 +195,7 @@ async function runCaptured(ctx) {
192
195
  };
193
196
  let child;
194
197
  try {
195
- child = ctx.spawn(ctx.cmd, ctx.args, {
198
+ child = spawnDeclared(() => ctx.spawn(ctx.cmd, ctx.args, {
196
199
  cwd: ctx.cwd,
197
200
  stdio: [
198
201
  "ignore",
@@ -200,7 +203,7 @@ async function runCaptured(ctx) {
200
203
  "pipe"
201
204
  ],
202
205
  env: ctx.env
203
- });
206
+ }));
204
207
  } catch (error) {
205
208
  return {
206
209
  transcript,
@@ -1,4 +1,4 @@
1
- import { t as getExecutor } from "./exec-B29XNuhP.mjs";
1
+ import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
2
2
  import { openSync } from "node:fs";
3
3
  //#region src/detached-entry.ts
4
4
  const LOG_FILE_FLAG = "--log-file";