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,15 +1,15 @@
1
- import { B as validateDeviceSlot, C as withConfigLock, I as deviceSlotPlatforms, L as parseDeviceSlotKey, R as projectDeviceSlots, S as upsertProject, a as clearSupervisor, f as getProject, w as process_identity_exports } from "./config-B7iyKWkL.mjs";
2
- import { a as withWorkspaceStateLock, h as workspaceDir, n as clearWorkspaceStateKeys, o as writeWorkspaceState, r as readWorkspaceState } from "./workspace-state-BT5E3SA2.mjs";
3
- import { a as findProjectRoot } from "./project-DDZWf_16.mjs";
4
- import { a as clearRemoteSession, i as clearManagedMetroTunnel, l as readRemoteSession, o as clearWorkspaceSupervisor, s as readMetroTunnel } from "./state-DZobzxLW.mjs";
5
- import { a as resolveProjectMetro, i as pidExists, n as killMetroTree, o as signalProcessTree } from "./metro-C7bcJ0uo.mjs";
6
- import { n as verifyCollectorOwnership } from "./ownership-Cp52QAK1.mjs";
7
- import { f as releasedLeaseFact, l as phaseLine, u as plural } from "./command-output-B3ycpsYv.mjs";
8
- import { S as resolveEasCliBin, n as teardownOwnedIosSim, t as teardownOwnedAvd } from "./teardown-BJqEzPo_.mjs";
9
- import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BYfZz_S-.mjs";
10
- import { l as releaseWorkspaceLeases } from "./device-lease-Ffz_M5t0.mjs";
11
- import { i as endRecordedSession } from "./device-remote-ByN8P6CB.mjs";
12
- import { r as stopTunnel, t as resolveSupervisorTarget } from "./ownership-wolE-0Do.mjs";
1
+ import { D as process_identity_exports, G as parseDeviceSlotOption, J as validateDeviceSlot, K as projectDeviceSlots, T as withConfigLock, U as deviceSlotPlatforms, W as parseDeviceSlotKey, a as clearSupervisor, f as getProject, w as upsertProject } from "./config-Cp9lM3wX.mjs";
2
+ import { _ as workspaceDir, c as writeWorkspaceState, n as clearWorkspaceStateKeys, o as withWorkspaceStateLock, r as readWorkspaceState } from "./workspace-state-DF2gCFhx.mjs";
3
+ import { o as findProjectRoot } from "./project-DS112DYB.mjs";
4
+ import { a as clearRemoteSession, i as clearManagedMetroTunnel, l as readRemoteSession, o as clearWorkspaceSupervisor, s as readMetroTunnel } from "./state-NcJzMEBF.mjs";
5
+ import { a as pidExists, c as resolveProjectMetro, l as signalProcessTree, n as killMetroTree } from "./metro-DrLHaMn2.mjs";
6
+ import { n as verifyCollectorOwnership } from "./ownership-D15Op8vN.mjs";
7
+ import { f as refuseNoProject, l as phaseLine, p as releasedLeaseFact, u as plural } from "./command-output-gByVmIjy.mjs";
8
+ import { b as resolveEasCliBin } from "./sim-pool-KaIsp2Ft.mjs";
9
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-DWaiRD0e.mjs";
10
+ import { l as releaseWorkspaceLeases } from "./device-lease-Crs-HkJY.mjs";
11
+ import { E as teardownOwnedIosSim, T as teardownOwnedAvd, i as endRecordedSession } from "./device-remote-AQ5rQPqr.mjs";
12
+ import { i as stopTunnel, t as resolveSupervisorTarget } from "./ownership-DCQPuWEc.mjs";
13
13
  import chalk from "chalk";
14
14
  //#region src/commands/stop.ts
15
15
  const DEFAULT_WAIT_MS = 1e4;
@@ -143,7 +143,7 @@ async function runStop(options) {
143
143
  result.summary = summarize(root, result.outcomes, result.ok);
144
144
  return result;
145
145
  }
