stim 1.9.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 (83) hide show
  1. package/dist/{android-_y4Ohi5z.mjs → android-787HhP5w.mjs} +119 -29
  2. package/dist/{android-BPGizezh.mjs → android-DBkeHwpm.mjs} +168 -95
  3. package/dist/{android-DEgo4oCo.mjs → android-DPov-fv2.mjs} +2 -2
  4. package/dist/{android-cas-eiExNLf7.mjs → android-cas-CITmIeWY.mjs} +9 -8
  5. package/dist/android-cas-compiler.mjs +1 -1
  6. package/dist/{app-install-Uf3a83Tv.mjs → app-install-CrJ6t_bv.mjs} +2 -2
  7. package/dist/budget-BP4jZZL4.mjs +620 -0
  8. package/dist/build-progress-BXx-ZBe4.mjs +512 -0
  9. package/dist/{cache-manifest-Dg7OnMrp.mjs → cache-manifest-Pubfp8wV.mjs} +1 -1
  10. package/dist/cache-manifest.mjs +1 -1
  11. package/dist/cli.mjs +16 -16
  12. package/dist/collector-run.d.mts +5 -1
  13. package/dist/collector-run.mjs +7 -7
  14. package/dist/{command-output-gByVmIjy.mjs → command-output-D5z2_bcF.mjs} +1 -1
  15. package/dist/{config-Cp9lM3wX.mjs → config-BEq0Qw_a.mjs} +14 -131
  16. package/dist/{dependency-state-Bq5DcLGn.mjs → dependency-state-r9UesA9a.mjs} +2 -2
  17. package/dist/{deps-Cip5vUm1.mjs → deps-DP4eWqea.mjs} +3 -3
  18. package/dist/{detached-entry-KEu0_AXL.mjs → detached-entry-DJHoca49.mjs} +1 -1
  19. package/dist/{dev-client-HyqISM0h.mjs → dev-client-92U593KT.mjs} +137 -250
  20. package/dist/{device-D4U-xLty.mjs → device-DhTZe7L4.mjs} +7 -7
  21. package/dist/{build-lock-DPXPLgTc.mjs → device-lease-CbEaIk7b.mjs} +370 -12
  22. package/dist/{device-pool-CnTAz8X1.mjs → device-pool-Di8Q-9Ro.mjs} +4 -4
  23. package/dist/{device-remote-AQ5rQPqr.mjs → device-remote-Dy1jGj3U.mjs} +547 -468
  24. package/dist/{doctor-B_Ei3bZu.mjs → doctor-B-inAJmt.mjs} +58 -22
  25. package/dist/{error-diagnostics-Vfz83GCF.mjs → error-diagnostics-xxWKNO3-.mjs} +11 -12
  26. package/dist/{exec-jIAlgilk.mjs → exec-DK8KaVHK.mjs} +3 -2
  27. package/dist/{gc-Ckw8RQHi.mjs → gc-ChPFZMRC.mjs} +500 -1114
  28. package/dist/{guide-BiAbhs1J.mjs → guide-DwmaD_Wa.mjs} +882 -528
  29. package/dist/guide-status-CWvti2Lg.mjs +125 -0
  30. package/dist/idle-DpRLp5lj.mjs +157 -0
  31. package/dist/in-use-CjUluqIX.mjs +245 -0
  32. package/dist/{ios-BL2NFckr.mjs → ios-B2Fpqvlb.mjs} +25 -59
  33. package/dist/{ios-BzUJWk1K.mjs → ios-Dd-hYJBh.mjs} +130 -89
  34. package/dist/{ios-device-DPsSm0v_.mjs → ios-device-BiOsSVzd.mjs} +1 -1
  35. package/dist/{ios-device-D972i9wf.mjs → ios-device-CtRdEVjx.mjs} +3 -3
  36. package/dist/{launch-verify-Q_OtW1Rf.mjs → launch-verify-CQS_Kg-C.mjs} +5 -5
  37. package/dist/{logs-CI9NQSK7.mjs → logs-MARIy6m9.mjs} +9 -9
  38. package/dist/{metro-DrLHaMn2.mjs → metro-BWLOJ_Wr.mjs} +116 -10
  39. package/dist/{named-ports-CmLQSIRj.mjs → named-ports-DiTfSz6p.mjs} +4 -4
  40. package/dist/{ndjson-9uFNRwfe.mjs → ndjson-CDY9IkVC.mjs} +3 -56
  41. package/dist/{ndjson-DvHSsNU9.d.mts → ndjson-Du3j0AQ9.d.mts} +1 -0
  42. package/dist/{ownership-D15Op8vN.mjs → ownership-D2xbLVoz.mjs} +2 -3
  43. package/dist/ownership-DZjdx8Gq.mjs +1180 -0
  44. package/dist/{ports-BNKoWVWZ.mjs → ports-CLSSFOuz.mjs} +3 -3
  45. package/dist/{project-DS112DYB.mjs → project-BXZzOABt.mjs} +2 -2
  46. package/dist/{reload-ZNJUo1pl.mjs → reload-DoETJ3rl.mjs} +9 -10
  47. package/dist/{server-bare-DWkkcIxV.mjs → server-bare-DVbb7kXH.mjs} +1 -1
  48. package/dist/{server-expo-B_lctBR3.mjs → server-expo-BvOCeakp.mjs} +4 -4
  49. package/dist/server-expo-DBVTdult.mjs +2 -0
  50. package/dist/{settings-D030k0t4.mjs → settings-DOugPlPz.mjs} +16 -428
  51. package/dist/{settings-BKNh5rl4.mjs → settings-EWYNwe72.mjs} +16 -14
  52. package/dist/settings.schema.json +75 -0
  53. package/dist/{simslim-B_LsGLuJ.mjs → simslim-Ca3487H6.mjs} +182 -5
  54. package/dist/{slot-launch-DU9A-gYw.mjs → slot-launch-C5cg0fmk.mjs} +4 -4
  55. package/dist/{spawn-claims-Dn3C_N6w.mjs → spawn-claims-CUleC2cM.mjs} +1 -1
  56. package/dist/{start-DYkkKF0s.mjs → start-F9gv5dcC.mjs} +147 -98
  57. package/dist/start-VoFsPSes.mjs +2 -0
  58. package/dist/{state-NcJzMEBF.mjs → state-WGdIbi_d.mjs} +2 -2
  59. package/dist/{stats-DG9e66Rz.mjs → stats-BAgJaLA4.mjs} +1 -1
  60. package/dist/{stats-B63-JVAY.mjs → stats-DBYJ5bxL.mjs} +4 -4
  61. package/dist/{status-S6ehL4Ui.mjs → status-DLCD-o3s.mjs} +63 -231
  62. package/dist/status-Dr4fRkym.mjs +216 -0
  63. package/dist/{guide-status-BAMVU0ZA.mjs → stim-installations-CINXy_su.mjs} +3 -124
  64. package/dist/{stop-BO28HGqF.mjs → stop-Dj4pfmyv.mjs} +22 -18
  65. package/dist/{stop-T1R-cZRz.mjs → stop-ZvOeTmKb.mjs} +2 -2
  66. package/dist/supervisor-run.d.mts +11 -2
  67. package/dist/supervisor-run.mjs +185 -17
  68. package/dist/{workspace-process-lock-DWaiRD0e.mjs → workspace-process-lock-BXA96myD.mjs} +2 -2
  69. package/dist/workspaces-BTUyFfkv.mjs +1003 -0
  70. package/dist/{worktree-DKhSF0UX.mjs → worktree-4aZg2NnI.mjs} +1 -1
  71. package/dist/{worktree-LGB-UwgG.mjs → worktree-BMS7GeqB.mjs} +40 -122
  72. package/dist/{worktree-R_cUlE7s.mjs → worktree-BlizKDpZ.mjs} +143 -18
  73. package/dist/{xcode-BZqyxh3e.mjs → xcode-B2tBkf0m.mjs} +9 -158
  74. package/package.json +6 -5
  75. package/dist/build-progress-CVqkfPC6.mjs +0 -188
  76. package/dist/device-lease-Crs-HkJY.mjs +0 -366
  77. package/dist/logs-query-Cqma3WIu.mjs +0 -298
  78. package/dist/ownership-DCQPuWEc.mjs +0 -489
  79. package/dist/public-http-probe-CyMDrQ3c.mjs +0 -61
  80. package/dist/server-expo-CeILEu86.mjs +0 -2
  81. package/dist/sim-pool-KaIsp2Ft.mjs +0 -723
  82. package/dist/spawn-entry-nCE6m770.mjs +0 -14
  83. package/dist/workspace-state-DF2gCFhx.mjs +0 -169
