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,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";
2
+ import { t as getExecutor } from "./exec-jIAlgilk.mjs";
3
+ import { readFileSync } from "node:fs";
4
+ import { InvalidArgumentError } from "commander";
5
+ import { existsSync as existsSync$1, lstatSync as lstatSync$1, mkdirSync as mkdirSync$1, readdirSync as readdirSync$1, readlinkSync, renameSync as renameSync$1, statSync as statSync$1, unlinkSync as unlinkSync$1, writeFileSync as writeFileSync$1 } from "fs";
6
+ import { dirname, isAbsolute, join, normalize, parse, sep } from "path";
7
7
  import { configDir, withDirLock } from "@stim-cli/core";
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,26 +107,26 @@ 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
- st = lstatSync(prefix);
129
+ st = lstatSync$1(prefix);
121
130
  } catch (err) {
122
131
  if (err && err.code === "ENOENT") return normalized;
123
132
  return null;
@@ -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 {
@@ -154,7 +163,7 @@ function listWindowsVolumeRoots(statFn) {
154
163
  }
155
164
  return roots;
156
165
  }
157
- function listMountedVolumes({ statFn = statSync } = {}) {
166
+ function listMountedVolumes({ statFn = statSync$1 } = {}) {
158
167
  if (WINDOWS) return listWindowsVolumeRoots(statFn);
159
168
  const roots = ["/"];
160
169
  let rootDev;
@@ -164,8 +173,8 @@ function listMountedVolumes({ statFn = statSync } = {}) {
164
173
  return roots;
165
174
  }
166
175
  try {
167
- for (const name of readdirSync("/Volumes")) {
168
- const path = join$1("/Volumes", name);
176
+ for (const name of readdirSync$1("/Volumes")) {
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$1 } = {}) {
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);
@@ -212,6 +224,22 @@ function formatBytes(bytes) {
212
224
  return `${Math.round(bytes / 1024)}K`;
213
225
  }
214
226
  //#endregion
227
+ //#region src/json-file.ts
228
+ function readJsonFile(path) {
229
+ return JSON.parse(readFileSync(path, "utf-8"));
230
+ }
231
+ function isJsonObject(value) {
232
+ return typeof value === "object" && value !== null && !Array.isArray(value);
233
+ }
234
+ function readJsonObject(path) {
235
+ try {
236
+ const value = readJsonFile(path);
237
+ return isJsonObject(value) ? value : null;
238
+ } catch {
239
+ return null;
240
+ }
241
+ }
242
+ //#endregion
215
243
  //#region src/ownership-claim.ts
216
244
  var ownership_claim_exports = /* @__PURE__ */ __exportAll({});
217
245
  import * as import__stim_cli_core_ownership_claim from "@stim-cli/core/ownership-claim";
@@ -219,7 +247,7 @@ __reExport(ownership_claim_exports, import__stim_cli_core_ownership_claim);
219
247
  //#endregion
220
248
  //#region src/devices/avd-claim.ts
221
249
  function acquireAvdClaim(avdName) {
222
- const root = join(configDir(), "avd-locks", `${encodeURIComponent(avdName.toLowerCase())}.lock`);
250
+ const root = join$1(configDir(), "avd-locks", `${encodeURIComponent(avdName.toLowerCase())}.lock`);
223
251
  const label = `AVD ${avdName}`;
224
252
  const attempt = (0, ownership_claim_exports.tryAcquireClaim)({
225
253
  root,
@@ -243,18 +271,31 @@ __reExport(process_identity_exports, import__stim_cli_core_process_identity);
243
271
  //#endregion
244
272
  //#region src/workspace/config.ts
245
273
  function getConfigDir() {
246
- return process.env.STIM_HOME || join$1(homedir(), ".stim");
274
+ return configDir();
275
+ }
276
+ function refuseRelativeStimPaths(env = process.env) {
277
+ for (const name of [
278
+ "STIM_HOME",
279
+ "STIM_BUILD_CACHE",
280
+ "STIM_METRO_CACHE"
281
+ ]) {
282
+ const value = env[name];
283
+ if (!value || isAbsolute(value)) continue;
284
+ 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.`);
285
+ error.code = "STIM_RELATIVE_PATH";
286
+ throw error;
287
+ }
247
288
  }
248
289
  function getConfigPath() {
249
- return join$1(getConfigDir(), "config.json");
290
+ return join(getConfigDir(), "config.json");
250
291
  }
251
292
  function ensureDir() {
252
293
  const dir = getConfigDir();
253
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
294
+ if (!existsSync$1(dir)) mkdirSync$1(dir, { recursive: true });
254
295
  }
255
296
  const LOCK_DIR_NAME = "config.lock";
256
297
  function lockPath() {
257
- return join$1(getConfigDir(), LOCK_DIR_NAME);
298
+ return join(getConfigDir(), LOCK_DIR_NAME);
258
299
  }
259
300
  function withConfigLock(fn) {
260
301
  return withDirLock(lockPath(), fn, { ensureParent: ensureDir });
@@ -270,40 +311,50 @@ function configCorrupt(reason, path = getConfigPath()) {
270
311
  }
271
312
  function loadConfig() {
272
313
  const p = getConfigPath();
273
- if (!existsSync(p)) return null;
274
- const raw = readFileSync(p, "utf-8");
314
+ if (!existsSync$1(p)) return null;
315
+ let parsed;
275
316
  try {
276
- return JSON.parse(raw);
317
+ parsed = readJsonFile(p);
277
318
  } catch (err) {
319
+ if (!(err instanceof SyntaxError)) throw err;
278
320
  throw configCorrupt(`is not valid JSON: ${err.message}`, p);
279
321
  }
322
+ return configFromJson(parsed, p);
280
323
  }
281
- function isRecord(value) {
282
- return typeof value === "object" && value !== null && !Array.isArray(value);
324
+ function configFromJson(value, path) {
325
+ if (!isJsonObject(value)) throw configCorrupt(`is not a JSON object: ${JSON.stringify(value)}`, path);
326
+ return {
327
+ ...value,
328
+ projects: registryFromJson(value, "projects", path),
329
+ repos: registryFromJson(value, "repos", path)
330
+ };
283
331
  }
284
332
  /**
285
333
  * 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.
334
+ * an array, so the record is gone by the time the file is written. Absent or null is read as empty; an
335
+ * unusable container or entry is refused.
287
336
  */
288
- function adoptContainer(cfg, key) {
289
- const value = cfg[key];
290
- if (value === void 0 || value === null) {
291
- cfg[key] = {};
292
- return true;
337
+ function registryFromJson(config, key, path) {
338
+ const value = config[key];
339
+ if (value === void 0 || value === null) return {};
340
+ if (!isJsonObject(value)) throw configCorrupt(`has a ${key} that is not an object: ${JSON.stringify(value)}`, path);
341
+ const registry = {};
342
+ for (const [name, entry] of Object.entries(value)) {
343
+ if (!isJsonObject(entry)) throw configCorrupt(`has a ${key} entry ${JSON.stringify(name)} that is not an object: ${JSON.stringify(entry)}`, path);
344
+ registry[name] = entry;
293
345
  }
294
- if (!isRecord(value)) throw configCorrupt(`has a ${key} that is not an object: ${JSON.stringify(value)}`);
295
- return false;
346
+ return registry;
296
347
  }
297
348
  function saveConfig(config) {
298
349
  ensureDir();
299
350
  const target = getConfigPath();
300
351
  const tmp = `${target}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
301
- writeFileSync(tmp, JSON.stringify(config, null, 2) + "\n");
352
+ writeFileSync$1(tmp, JSON.stringify(config, null, 2) + "\n");
302
353
  try {
303
- renameSync(tmp, target);
354
+ renameSync$1(tmp, target);
304
355
  } catch (err) {
305
356
  try {
306
- unlinkSync(tmp);
357
+ unlinkSync$1(tmp);
307
358
  } catch {}
308
359
  throw err;
309
360
  }
@@ -313,14 +364,10 @@ function ensureConfig() {
313
364
  return withConfigLock(() => {
314
365
  const existing = loadConfig();
315
366
  if (existing) {
316
- let changed = false;
317
- if (adoptContainer(existing, "projects")) changed = true;
318
- if (adoptContainer(existing, "repos")) changed = true;
319
367
  if (existing.version !== CONFIG_VERSION) {
320
368
  existing.version = CONFIG_VERSION;
321
- changed = true;
369
+ saveConfig(existing);
322
370
  }
323
- if (changed) saveConfig(existing);
324
371
  return existing;
325
372
  }
326
373
  const fresh = {
@@ -336,7 +383,7 @@ function getProject(projectPath) {
336
383
  return loadConfig()?.projects?.[projectPath] || null;
337
384
  }
338
385
  function requireAbsoluteProjectPath(projectPath) {
339
- if (isAbsolute$1(projectPath)) return;
386
+ if (isAbsolute(projectPath)) return;
340
387
  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
388
  }
342
389
  function upsertProject(projectPath, fields) {
@@ -378,6 +425,15 @@ function claimMetroPort(projectPath, port) {
378
425
  return port;
379
426
  });
380
427
  }
428
+ function releaseMetroPort(projectPath, port) {
429
+ withConfigLock(() => {
430
+ const cfg = loadConfig();
431
+ const project = cfg?.projects?.[projectPath];
432
+ if (!cfg || project?.metroPort !== port) return;
433
+ project.metroPort = null;
434
+ saveConfig(cfg);
435
+ });
436
+ }
381
437
  function setDevice(projectPath, platform, deviceFields, slot = "default") {
382
438
  requireAbsoluteProjectPath(projectPath);
383
439
  withConfigLock(() => {
@@ -456,6 +512,80 @@ function getProjectSettings(projectPath) {
456
512
  function getRepoSettings(gitCommonDir) {
457
513
  return loadConfig()?.repos?.[gitCommonDir]?.settings || {};
458
514
  }
515
+ function targetSettings(cfg, target, create) {
516
+ if (target.scope === "machine") return cfg;
517
+ if (target.scope === "workspace") {
518
+ if (!cfg.projects[target.projectPath]) {
519
+ if (!create) return null;
520
+ cfg.projects[target.projectPath] = {
521
+ metroPort: null,
522
+ platforms: {}
523
+ };
524
+ }
525
+ const project = cfg.projects[target.projectPath];
526
+ if (!project.settings && create) project.settings = {};
527
+ return project.settings ?? null;
528
+ }
529
+ if (!cfg.repos[target.gitCommonDir]) {
530
+ if (!create) return null;
531
+ cfg.repos[target.gitCommonDir] = {};
532
+ }
533
+ const repo = cfg.repos[target.gitCommonDir];
534
+ if (!repo.settings && create) repo.settings = {};
535
+ return repo.settings ?? null;
536
+ }
537
+ function writeConfigSetting(target, dottedKey, value) {
538
+ if (target.scope === "workspace") requireAbsoluteProjectPath(target.projectPath);
539
+ return withConfigLock(() => {
540
+ const cfg = value === void 0 ? loadConfig() : ensureConfig();
541
+ const settings = cfg ? targetSettings(cfg, target, value !== void 0) : null;
542
+ if (!cfg || !settings) return false;
543
+ if (value === void 0) {
544
+ if (!deleteNested(settings, dottedKey)) return false;
545
+ } else writeNested(settings, dottedKey, value);
546
+ saveConfig(cfg);
547
+ return true;
548
+ });
549
+ }
550
+ function writeNested(obj, dottedKey, value) {
551
+ const keys = dottedKey.split(".");
552
+ let cur = obj;
553
+ for (let i = 0; i < keys.length - 1; i++) {
554
+ const k = keys[i];
555
+ if (k === void 0) continue;
556
+ const next = cur[k];
557
+ if (typeof next !== "object" || next === null) cur[k] = {};
558
+ cur = cur[k];
559
+ }
560
+ const leaf = keys[keys.length - 1];
561
+ if (leaf !== void 0) cur[leaf] = value;
562
+ }
563
+ function deleteNested(obj, dottedKey) {
564
+ const keys = dottedKey.split(".");
565
+ const chain = [obj];
566
+ let cur = obj;
567
+ for (let i = 0; i < keys.length - 1; i++) {
568
+ const k = keys[i];
569
+ if (k === void 0) return false;
570
+ const next = cur[k];
571
+ if (next == null || typeof next !== "object") return false;
572
+ cur = next;
573
+ chain.push(cur);
574
+ }
575
+ const leaf = keys[keys.length - 1];
576
+ if (leaf === void 0) return false;
577
+ if (!(leaf in cur)) return false;
578
+ delete cur[leaf];
579
+ for (let i = chain.length - 2; i >= 0; i--) {
580
+ const parent = chain[i];
581
+ const key = keys[i];
582
+ const child = chain[i + 1];
583
+ if (parent === void 0 || key === void 0 || child === void 0) break;
584
+ if (Object.keys(child).length === 0) delete parent[key];
585
+ else break;
586
+ }
587
+ return true;
588
+ }
459
589
  function isPathPrefix(prefix, path) {
460
590
  const head = comparablePath(prefix);
461
591
  const candidate = comparablePath(path);
@@ -463,7 +593,7 @@ function isPathPrefix(prefix, path) {
463
593
  return candidate.startsWith(head.endsWith("/") ? head : `${head}/`);
464
594
  }
465
595
  function comparablePath(path) {
466
- return sep$1 === "/" ? path : path.replaceAll(sep$1, "/");
596
+ return sep === "/" ? path : path.replaceAll(sep, "/");
467
597
  }
468
598
  function findEnclosingWorktreeRoot(projectPath) {
469
599
  const cfg = loadConfig();
@@ -489,7 +619,7 @@ function allConsolePortsAndSerials({ isMounted = isOnMountedVolume } = {}) {
489
619
  };
490
620
  if (!cfg) return result;
491
621
  for (const [path, proj] of Object.entries(cfg.projects || {})) {
492
- if (!existsSync(path) && isMounted(path)) continue;
622
+ if (!existsSync$1(path) && isMounted(path)) continue;
493
623
  for (const { platforms } of projectDeviceSlots(proj)) {
494
624
  const android = platforms.android;
495
625
  if (typeof android?.consolePort === "number") result.androidConsolePorts.push(android.consolePort);
@@ -499,4 +629,4 @@ function allConsolePortsAndSerials({ isMounted = isOnMountedVolume } = {}) {
499
629
  return result;
500
630
  }
501
631
  //#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 };
632
+ export { withDirLock as A, assignSlotDevice as B, setSupervisor as C, process_identity_exports as D, writeConfigSetting as E, formatBytes as F, parseDeviceSlotOption as G, deviceSlotKey as H, isOnMountedVolume as I, validateDeviceSlot as J, projectDeviceSlots as K, listMountedVolumes as L, readJsonFile as M, readJsonObject as N, acquireAvdClaim as O, directorySize as P, measuredDirectorySize as R, setDevice as S, withConfigLock as T, deviceSlotPlatforms as U, deviceSlotFileKey as V, parseDeviceSlotKey as W, refuseRelativeStimPaths as _, clearSupervisor as a, removeProject as b, findEnclosingWorktreeRoot as c, getConfigPath as d, getProject as f, loadConfig as g, isPathPrefix as h, clearDevice as i, isJsonObject as j, ownership_claim_exports as k, getConcurrencyLimits as l, getRepoSettings as m, allMetroPorts as n, configCorruptRepair as o, getProjectSettings as p, removeSlotDevice as q, claimMetroPort as r, ensureConfig as s, allConsolePortsAndSerials as t, getConfigDir as u, releaseAndroidConsolePort as v, upsertProject as w, saveConfig as x, releaseMetroPort as y, volumeRootFor 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-jIAlgilk.mjs";
2
+ import { _ as repoRoot } from "./worktree-R_cUlE7s.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-jIAlgilk.mjs";
2
+ import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
3
+ import { n as spawnDeclared } from "./spawn-claims-Dn3C_N6w.mjs";
4
+ import { d as startBuildHeartbeat, m as bundlerPin, n as HEARTBEAT_INTERVAL_MS } from "./xcode-BZqyxh3e.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-jIAlgilk.mjs";
2
2
  import { openSync } from "node:fs";
3
3
  //#region src/detached-entry.ts
4
4
  const LOG_FILE_FLAG = "--log-file";