146
- async function stopWorkspace({ root, project = void 0, state = void 0, collectors = void 0, signalCollector = (pid) => signalProcessTree(pid), verifyCollector = verifyCollectorOwnership, clearCollectors = clearCollectorState, isAlive = pidExists, killGroup = killMetroTree, inspectIdentity = process_identity_exports.inspectProcessIdentity, waitForDeath = void 0, waitMs = DEFAULT_WAIT_MS, resolveMetro = resolveProjectMetro, teardownIos = teardownOwnedIosSim, teardownAvd = teardownOwnedAvd, remoteDevice = void 0, teardownRemoteSession = defaultTeardownRemoteSession, metroTunnel = void 0, stopMetroTunnel = stopTunnel, releaseLeases = releaseWorkspaceLeases, freePort = defaultFreePort, clearRegistration = defaultClearRegistration, clearState = clearSupervisorState, report = (line) => console.error(line) }) {
146
+ async function stopWorkspace({ root, project = void 0, state = void 0, collectors = void 0, signalCollector = (pid) => signalProcessTree(pid), verifyCollector = verifyCollectorOwnership, clearCollectors = clearCollectorState, isAlive = pidExists, killGroup = killMetroTree, signalServer = (pid) => signalProcessTree(pid), inspectIdentity = process_identity_exports.inspectProcessIdentity, waitForDeath = void 0, waitMs = DEFAULT_WAIT_MS, resolveMetro = resolveProjectMetro, teardownIos = teardownOwnedIosSim, teardownAvd = teardownOwnedAvd, remoteDevice = void 0, teardownRemoteSession = defaultTeardownRemoteSession, metroTunnel = void 0, stopMetroTunnel = stopTunnel, releaseLeases = releaseWorkspaceLeases, freePort = defaultFreePort, clearRegistration = defaultClearRegistration, clearState = clearSupervisorState, report = (line) => console.error(line) }) {
147
147
  const proj = project === void 0 ? getProject(root) : project;
148
148
  const sup = state === void 0 ? readSupervisorState(root) : state;
149
149
  const collectorRecords = collectors === void 0 ? readCollectorState(root) : collectors;
@@ -172,6 +172,7 @@ async function stopWorkspace({ root, project = void 0, state = void 0, collector
172
172
  };
173
173
  let ok = true;
174
174
  let stillHolding = null;
175
+ let keepPort = null;
175
176
  const target = resolveSupervisorTarget({
176
177
  state: sup,
177
178
  record: proj?.supervisor ?? null,
@@ -232,10 +233,17 @@ async function stopWorkspace({ root, project = void 0, state = void 0, collector
232
233
  };
233
234
  else if (reservedPort === null) report(chalk.dim(phaseLine("metro", "no port reserved")));
234
235
  else {
235
- outcomes.metro = await stopMetro(reservedPort, root, {
236
+ const metro = await stopMetro(reservedPort, root, {
237
+ server: sup,
238
+ supervisorGone: target.status === "stale",
239
+ signalServer,
240
+ inspectIdentity,
241
+ waiter,
236
242
  resolveMetro,
237
243
  report
238
244
  });
245
+ outcomes.metro = metro.outcome;
246
+ keepPort = metro.keepPort;
239
247
  if (outcomes.metro.status === "refused" || outcomes.metro.status === "failed") {
240
248
  ok = false;
241
249
  stillHolding = outcomes.metro.reason;
@@ -254,6 +262,15 @@ async function stopWorkspace({ root, project = void 0, state = void 0, collector
254
262
  return;
255
263
  }
256
264
  if (reservedPort !== null) {
265
+ if (keepPort) {
266
+ outcomes.port = {
267
+ status: "kept",
268
+ port: reservedPort,
269
+ reason: keepPort
270
+ };
271
+ report(chalk.dim(phaseLine("port", `keeping reservation ${reservedPort} -- ${keepPort}`)));
272
+ return;
273
+ }
257
274
  if (freePort(root, reservedPort) === false) {
258
275
  ok = false;
259
276
  outcomes.port = {
@@ -334,8 +351,12 @@ async function stopWorkspace({ root, project = void 0, state = void 0, collector
334
351
  reason: stillHolding
335
352
  };
336
353
  report(chalk.yellow(phaseLine("port", `keeping reservation ${reservedPort ?? "(none)"} -- ${stillHolding}`)));
337
- const supervisorIsDown = outcomes.supervisor.status === "none" || outcomes.supervisor.status === "already-stopped" || outcomes.supervisor.status === "stopped";
338
- if (tunnelHolding && supervisorIsDown) {
354
+ const serverIsDown = [
355
+ "none",
356
+ "already-stopped",
357
+ "stopped"
358
+ ].includes(outcomes.supervisor.status) && !["refused", "failed"].includes(outcomes.metro.status);
359
+ if (tunnelHolding && serverIsDown) {
339
360
  clearState(root, sup);
340
361
  await clearRegistration(root, proj?.supervisor ?? null);
341
362
  }
@@ -476,21 +497,76 @@ async function stopSupervisor(target, { killGroup, waiter, report }) {
476
497
  reason
477
498
  };
478
499
  }
479
- async function stopMetro(port, root, { resolveMetro, report }) {
500
+ async function stopMetro(port, root, { server, supervisorGone, signalServer, inspectIdentity, waiter, resolveMetro, report }) {
501
+ const serverPid = numberOrNull(server?.serverPid);
502
+ const serverProcessToken = server?.serverProcessToken;
503
+ const serverRecord = {
504
+ pid: serverPid,
505
+ processToken: serverProcessToken
506
+ };
507
+ const identity = supervisorGone && serverPid && typeof serverProcessToken === "string" ? inspectIdentity(serverRecord) : null;
508
+ if (identity === "unknown") {
509
+ const reason = `the identity of dev server pid ${serverPid} left by the supervisor could not be verified`;
510
+ report(chalk.yellow(phaseLine("metro", `refusing to signal it: ${reason}`)));
511
+ return {
512
+ outcome: {
513
+ status: "refused",
514
+ port,
515
+ pid: serverPid,
516
+ reason
517
+ },
518
+ keepPort: null
519
+ };
520
+ }
521
+ if (identity === "same") {
522
+ report(chalk.dim(phaseLine("metro", `sending SIGTERM to dev server pid ${serverPid} left by the supervisor`)));
523
+ let signalled = false;
524
+ try {
525
+ signalled = signalServer(serverPid);
526
+ } catch {}
527
+ if (signalled ? await waiter(serverPid, serverProcessToken) : ["gone", "different"].includes(inspectIdentity(serverRecord))) {
528
+ report(chalk.green(phaseLine("metro", `dev server pid ${serverPid}`)));
529
+ return {
530
+ outcome: {
531
+ status: "stopped",
532
+ port,
533
+ pid: serverPid
534
+ },
535
+ keepPort: null
536
+ };
537
+ }
538
+ const reason = `dev server pid ${serverPid} left by the supervisor did not exit`;
539
+ report(chalk.red(phaseLine("metro", reason)));
540
+ return {
541
+ outcome: {
542
+ status: "failed",
543
+ port,
544
+ pid: serverPid,
545
+ reason
546
+ },
547
+ keepPort: null
548
+ };
549
+ }
480
550
  const resolution = await resolveMetro(port, root);
481
551
  if (resolution.missing) {
482
552
  report(chalk.dim(phaseLine("metro", `nothing listening on port ${port}`)));
483
553
  return {
484
- status: "missing",
485
- port
554
+ outcome: {
555
+ status: "missing",
556
+ port
557
+ },
558
+ keepPort: null
486
559
  };
487
560
  }
488
561
  const reason = resolution.notOurs || "no recorded Stim supervisor owns this server; stop it with the tool that started it";
489
562
  report(chalk.yellow(phaseLine("metro", `leaving port ${port} alone: ${reason}`)));
490
563
  return {
491
- status: "not-managed",
492
- port,
493
- reason
564
+ outcome: {
565
+ status: "not-managed",
566
+ port,
567
+ reason
568
+ },
569
+ keepPort: resolution.metro ? `a dev server from this project still answers on port ${port}` : null
494
570
  };
495
571
  }
496
572
  const OCCUPANCY_HINT = "often a UI-test runner or device tool still attached";
@@ -617,11 +693,11 @@ async function defaultClearRegistration(root, expected) {
617
693
  }
618
694
  }
619
695
  function stopCommand(program) {
620
- program.command("stop").description("The inverse of `start`: halt this workspace's supervisor, shut the owned device down (never deleted), and free the reserved port. Non-destructive -- the device stays assigned, so coming back costs a boot. Acts on the current workspace.").option("--slot <name>", "Stop only this device slot, keeping the shared server running", validateDeviceSlot).option("--json", "print the per-step outcomes as JSON").action(async (opts) => {
696
+ program.command("stop").description("The inverse of `start`: halt this workspace's supervisor, shut the owned device down (never deleted), and free the reserved port. Non-destructive -- the device stays assigned, so coming back costs a boot. Acts on the current workspace.").option("--slot <name>", "Stop only this device slot, keeping the shared server running", parseDeviceSlotOption).option("--json", "print the per-step outcomes as JSON").action(async (opts) => {
621
697
  const root = findProjectRoot(process.cwd());
622
698
  if (!root) {
623
- console.error(chalk.red("Not inside a project (no package.json found above the current directory)."));
624
- process.exit(1);
699
+ refuseNoProject({ json: Boolean(opts.json) });
700
+ return;
625
701
  }
626
702
  const { ok, outcomes, summary } = await withWorkspaceProcessLock(workspaceDir(root), "native-run", () => runStop({
627
703
  root,
@@ -1,3 +1,3 @@
1
- import { t as resolveSupervisorTarget } from "./ownership-wolE-0Do.mjs";
2
- import { a as resolveCollectorTargets, i as readSupervisorState, n as clearSupervisorState, o as runStop, r as readCollectorState, s as stopCommand, t as clearCollectorState } from "./stop-C8JW6kKp.mjs";
1
+ import { t as resolveSupervisorTarget } from "./ownership-DCQPuWEc.mjs";
2
+ import { a as resolveCollectorTargets, i as readSupervisorState, n as clearSupervisorState, o as runStop, r as readCollectorState, s as stopCommand, t as clearCollectorState } from "./stop-BO28HGqF.mjs";
3
3
  export { clearCollectorState, clearSupervisorState, stopCommand as default, readCollectorState, readSupervisorState, resolveCollectorTargets, resolveSupervisorTarget, runStop };
@@ -1,11 +1,12 @@
1
- import { a as clearSupervisor, w as process_identity_exports, x as setSupervisor } from "./config-B7iyKWkL.mjs";
2
- import { r as createNdjsonWriter } from "./ndjson-BZwI1NBv.mjs";
3
- import { _ as workspaceLogsDir, a as withWorkspaceStateLock, o as writeWorkspaceState, r as readWorkspaceState } from "./workspace-state-BT5E3SA2.mjs";
4
- import { i as detectIsExpo } from "./project-DDZWf_16.mjs";
1
+ import { C as setSupervisor, D as process_identity_exports, a as clearSupervisor } from "./config-Cp9lM3wX.mjs";
2
+ import { r as createNdjsonWriter } from "./ndjson-9uFNRwfe.mjs";
3
+ import { b as workspaceLogsDir, c as writeWorkspaceState, o as withWorkspaceStateLock, r as readWorkspaceState } from "./workspace-state-DF2gCFhx.mjs";
4
+ import { a as detectIsExpo } from "./project-DS112DYB.mjs";
5
5
  import { t as describeError } from "./errors-B6W7FkxQ.mjs";
6
- import { t as relaunchWithLogFile } from "./detached-entry-Bpo6ZY_I.mjs";
7
- import { c as readPidFile, f as writePidFile, n as MODE_EXPO, o as clearWorkspaceSupervisor, r as clearExpoMetroTunnel, t as MODE_BARE } from "./state-DZobzxLW.mjs";
6
+ import { t as relaunchWithLogFile } from "./detached-entry-KEu0_AXL.mjs";
7
+ import { c as readPidFile, f as writePidFile, n as MODE_EXPO, o as clearWorkspaceSupervisor, r as clearExpoMetroTunnel, t as MODE_BARE } from "./state-NcJzMEBF.mjs";
8
8
  import { existsSync, realpathSync } from "node:fs";
9
+ import { LOG_ROTATE_BYTES } from "@stim-cli/core";
9
10
  import { isAbsolute, join, resolve } from "node:path";
10
11
  import { fileURLToPath } from "node:url";
11
12
  //#region src/supervisor/run.ts
@@ -48,7 +49,7 @@ function parseArgs(argv) {
48
49
  async function runSupervisor({ root, port, tunnel = false, resetCache = false, isExpo = detectIsExpo, startBare = null, startExpo = null, now = Date.now, onExit = (code) => process.exit(code), attachSignals = true, stderr = (line) => console.error(line) }) {
49
50
  root = realpathSync(root);
50
51
  const logsDir = workspaceLogsDir(root);
51
- const writer = createNdjsonWriter(join(logsDir, "metro.ndjson"));
52
+ const writer = createNdjsonWriter(join(logsDir, "metro.ndjson"), { maxBytes: LOG_ROTATE_BYTES });
52
53
  const mode = isExpo(root) ? MODE_EXPO : MODE_BARE;
53
54
  const startedAt = new Date(now()).toISOString();
54
55
  const processToken = (0, process_identity_exports.captureProcessToken)(process.pid);
@@ -104,8 +105,32 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
104
105
  onExit(code);
105
106
  };
106
107
  let server;
108
+ const shutdown = async (code, event, msg) => {
109
+ if (stopping || !server) return;
110
+ stopping = true;
111
+ try {
112
+ await server.close();
113
+ } catch (err) {
114
+ writer.write({
115
+ src: "metro",
116
+ level: "warn",
117
+ event: "server_close_failed",
118
+ msg: describeError(err)
119
+ });
120
+ }
121
+ finish(code, event, code === 0 ? "info" : "error", msg);
122
+ };
123
+ if (attachSignals) for (const signal of ["SIGTERM", "SIGINT"]) process.on(signal, () => {
124
+ if (server) {
125
+ shutdown(0, "supervisor_stopped", `received ${signal}; stopping the ${mode} dev server`);
126
+ return;
127
+ }
128
+ if (stopping) return;
129
+ stopping = true;
130
+ finish(signal === "SIGINT" ? 130 : 143, "supervisor_stopped", "warn", `received ${signal} before the ${mode} dev server started; stopping`);
131
+ });
107
132
  try {
108
- server = await (mode === "expo-child" ? startExpo || (await import("./server-expo-YDDG3eP7.mjs")).startExpoServer : startBare || (await import("./server-bare-BQkyxa_b.mjs")).startBareServer)({
133
+ server = await (mode === "expo-child" ? startExpo || (await import("./server-expo-CeILEu86.mjs")).startExpoServer : startBare || (await import("./server-bare-DWkkcIxV.mjs")).startBareServer)({
109
134
  root,
110
135
  port,
111
136
  logsDir,
@@ -132,46 +157,39 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
132
157
  }
133
158
  });
134
159
  } catch (err) {
160
+ if (stopping) return null;
135
161
  stderr(`Stim supervisor: failed to start the ${mode} dev server: ${describeError(err)}`);
136
162
  finish(1, "supervisor_failed", "fatal", `failed to start the ${mode} dev server: ${describeError(err)}`);
137
163
  return null;
138
164
  }
139
- const readyServer = server;
140
- if (readyServer.serverPid) withWorkspaceStateLock(root, () => {
141
- if (readWorkspaceState(root)?.supervisor?.processToken === processToken) writeWorkspaceState(root, { supervisor: {
142
- ...record,
143
- serverPid: readyServer.serverPid
144
- } });
145
- });
165
+ if (stopping) {
166
+ try {
167
+ await server.close();
168
+ } catch {}
169
+ return null;
170
+ }
171
+ const serverPid = server.serverPid;
172
+ if (serverPid) {
173
+ const serverProcessToken = (0, process_identity_exports.captureProcessToken)(serverPid);
174
+ withWorkspaceStateLock(root, () => {
175
+ if (readWorkspaceState(root)?.supervisor?.processToken === processToken) writeWorkspaceState(root, { supervisor: {
176
+ ...record,
177
+ serverPid,
178
+ ...serverProcessToken ? { serverProcessToken } : {}
179
+ } });
180
+ });
181
+ }
146
182
  writer.write({
147
183
  src: "metro",
148
184
  level: "info",
149
185
  event: "server_started",
150
186
  msg: `${mode} dev server listening on port ${port}`
151
187
  });
152
- const shutdown = async (code, event, msg) => {
153
- if (stopping) return;
154
- stopping = true;
155
- try {
156
- await readyServer.close();
157
- } catch (err) {
158
- writer.write({
159
- src: "metro",
160
- level: "warn",
161
- event: "server_close_failed",
162
- msg: describeError(err)
163
- });
164
- }
165
- finish(code, event, code === 0 ? "info" : "error", msg);
166
- };
167
- readyServer.onExit?.((info) => {
188
+ server.onExit?.((info) => {
168
189
  if (stopping) return;
169
190
  const detail = info?.signal ? `signal ${info.signal}` : `exit code ${info?.code ?? "unknown"}`;
170
191
  shutdown(1, "supervisor_stopped", `the ${mode} dev server exited unexpectedly (${detail}); shutting the supervisor down`);
171
192
  });
172
- if (attachSignals) for (const signal of ["SIGTERM", "SIGINT"]) process.on(signal, () => {
173
- shutdown(0, "supervisor_stopped", `received ${signal}; stopping the ${mode} dev server`);
174
- });
175
193
  return {
176
194
  mode,
177
195
  server,
@@ -1,4 +1,5 @@
1
- import { E as ownership_claim_exports } from "./config-B7iyKWkL.mjs";
1
+ import { k as ownership_claim_exports } from "./config-Cp9lM3wX.mjs";
2
+ import { r as stopDeclaringSpawnsOn, t as declareSpawnsOn } from "./spawn-claims-Dn3C_N6w.mjs";
2
3
  import { join } from "node:path";
3
4
  //#region src/engine/workspace-process-lock.ts
4
5
  const DEFAULT_WAIT_MS = 6e4;
@@ -10,11 +11,11 @@ function workspaceProcessLockError(err) {
10
11
  if (code === "STIM_LOCK_TIMEOUT") return "timeout";
11
12
  return null;
12
13
  }
13
- function lockPath(root, name, external) {
14
+ function workspaceProcessLockPath(root, name, external) {
14
15
  return external ? join(root, `${name}.lock`) : join(root, ".stim", `${name}.lock`);
15
16
  }
16
- async function withWorkspaceProcessLock(root, name, fn, { now = Date.now, sleep = defaultSleep, waitMs = DEFAULT_WAIT_MS, ownerPurpose, rejectOwnerPurposes = [], external = false } = {}) {
17
- const path = lockPath(root, name, external);
17
+ async function withWorkspaceProcessLock(root, name, fn, { now = Date.now, sleep = defaultSleep, waitMs = DEFAULT_WAIT_MS, ownerPurpose, rejectOwnerPurposes = [], external = false, declareSpawns = false } = {}) {
18
+ const path = workspaceProcessLockPath(root, name, external);
18
19
  const deadline = now() + waitMs;
19
20
  for (;;) {
20
21
  const attempt = (0, ownership_claim_exports.tryAcquireClaim)({
@@ -24,10 +25,14 @@ async function withWorkspaceProcessLock(root, name, fn, { now = Date.now, sleep
24
25
  details: ownerPurpose ? { purpose: ownerPurpose } : {}
25
26
  });
26
27
  if (attempt.pending) (0, ownership_claim_exports.releaseClaim)(attempt.pending);
27
- if (attempt.acquired) try {
28
- return await fn();
29
- } finally {
30
- (0, ownership_claim_exports.releaseClaim)(attempt.acquired);
28
+ if (attempt.acquired) {
29
+ if (declareSpawns) declareSpawnsOn(attempt.acquired);
30
+ try {
31
+ return await fn(attempt.acquired);
32
+ } finally {
33
+ stopDeclaringSpawnsOn(attempt.acquired);
34
+ (0, ownership_claim_exports.releaseClaim)(attempt.acquired);
35
+ }
31
36
  }
32
37
  const purpose = (attempt.held ?? attempt.waitingFor?.[0])?.details.purpose;
33
38
  if (typeof purpose === "string" && rejectOwnerPurposes.includes(purpose)) {
@@ -44,4 +49,4 @@ async function withWorkspaceProcessLock(root, name, fn, { now = Date.now, sleep
44
49
  }
45
50
  }
46
51
  //#endregion
47
- export { workspaceProcessLockError as n, withWorkspaceProcessLock as t };
52
+ export { workspaceProcessLockError as n, workspaceProcessLockPath as r, withWorkspaceProcessLock as t };
@@ -1,19 +1,19 @@
1
- import { D as withDirLock, u as getConfigDir } from "./config-B7iyKWkL.mjs";
2
- import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
3
- import { dirname } from "node:path";
4
- import { join as join$1, resolve as resolve$1 } from "path";
1
+ import { A as withDirLock, N as readJsonObject, u as getConfigDir } from "./config-Cp9lM3wX.mjs";
2
+ import { existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
3
+ import { join, resolve } from "path";
5
4
  import { buildCacheRoot as sharedBuildCache, workspaceId, workspaceName as workspaceName$1, workspaceStateDir as workspaceDir } from "@stim-cli/core";
5
+ import { dirname as dirname$1, join as join$1 } from "node:path";
6
6
  //#region src/workspace/paths.ts
7
7
  const WORKSPACE_METADATA_FILE_NAME = "workspace.json";
8
8
  function workspaceMetadataFile(projectRoot) {
9
- return join$1(workspaceDir(projectRoot), WORKSPACE_METADATA_FILE_NAME);
9
+ return join(workspaceDir(projectRoot), WORKSPACE_METADATA_FILE_NAME);
10
10
  }
11
11
  function ensureWorkspaceStorage(projectRoot) {
12
- const canonicalRoot = resolve$1(projectRoot);
12
+ const canonicalRoot = resolve(projectRoot);
13
13
  const dir = workspaceDir(canonicalRoot);
14
14
  const file = workspaceMetadataFile(canonicalRoot);
15
15
  mkdirSync(dir, { recursive: true });
16
- return withDirLock(join$1(dir, "metadata.lock"), () => {
16
+ return withDirLock(join(dir, "metadata.lock"), () => {
17
17
  if (!existsSync(file)) {
18
18
  const metadata = {
19
19
  projectRoot: canonicalRoot,
@@ -30,57 +30,52 @@ function ensureWorkspaceStorage(projectRoot) {
30
30
  }
31
31
  return dir;
32
32
  }
33
- try {
34
- if (JSON.parse(readFileSync(file, "utf-8")).projectRoot === canonicalRoot) return dir;
35
- } catch {}
33
+ if (readJsonObject(file)?.projectRoot === canonicalRoot) return dir;
36
34
  const error = /* @__PURE__ */ new Error(`Stim workspace collision at ${dir}: its workspace.json does not belong to ${canonicalRoot}.`);
37
35
  error.code = "STIM_WORKSPACE_COLLISION";
38
36
  throw error;
39
37
  });
40
38
  }
41
39
  function workspaceLogsDir(projectRoot) {
42
- return join$1(workspaceDir(projectRoot), "logs");
40
+ return join(workspaceDir(projectRoot), "logs");
41
+ }
42
+ function workspaceLogErrorIndex(projectRoot) {
43
+ return join(workspaceDir(projectRoot), "log-error-index.json");
43
44
  }
44
45
  function workspaceDerivedData(projectRoot) {
45
- return join$1(workspaceDir(projectRoot), "derived-data");
46
+ return join(workspaceDir(projectRoot), "derived-data");
46
47
  }
47
48
  function supervisorPidFile(projectRoot) {
48
- return join$1(workspaceDir(projectRoot), "supervisor.pid");
49
+ return join(workspaceDir(projectRoot), "supervisor.pid");
49
50
  }
50
51
  function workspaceStateFile(projectRoot) {
51
- return join$1(workspaceDir(projectRoot), "state.json");
52
+ return join(workspaceDir(projectRoot), "state.json");
52
53
  }
53
54
  function workspaceStateLock(projectRoot) {
54
- return join$1(workspaceDir(projectRoot), "state.lock");
55
+ return join(workspaceDir(projectRoot), "state.lock");
55
56
  }
56
57
  function supervisorLogFile(projectRoot) {
57
- return join$1(workspaceLogsDir(projectRoot), "supervisor.log");
58
+ return join(workspaceLogsDir(projectRoot), "supervisor.log");
58
59
  }
59
60
  function emulatorLogFile(projectRoot) {
60
- return join$1(workspaceLogsDir(projectRoot), "emulator.log");
61
+ return join(workspaceLogsDir(projectRoot), "emulator.log");
61
62
  }
62
63
  function sharedCompilationCache() {
63
- return join$1(getConfigDir(), "compilation-cache");
64
+ return join(getConfigDir(), "compilation-cache");
64
65
  }
65
66
  function sharedCcache() {
66
- return join$1(getConfigDir(), "ccache");
67
+ return join(getConfigDir(), "ccache");
67
68
  }
68
69
  //#endregion
69
70
  //#region src/workspace/workspace-state.ts
70
71
  function readWorkspaceState(root) {
71
- try {
72
- const parsed = JSON.parse(readFileSync(workspaceStateFile(root), "utf-8"));
73
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
74
- return parsed;
75
- } catch {
76
- return null;
77
- }
72
+ return readJsonObject(workspaceStateFile(root));
78
73
  }
79
74
  function withWorkspaceStateLock(root, fn) {
80
75
  const file = workspaceStateFile(root);
81
76
  return withDirLock(workspaceStateLock(root), fn, { ensureParent: () => {
82
77
  ensureWorkspaceStorage(root);
83
- mkdirSync(dirname(file), { recursive: true });
78
+ mkdirSync(dirname$1(file), { recursive: true });
84
79
  } });
85
80
  }
86
81
  function writeWorkspaceState(root, patch) {
@@ -94,7 +89,7 @@ function updateWorkspaceState(root, update) {
94
89
  }
95
90
  function replaceWorkspaceState(root, state) {
96
91
  const file = workspaceStateFile(root);
97
- mkdirSync(dirname(file), { recursive: true });
92
+ mkdirSync(dirname$1(file), { recursive: true });
98
93
  const tmp = `${file}.tmp-${process.pid}`;
99
94
  writeFileSync(tmp, `${JSON.stringify(state, null, 2)}\n`);
100
95
  renameSync(tmp, file);
@@ -142,5 +137,33 @@ function clearWorkspaceStateKey(root, key, shouldClear) {
142
137
  return true;
143
138
  });
144
139
  }
140
+ function recordWorkspaceUse(root, now = /* @__PURE__ */ new Date()) {
141
+ try {
142
+ writeWorkspaceState(root, { lastUsedAt: now.toISOString() });
143
+ } catch {}
144
+ }
145
+ function lastUseFrom(state, logMtimes) {
146
+ const candidates = [
147
+ Date.parse(String(state?.lastUsedAt ?? "")),
148
+ Date.parse(String(state?.lastBuild?.startedAt ?? "")),
149
+ Date.parse(String(state?.supervisor?.startedAt ?? "")),
150
+ ...logMtimes
151
+ ].filter(Number.isFinite);
152
+ return candidates.length ? Math.max(...candidates) : NaN;
153
+ }
154
+ function workspaceLastUsed(root) {
155
+ const logs = workspaceLogsDir(root);
156
+ let mtimes = [];
157
+ try {
158
+ mtimes = readdirSync(logs).flatMap((name) => {
159
+ try {
160
+ return [statSync(join$1(logs, name)).mtimeMs];
161
+ } catch {
162
+ return [];
163
+ }
164
+ });
165
+ } catch {}
166
+ return lastUseFrom(readWorkspaceState(root), mtimes);
167
+ }
145
168
  //#endregion
146
- export { workspaceLogsDir as _, withWorkspaceStateLock as a, ensureWorkspaceStorage as c, sharedCompilationCache as d, supervisorLogFile as f, workspaceId as g, workspaceDir as h, updateWorkspaceState as i, sharedBuildCache as l, workspaceDerivedData as m, clearWorkspaceStateKeys as n, writeWorkspaceState as o, supervisorPidFile as p, readWorkspaceState as r, emulatorLogFile as s, clearWorkspaceStateKey as t, sharedCcache as u, workspaceName$1 as v, workspaceStateFile as y };
169
+ export { workspaceStateFile as S, workspaceDir as _, updateWorkspaceState as a, workspaceLogsDir as b, writeWorkspaceState as c, sharedBuildCache as d, sharedCcache as f, workspaceDerivedData as g, supervisorPidFile as h, recordWorkspaceUse as i, emulatorLogFile as l, supervisorLogFile as m, clearWorkspaceStateKeys as n, withWorkspaceStateLock as o, sharedCompilationCache as p, readWorkspaceState as r, workspaceLastUsed as s, clearWorkspaceStateKey as t, ensureWorkspaceStorage as u, workspaceId as v, workspaceName$1 as x, workspaceLogErrorIndex as y };
@@ -0,0 +1,2 @@
1
+ import { a as reclaimKeys, c as removalBlockers, d as removeWorktreeTarget, f as worktreeCommand, i as porcelainPath, l as removalPath, n as excludePodChurn, o as registerRemove, r as matchWorktreeEntry, s as registerWarm, t as dependencyInstallCommand, u as removalRemedy } from "./worktree-LGB-UwgG.mjs";
2
+ export { worktreeCommand as default, dependencyInstallCommand, excludePodChurn, matchWorktreeEntry, porcelainPath, reclaimKeys, registerRemove, registerWarm, removalBlockers, removalPath, removalRemedy, removeWorktreeTarget };