@@ -1,7 +1,5 @@
1
- import { f as getProject, u as getConfigDir, w as upsertProject } from "./config-Cp9lM3wX.mjs";
2
- import { t as getExecutor } from "./exec-jIAlgilk.mjs";
3
- import { n as appProjectProblem } from "./project-DS112DYB.mjs";
4
- import { accessSync, constants, mkdirSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
1
+ import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
2
+ import { accessSync, constants, realpathSync, statSync } from "node:fs";
5
3
  import { delimiter, join, resolve } from "node:path";
6
4
  //#region src/diagnostics/stim-installations.ts
7
5
  const VERSION_OUTPUT_LIMIT = 4096;
@@ -151,123 +149,4 @@ async function inspectStimVersions(runningVersion, { pathValue = process.env.PAT
151
149
  return analyzeStimVersions(runningVersion, canonicalPath(runningPath), installations);
152
150
  }
153
151
  //#endregion
154
- //#region src/guide-status.ts
155
- const DAY_MS = 864e5;
156
- const DOCTOR_STALE_MS = 7 * DAY_MS;
157
- const UPDATE_CHECK_INTERVAL_MS = DAY_MS;
158
- const UPDATE_CHECK_TIMEOUT_MS = 2e3;
159
- const LATEST_URL = "https://registry.npmjs.org/stim/latest";
160
- const PLATFORMS = ["ios", "android"];
161
- function recordDoctorRun(projectRoot, platform, version, now = /* @__PURE__ */ new Date()) {
162
- const run = {
163
- at: now.toISOString(),
164
- version
165
- };
166
- try {
167
- if (appProjectProblem(projectRoot)) return;
168
- upsertProject(projectRoot, (existing) => {
169
- const doctorRuns = { ...existing.doctorRuns };
170
- for (const target of platform ? [platform] : PLATFORMS) doctorRuns[target] = run;
171
- return { doctorRuns };
172
- });
173
- } catch {}
174
- }
175
- function doctorDueReason(record, running, now) {
176
- const at = record ? Date.parse(record.at) : NaN;
177
- if (!record || Number.isNaN(at)) return "never run";
178
- if (record.version !== running) return `last run with stim ${record.version}`;
179
- const age = now.getTime() - at;
180
- if (age > DOCTOR_STALE_MS) return `last run ${Math.floor(age / DAY_MS)} days ago`;
181
- return null;
182
- }
183
- function updateCacheFile() {
184
- return join(getConfigDir(), "update-check.json");
185
- }
186
- function readUpdateCache() {
187
- try {
188
- const parsed = JSON.parse(readFileSync(updateCacheFile(), "utf-8"));
189
- if (typeof parsed?.checkedAt !== "string") return null;
190
- return {
191
- checkedAt: parsed.checkedAt,
192
- latest: typeof parsed.latest === "string" ? parsed.latest : null
193
- };
194
- } catch {
195
- return null;
196
- }
197
- }
198
- function writeUpdateCache(cache) {
199
- const target = updateCacheFile();
200
- const tmp = `${target}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
201
- try {
202
- mkdirSync(getConfigDir(), { recursive: true });
203
- writeFileSync(tmp, `${JSON.stringify(cache)}\n`);
204
- renameSync(tmp, target);
205
- } catch {
206
- try {
207
- unlinkSync(tmp);
208
- } catch {}
209
- }
210
- }
211
- async function fetchLatest(fetchImpl) {
212
- const res = await fetchImpl(LATEST_URL, { signal: AbortSignal.timeout(UPDATE_CHECK_TIMEOUT_MS) });
213
- if (!res.ok) return null;
214
- const body = await res.json();
215
- return typeof body.version === "string" ? body.version : null;
216
- }
217
- async function checkForUpdate(running, { now = /* @__PURE__ */ new Date(), fetch: fetchImpl = fetch } = {}) {
218
- if (process.env.STIM_NO_UPDATE_CHECK) return null;
219
- const cached = readUpdateCache();
220
- const checkedAt = cached ? Date.parse(cached.checkedAt) : NaN;
221
- let latest = cached?.latest ?? null;
222
- if (Number.isNaN(checkedAt) || now.getTime() - checkedAt > UPDATE_CHECK_INTERVAL_MS) {
223
- try {
224
- latest = await fetchLatest(fetchImpl);
225
- } catch {
226
- latest = null;
227
- }
228
- writeUpdateCache({
229
- checkedAt: now.toISOString(),
230
- latest
231
- });
232
- }
233
- if (!latest) return null;
234
- const comparison = compareStimVersions(latest, running);
235
- return comparison !== null && comparison > 0 ? latest : null;
236
- }
237
- function renderGuideStatus({ running, doctor, latest }) {
238
- const lines = [];
239
- if (doctor.length) {
240
- const named = doctor.map((due) => `${due.platform} (${due.reason})`).join(" and ");
241
- const command = doctor.length === 1 ? `stim doctor --platform ${doctor[0]?.platform}` : "stim doctor";
242
- lines.push(` Doctor is due for ${named}.`, ` Run before native work: ${command}`);
243
- }
244
- if (latest) lines.push(` stim ${latest} is available (running ${running}): npm install -g stim@latest`);
245
- if (!lines.length) return null;
246
- return ["STATUS", ...lines].join("\n");
247
- }
248
- async function guideStatus({ projectRoot, running, now = /* @__PURE__ */ new Date(), fetch: fetchImpl = fetch }) {
249
- const doctor = [];
250
- if (projectRoot && !appProjectProblem(projectRoot)) {
251
- let runs = null;
252
- try {
253
- runs = getProject(projectRoot)?.doctorRuns ?? {};
254
- } catch {}
255
- if (runs) for (const platform of PLATFORMS) {
256
- const reason = doctorDueReason(runs[platform], running, now);
257
- if (reason) doctor.push({
258
- platform,
259
- reason
260
- });
261
- }
262
- }
263
- return renderGuideStatus({
264
- running,
265
- doctor,
266
- latest: await checkForUpdate(running, {
267
- now,
268
- fetch: fetchImpl
269
- })
270
- });
271
- }
272
- //#endregion
273
- export { inspectStimVersions as i, recordDoctorRun as n, compareStimVersions as r, guideStatus as t };
152
+ export { inspectStimVersions as n, compareStimVersions as t };
@@ -1,15 +1,13 @@
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";
1
+ import { B as parseDeviceSlotKey, D as process_identity_exports, H as projectDeviceSlots, T as withConfigLock, V as parseDeviceSlotOption, W as validateDeviceSlot, a as clearSupervisor, f as getProject, w as upsertProject, z as deviceSlotPlatforms } from "./config-BEq0Qw_a.mjs";
2
+ import { O as workspaceDir, _ as withWorkspaceStateLock, c as signalProcessTree, i as pidExists, m as readWorkspaceState, p as clearWorkspaceStateKeys, s as resolveProjectMetro, t as killMetroTree, y as writeWorkspaceState } from "./metro-BWLOJ_Wr.mjs";
3
+ import { o as findProjectRoot } from "./project-BXZzOABt.mjs";
4
+ import { a as clearRemoteSession, i as clearManagedMetroTunnel, l as readRemoteSession, o as clearWorkspaceSupervisor, s as readMetroTunnel } from "./state-WGdIbi_d.mjs";
5
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BXA96myD.mjs";
6
+ import { f as refuseNoProject, l as phaseLine, p as releasedLeaseFact, u as plural } from "./command-output-D5z2_bcF.mjs";
7
+ import { u as releaseWorkspaceLeases } from "./device-lease-CbEaIk7b.mjs";
8
+ import { d as teardownOwnedAvd, f as teardownOwnedIosSim, i as stopTunnel, t as resolveSupervisorTarget } from "./ownership-DZjdx8Gq.mjs";
9
+ import { n as verifyCollectorOwnership } from "./ownership-D2xbLVoz.mjs";
10
+ import { i as endRecordedSession, z as resolveEasCliBin } from "./device-remote-Dy1jGj3U.mjs";
13
11
  import chalk from "chalk";
14
12
  //#region src/commands/stop.ts
15
13
  const DEFAULT_WAIT_MS = 1e4;
@@ -597,7 +595,7 @@ function shutDownDevices(project, { teardownIos, teardownAvd, report }) {
597
595
  } else device[iosKey] = reportDevice(iosUdid, teardownIos(iosUdid, {
598
596
  del: false,
599
597
  label: iosName
600
- }), report);
598
+ }), report, "ios");
601
599
  }
602
600
  const android = platforms.android;
603
601
  if (android?.avdName) {
@@ -609,12 +607,15 @@ function shutDownDevices(project, { teardownIos, teardownAvd, report }) {
609
607
  reason: "Stim does not own this device"
610
608
  };
611
609
  report(chalk.dim(phaseLine("device", `${android.avdName} is not Stim-owned, leaving it running`)));
612
- } else device[androidKey] = reportDevice(android.avdName, teardownAvd(android.avdName, { del: false }), report);
610
+ } else device[androidKey] = reportDevice(android.avdName, teardownAvd(android.avdName, { del: false }), report, "android");
613
611
  }
614
612
  }
615
613
  return device;
616
614
  }
617
- function reportDevice(label, r, report) {
615
+ function deviceShutdownRemedy(kind, label) {
616
+ return kind === "ios" ? `check it with \`xcrun simctl list devices\`, then \`xcrun simctl shutdown ${label}\` yourself -- if it will not respond, \`stim gc --delete\` reclaims it` : `check it with \`adb devices\`, then \`adb -s <serial> emu kill\` yourself -- if it will not respond, \`stim gc --delete\` reclaims it`;
617
+ }
618
+ function reportDevice(label, r, report, kind) {
618
619
  if (r.status === "torn-down") {
619
620
  report(chalk.green(phaseLine("device", `shut down ${r.label ?? label}`)));
620
621
  return {
@@ -639,11 +640,14 @@ function reportDevice(label, r, report) {
639
640
  reason
640
641
  };
641
642
  }
642
- report(chalk.red(phaseLine("device", `failed to shut down ${label}: ${r.reason}`)));
643
+ const remedy = deviceShutdownRemedy(kind, r.label ?? label);
644
+ report(chalk.red(phaseLine("device", `failed to shut down ${r.label ?? label}: ${r.reason}`)));
645
+ report(chalk.dim(phaseLine("", remedy)));
643
646
  return {
644
647
  status: "failed",
645
- label,
646
- reason: r.reason
648
+ label: r.label ?? label,
649
+ reason: r.reason,
650
+ remedy
647
651
  };
648
652
  }
649
653
  function summarize(root, outcomes, ok) {
@@ -1,3 +1,3 @@
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";
1
+ import { t as resolveSupervisorTarget } from "./ownership-DZjdx8Gq.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-Dj4pfmyv.mjs";
3
3
  export { clearCollectorState, clearSupervisorState, stopCommand as default, readCollectorState, readSupervisorState, resolveCollectorTargets, resolveSupervisorTarget, runStop };
@@ -1,4 +1,10 @@
1
- import { t as NdjsonWriter } from "./ndjson-DvHSsNU9.mjs";
1
+ import { t as NdjsonWriter } from "./ndjson-Du3j0AQ9.mjs";
2
+ import "@stim-cli/core/state";
3
+ //#region src/supervisor/idle-stop.d.ts
4
+ interface IdleProbe {
5
+ lastActivityAt(): number;
6
+ blocker(): string | null;
7
+ }
2
8
  import * as import__stim_cli_core_process_identity from "@stim-cli/core/process-identity";
3
9
  //#endregion
4
10
  //#region src/supervisor/state.d.ts
@@ -15,6 +21,7 @@ interface ParsedSupervisorArgs {
15
21
  port?: number;
16
22
  tunnel?: boolean;
17
23
  resetCache?: boolean;
24
+ idleStopMinutes?: number;
18
25
  error?: string;
19
26
  }
20
27
  declare function parseArgs(argv: string[]): ParsedSupervisorArgs;
@@ -43,6 +50,8 @@ interface RunSupervisorOptions {
43
50
  port: number;
44
51
  tunnel?: boolean;
45
52
  resetCache?: boolean;
53
+ idleStopMinutes?: number;
54
+ idleProbe?: IdleProbe;
46
55
  isExpo?: (projectRoot: string) => boolean;
47
56
  startBare?: ServerStarter | null;
48
57
  startExpo?: ServerStarter | null;
@@ -51,7 +60,7 @@ interface RunSupervisorOptions {
51
60
  attachSignals?: boolean;
52
61
  stderr?: (line: string) => void;
53
62
  }
54
- declare function runSupervisor({ root, port, tunnel, resetCache, isExpo, startBare, startExpo, now, onExit, attachSignals, stderr }: RunSupervisorOptions): Promise<{
63
+ declare function runSupervisor({ root, port, tunnel, resetCache, idleStopMinutes, idleProbe, isExpo, startBare, startExpo, now, onExit, attachSignals, stderr }: RunSupervisorOptions): Promise<{
55
64
  mode: string;
56
65
  server: ServerHandle | undefined;
57
66
  shutdown: (code: number, event: string, msg: string) => Promise<void>;
@@ -1,20 +1,139 @@
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";
1
+ import { B as parseDeviceSlotKey, C as setSupervisor, D as process_identity_exports, H as projectDeviceSlots, a as clearSupervisor, f as getProject } from "./config-BEq0Qw_a.mjs";
2
+ import { r as createNdjsonWriter } from "./ndjson-CDY9IkVC.mjs";
3
+ import { _ as withWorkspaceStateLock, j as workspaceLogsDir, m as readWorkspaceState$1, p as clearWorkspaceStateKeys, y as writeWorkspaceState } from "./metro-BWLOJ_Wr.mjs";
4
+ import { a as detectIsExpo } from "./project-BXZzOABt.mjs";
5
5
  import { t as describeError } from "./errors-B6W7FkxQ.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
- import { existsSync, realpathSync } from "node:fs";
6
+ import { t as relaunchWithLogFile } from "./detached-entry-DJHoca49.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-WGdIbi_d.mjs";
8
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BXA96myD.mjs";
9
+ import { a as parseLease, r as leaseIsExpired, s as parseWorkspaceLeases, t as deviceLeasePath } from "./device-lease-CbEaIk7b.mjs";
10
+ import { c as createActivityReader } from "./build-progress-BXx-ZBe4.mjs";
11
+ import { N as ownedAvdSerialResolver } from "./android-787HhP5w.mjs";
12
+ import { s as workspaceBuildInProgress } from "./idle-DpRLp5lj.mjs";
13
+ import { n as withIdleWorkspace } from "./in-use-CjUluqIX.mjs";
14
+ import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
9
15
  import { LOG_ROTATE_BYTES } from "@stim-cli/core";
10
- import { isAbsolute, join, resolve } from "node:path";
16
+ import { IDLE_STOP_KEY } from "@stim-cli/core/state";
17
+ import { dirname, isAbsolute, join, resolve } from "node:path";
11
18
  import { fileURLToPath } from "node:url";
19
+ //#region src/supervisor/idle-stop.ts
20
+ const IDLE_CHECK_MS = 6e4;
21
+ const MINUTE_MS = 6e4;
22
+ function trackDevServerActivity(now) {
23
+ let last = now();
24
+ const open = /* @__PURE__ */ new Set();
25
+ return {
26
+ record(entry) {
27
+ const { event, requestId } = entry ?? {};
28
+ if (event === "bundle_response_started") {
29
+ if (typeof requestId === "string") open.add(requestId);
30
+ } else if (event === "bundle_response_finished" || event === "bundle_response_failed") {
31
+ if (typeof requestId === "string") open.delete(requestId);
32
+ } else if (event !== "expo_stdout") return;
33
+ last = now();
34
+ },
35
+ lastActivityAt: () => open.size > 0 ? now() : last
36
+ };
37
+ }
38
+ function deviceTargets(root) {
39
+ const serialOf = ownedAvdSerialResolver({ timeoutMs: 5e3 });
40
+ return projectDeviceSlots(getProject(root)).flatMap(({ slot, platforms }) => {
41
+ const targets = [];
42
+ if (platforms.ios?.deviceUdid) targets.push({
43
+ platform: "ios",
44
+ id: platforms.ios.deviceUdid,
45
+ slot
46
+ });
47
+ const android = platforms.android;
48
+ const serial = android?.avdName ? serialOf(android.avdName).serial : android?.serial;
49
+ if (serial) targets.push({
50
+ platform: "android",
51
+ id: serial,
52
+ slot
53
+ });
54
+ return targets;
55
+ });
56
+ }
57
+ function heldDeviceLease(root, now) {
58
+ for (const [key, record] of Object.entries(parseWorkspaceLeases(readWorkspaceState$1(root)?.deviceLeases))) {
59
+ const platform = parseDeviceSlotKey(key)?.platform;
60
+ if (!platform) continue;
61
+ let raw;
62
+ try {
63
+ raw = readFileSync(deviceLeasePath(platform, record.id), "utf8");
64
+ } catch (error) {
65
+ if (error.code === "ENOENT") continue;
66
+ return `${platform} device ${record.id} has a lease that cannot be read (${describeError(error)})`;
67
+ }
68
+ const lease = parseLease(raw);
69
+ if (!lease) return `${platform} device ${record.id} has a lease that cannot be read`;
70
+ if (lease.token !== record.token || leaseIsExpired(lease, now)) continue;
71
+ const by = record.kind === "declared" ? "stim device lock" : "a stim ios or android run";
72
+ return `${platform} device ${record.id} is leased by ${by} until ${lease.expiresAt}`;
73
+ }
74
+ return null;
75
+ }
76
+ function workspaceIdleProbe(root, { platform = process.platform } = {}) {
77
+ return {
78
+ lastActivityAt() {
79
+ const used = Date.parse(String(readWorkspaceState$1(root)?.lastUsedAt ?? ""));
80
+ let clientLog = NaN;
81
+ try {
82
+ clientLog = statSync(join(workspaceLogsDir(root), "client.ndjson")).mtimeMs;
83
+ } catch {}
84
+ const times = [used, clientLog].filter(Number.isFinite);
85
+ return times.length ? Math.max(...times) : NaN;
86
+ },
87
+ blocker() {
88
+ if (workspaceBuildInProgress(root)) return "a build is in progress";
89
+ const lease = heldDeviceLease(root, Date.now());
90
+ if (lease) return lease;
91
+ const readActivity = createActivityReader();
92
+ for (const target of deviceTargets(root)) {
93
+ const activity = readActivity({
94
+ ...target,
95
+ workspace: root
96
+ });
97
+ if (activity.state === "driven") return `${target.platform} device ${target.id} is driven by ${activity.driver?.tool ?? "an unknown tool"}`;
98
+ const unknown = platform === "win32" ? activity.basis.filter((basis) => basis !== "driver-process") : activity.basis;
99
+ if (activity.state === "unknown" && unknown.length > 0) return `${target.platform} device ${target.id} has unknown activity (${unknown.join(", ")})`;
100
+ }
101
+ return null;
102
+ }
103
+ };
104
+ }
105
+ function watchIdleDevServer({ idleStopMs, now, serverActivityAt, probe, onIdle, checkMs = IDLE_CHECK_MS }) {
106
+ const idleMinutes = () => {
107
+ const last = Math.max(serverActivityAt(), ...[probe.lastActivityAt()].filter(Number.isFinite));
108
+ if (now() - last < idleStopMs) return null;
109
+ let blocker;
110
+ try {
111
+ blocker = probe.blocker();
112
+ } catch (error) {
113
+ blocker = describeError(error);
114
+ }
115
+ return blocker ? null : Math.floor((now() - last) / MINUTE_MS);
116
+ };
117
+ let deciding = false;
118
+ const timer = setInterval(() => {
119
+ if (deciding || idleMinutes() === null) return;
120
+ deciding = true;
121
+ onIdle(idleMinutes).finally(() => {
122
+ deciding = false;
123
+ });
124
+ }, Math.min(checkMs, idleStopMs));
125
+ timer.unref?.();
126
+ return () => clearInterval(timer);
127
+ }
128
+ //#endregion
12
129
  //#region src/supervisor/run.ts
130
+ const USAGE = "Usage: run.js --root <path> --port <n> [--tunnel] [--reset-cache] [--idle-stop-minutes <n>]";
13
131
  function parseArgs(argv) {
14
132
  let root;
15
133
  let port;
16
134
  let tunnel = false;
17
135
  let resetCache = false;
136
+ let idleStopMinutes = "0";
18
137
  for (let i = 0; i < argv.length; i++) {
19
138
  const arg = argv[i];
20
139
  if (arg === "--root") {
@@ -33,20 +152,26 @@ function parseArgs(argv) {
33
152
  resetCache = true;
34
153
  continue;
35
154
  }
36
- return { error: `Unknown supervisor argument "${arg}". Usage: run.js --root <path> --port <n> [--tunnel] [--reset-cache]` };
155
+ if (arg === "--idle-stop-minutes") {
156
+ idleStopMinutes = argv[++i] ?? "";
157
+ continue;
158
+ }
159
+ return { error: `Unknown supervisor argument "${arg}". ${USAGE}` };
37
160
  }
38
- if (!root) return { error: "Missing --root. Usage: run.js --root <path> --port <n> [--tunnel] [--reset-cache]" };
161
+ if (!root) return { error: `Missing --root. ${USAGE}` };
39
162
  if (!isAbsolute(root)) return { error: `--root must be an absolute path, got "${root}".` };
40
163
  const parsedPort = Number(port);
41
164
  if (!Number.isInteger(parsedPort) || parsedPort <= 0 || parsedPort > 65535) return { error: `--port must be a TCP port number, got "${port}".` };
165
+ if (!/^\d+$/.test(idleStopMinutes)) return { error: `--idle-stop-minutes must be a whole number of minutes, got "${idleStopMinutes}".` };
42
166
  return {
43
167
  root: resolve(root),
44
168
  port: parsedPort,
45
169
  tunnel,
46
- resetCache
170
+ resetCache,
171
+ idleStopMinutes: Number(idleStopMinutes)
47
172
  };
48
173
  }
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) }) {
174
+ async function runSupervisor({ root, port, tunnel = false, resetCache = false, idleStopMinutes = 0, idleProbe, isExpo = detectIsExpo, startBare = null, startExpo = null, now = Date.now, onExit = (code) => process.exit(code), attachSignals = true, stderr = (line) => console.error(line) }) {
50
175
  root = realpathSync(root);
51
176
  const logsDir = workspaceLogsDir(root);
52
177
  const writer = createNdjsonWriter(join(logsDir, "metro.ndjson"), { maxBytes: LOG_ROTATE_BYTES });
@@ -69,6 +194,7 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
69
194
  clearExpoMetroTunnel(root);
70
195
  writePidFile(root, process.pid);
71
196
  writeWorkspaceState(root, { supervisor: record });
197
+ clearWorkspaceStateKeys(root, [IDLE_STOP_KEY]);
72
198
  try {
73
199
  setSupervisor(root, record);
74
200
  } catch (err) {
@@ -87,7 +213,9 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
87
213
  msg: `supervisor pid ${process.pid} starting the ${mode} dev server on port ${port} for ${root}`
88
214
  });
89
215
  let stopping = false;
216
+ let stopWatchingIdle = null;
90
217
  const finish = (code, event, level, msg) => {
218
+ stopWatchingIdle?.();
91
219
  writer.write({
92
220
  src: "metro",
93
221
  level,
@@ -108,6 +236,7 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
108
236
  const shutdown = async (code, event, msg) => {
109
237
  if (stopping || !server) return;
110
238
  stopping = true;
239
+ stopWatchingIdle?.();
111
240
  try {
112
241
  await server.close();
113
242
  } catch (err) {
@@ -129,18 +258,23 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
129
258
  stopping = true;
130
259
  finish(signal === "SIGINT" ? 130 : 143, "supervisor_stopped", "warn", `received ${signal} before the ${mode} dev server started; stopping`);
131
260
  });
261
+ const serverActivity = trackDevServerActivity(now);
262
+ const serverWriter = Object.assign(Object.create(writer), { write(entry) {
263
+ serverActivity.record(entry);
264
+ return writer.write(entry);
265
+ } });
132
266
  try {
133
- server = await (mode === "expo-child" ? startExpo || (await import("./server-expo-CeILEu86.mjs")).startExpoServer : startBare || (await import("./server-bare-DWkkcIxV.mjs")).startBareServer)({
267
+ server = await (mode === "expo-child" ? startExpo || (await import("./server-expo-DBVTdult.mjs")).startExpoServer : startBare || (await import("./server-bare-DVbb7kXH.mjs")).startBareServer)({
134
268
  root,
135
269
  port,
136
270
  logsDir,
137
- writer,
271
+ writer: serverWriter,
138
272
  tunnel,
139
273
  resetCache,
140
274
  onTunnelUrl: (url) => {
141
275
  try {
142
276
  withWorkspaceStateLock(root, () => {
143
- if (readWorkspaceState(root)?.supervisor?.processToken === processToken) writeWorkspaceState(root, { metroTunnel: {
277
+ if (readWorkspaceState$1(root)?.supervisor?.processToken === processToken) writeWorkspaceState(root, { metroTunnel: {
144
278
  kind: "expo",
145
279
  url
146
280
  } });
@@ -172,7 +306,7 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
172
306
  if (serverPid) {
173
307
  const serverProcessToken = (0, process_identity_exports.captureProcessToken)(serverPid);
174
308
  withWorkspaceStateLock(root, () => {
175
- if (readWorkspaceState(root)?.supervisor?.processToken === processToken) writeWorkspaceState(root, { supervisor: {
309
+ if (readWorkspaceState$1(root)?.supervisor?.processToken === processToken) writeWorkspaceState(root, { supervisor: {
176
310
  ...record,
177
311
  serverPid,
178
312
  ...serverProcessToken ? { serverProcessToken } : {}
@@ -185,6 +319,39 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
185
319
  event: "server_started",
186
320
  msg: `${mode} dev server listening on port ${port}`
187
321
  });
322
+ if (idleStopMinutes > 0) stopWatchingIdle = watchIdleDevServer({
323
+ idleStopMs: idleStopMinutes * 6e4,
324
+ now,
325
+ serverActivityAt: serverActivity.lastActivityAt,
326
+ probe: idleProbe ?? workspaceIdleProbe(root),
327
+ onIdle: async (idleMinutesNow) => {
328
+ const stopIfStillIdle = async () => {
329
+ const idleMinutes = idleMinutesNow();
330
+ if (stopping || idleMinutes === null) return;
331
+ withWorkspaceStateLock(root, () => {
332
+ if (readWorkspaceState$1(root)?.supervisor?.processToken === processToken) writeWorkspaceState(root, { [IDLE_STOP_KEY]: {
333
+ reason: "idle",
334
+ at: new Date(now()).toISOString(),
335
+ idleMinutes
336
+ } });
337
+ });
338
+ await shutdown(0, "supervisor_idle_stopped", `no bundle request, client log or Stim command for ${idleMinutes} minutes (metro.idleStopMinutes is ${idleStopMinutes}); stopped the ${mode} dev server`);
339
+ };
340
+ try {
341
+ await withIdleWorkspace(root, () => withWorkspaceProcessLock(dirname(logsDir), "metro-start", stopIfStillIdle, {
342
+ external: true,
343
+ waitMs: 0,
344
+ ownerPurpose: "idle stop"
345
+ }), {
346
+ purpose: "idle stop",
347
+ supervisor: false,
348
+ managedLocks: false
349
+ });
350
+ } catch (err) {
351
+ stderr(`Stim supervisor: could not stop the idle dev server: ${describeError(err)}`);
352
+ }
353
+ }
354
+ });
188
355
  server.onExit?.((info) => {
189
356
  if (stopping) return;
190
357
  const detail = info?.signal ? `signal ${info.signal}` : `exit code ${info?.code ?? "unknown"}`;
@@ -219,7 +386,8 @@ async function main(argv = process.argv.slice(2)) {
219
386
  root,
220
387
  port: parsed.port,
221
388
  tunnel: parsed.tunnel ?? false,
222
- resetCache: parsed.resetCache ?? false
389
+ resetCache: parsed.resetCache ?? false,
390
+ idleStopMinutes: parsed.idleStopMinutes ?? 0
223
391
  });
224
392
  }
225
393
  function invokedDirectly() {
@@ -1,5 +1,5 @@
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";
1
+ import { k as ownership_claim_exports } from "./config-BEq0Qw_a.mjs";
2
+ import { r as stopDeclaringSpawnsOn, t as declareSpawnsOn } from "./spawn-claims-CUleC2cM.mjs";
3
3
  import { join } from "node:path";
4
4
  //#region src/engine/workspace-process-lock.ts
5
5
  const DEFAULT_WAIT_MS = 6e4;