stim 1.13.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/README.md +2 -0
  2. package/dist/{activity-DfVKZTEB.mjs → activity-B2-kb7k0.mjs} +281 -41
  3. package/dist/{android-BHfAu2mf.mjs → android-CYmq0jNi.mjs} +2 -2
  4. package/dist/{android-CD8pr8Q2.mjs → android-Cu_L4RpL.mjs} +524 -412
  5. package/dist/{android-qx_31wJo.mjs → android-Db2eEMaf.mjs} +152 -65
  6. package/dist/android-cas-PnKfAdB2.mjs +167 -0
  7. package/dist/android-cas-compiler.mjs +2 -2
  8. package/dist/{app-install-CDFY2nUL.mjs → app-install-CAAplXAt.mjs} +162 -173
  9. package/dist/{budget-E5o91NR1.mjs → budget-CMiqghig.mjs} +1048 -77
  10. package/dist/{build-plan-BTa93o_r.mjs → build-plan-aeGMFX7e.mjs} +193 -650
  11. package/dist/build-progress-C2Jv1AtA.mjs +872 -0
  12. package/dist/build-slots-Df1ApSuH.mjs +127 -0
  13. package/dist/{cache-manifest-DWPF4RKJ.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
  14. package/dist/cache-manifest.mjs +1 -1
  15. package/dist/{chrome-B5hWgQJS.mjs → chrome-BrcVWQyD.mjs} +4 -4
  16. package/dist/cli-0cX5s4NM.mjs +45 -0
  17. package/dist/cli.mjs +6 -42
  18. package/dist/client-C2IS36Qf.mjs +1333 -0
  19. package/dist/collector-run.mjs +8 -7
  20. package/dist/{command-output-BIL7rlX7.mjs → command-output-CliHHn7a.mjs} +1 -1
  21. package/dist/{config-0jCcMM0E.mjs → config-D0DbpZtM.mjs} +29 -29
  22. package/dist/{created-devices-Cd558h31.mjs → created-devices-BwVb3liH.mjs} +2 -2
  23. package/dist/{dependency-state-DNN0yfx6.mjs → dependency-state-BmsjKZ2H.mjs} +2 -2
  24. package/dist/{deps-2Wo81np9.mjs → deps-BFlKbOIp.mjs} +3 -3
  25. package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
  26. package/dist/dev-client-CjzLoNs0.mjs +80 -0
  27. package/dist/{device-Cf7vl-so.mjs → device-BZ1IyS5A.mjs} +7 -7
  28. package/dist/device-capacity-DMuKC7nx.mjs +136 -0
  29. package/dist/device-host-worker.d.mts +1 -0
  30. package/dist/device-host-worker.mjs +769 -0
  31. package/dist/device-ios-DMQBUPus.mjs +381 -0
  32. package/dist/{device-lease-CL9dZf73.mjs → device-lease-W5Pi1q38.mjs} +43 -36
  33. package/dist/{device-pool-MuoiyRcu.mjs → device-pool-B2r38YjS.mjs} +4 -4
  34. package/dist/{device-remote-DT9ngZiD.mjs → device-remote-9ulPsdhY.mjs} +68 -162
  35. package/dist/{doctor-D2iysNNH.mjs → doctor-BcE5xsBn.mjs} +191 -99
  36. package/dist/{error-diagnostics-BoiOhck8.mjs → error-diagnostics-Cb0P1WLS.mjs} +12 -11
  37. package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
  38. package/dist/{gc-eA-PRBX8.mjs → gc-BuL1Genr.mjs} +430 -102
  39. package/dist/{android-cas-B9Kv9aIy.mjs → gradle-CmdOBq1d.mjs} +8 -294
  40. package/dist/{guide-BfZ0OmiP.mjs → guide-cCSGRFd3.mjs} +2052 -240
  41. package/dist/{guide-status-BaMYu55M.mjs → guide-status-CIoWK6mG.mjs} +3 -3
  42. package/dist/hosted-macos-DwowNU7W.mjs +447 -0
  43. package/dist/idle-shutdown-B5LPaNyY.mjs +122 -0
  44. package/dist/in-use-CndPMilN.mjs +110 -0
  45. package/dist/{ios-CmEzZiOj.mjs → ios-3CtCRhwr.mjs} +201 -10
  46. package/dist/{ios-CcfCeZ19.mjs → ios-Chob8ny7.mjs} +560 -259
  47. package/dist/{ios-device-BW-t_Lkv.mjs → ios-device-DERmMmk-.mjs} +12 -7
  48. package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
  49. package/dist/{launch-verify-CuJaBpEa.mjs → launch-verify-CLlofxRt.mjs} +42 -11
  50. package/dist/{logs-DcSRbC2t.mjs → logs-h9h6MUvE.mjs} +95 -13
  51. package/dist/machine-usage-D_YYMzfR.mjs +283 -0
  52. package/dist/machines-CYtOCqcj.mjs +259 -0
  53. package/dist/macos-BwtbUtTN.mjs +468 -0
  54. package/dist/macos-run.d.mts +5 -0
  55. package/dist/macos-run.mjs +2 -0
  56. package/dist/{merge-state-ytFQCeh4.mjs → merge-state-BnxLNQiM.mjs} +22 -12
  57. package/dist/{metro-BPeY1YMh.mjs → metro-Di8EN2Tb.mjs} +20 -120
  58. package/dist/{named-ports-n7tnJsI8.mjs → named-ports-Dj_XBNaL.mjs} +8 -64
  59. package/dist/native-run-DFDoIRbY.mjs +147 -0
  60. package/dist/{native-runtime-CiiFHt0e.mjs → native-runtime-i6OkVg-w.mjs} +11 -6
  61. package/dist/offload-worker.d.mts +121 -0
  62. package/dist/offload-worker.mjs +601 -0
  63. package/dist/{ownership-Bz2kETAS.mjs → ownership-BkYxUtuj.mjs} +3 -2
  64. package/dist/{page-BTu7I60K.mjs → page-QF_Ke10s.mjs} +7 -2
  65. package/dist/{ports-C5UutqpJ.mjs → ports-DddwttKM.mjs} +3 -3
  66. package/dist/prebuild-BvxDlD6C.mjs +213 -0
  67. package/dist/process-identity-BmRm1qV0.mjs +12 -0
  68. package/dist/{project-DGeclhvO.mjs → project-CV3tDFHl.mjs} +9 -18
  69. package/dist/pull-request-pxnDCQzn.mjs +322 -0
  70. package/dist/pull-requests.d.mts +120 -0
  71. package/dist/pull-requests.mjs +2 -0
  72. package/dist/recordings-GEXp4iBT.mjs +37 -0
  73. package/dist/{reload-C-cV5yy_.mjs → reload-ChPiwvot.mjs} +70 -22
  74. package/dist/{remote-cache--aehr4Pr.mjs → remote-cache-CO2elm_b.mjs} +4 -4
  75. package/dist/run-B-xqRrsg.mjs +119 -0
  76. package/dist/{server-bare-Bq8BpSIo.mjs → server-bare-X7PUgizL.mjs} +60 -13
  77. package/dist/{server-expo-DUCg52-K.mjs → server-expo-DhtX6bom.mjs} +6 -10
  78. package/dist/server-expo-KJeD109I.mjs +2 -0
  79. package/dist/{settings-xB0Frkg6.mjs → settings-CduVcoxj.mjs} +77 -26
  80. package/dist/{settings-ssVBToDR.mjs → settings-DbL6uhH8.mjs} +16 -10
  81. package/dist/settings.schema.json +357 -2
  82. package/dist/{simslim-Bbg9UWrt.mjs → simslim-Ciluxlq4.mjs} +8 -132
  83. package/dist/slot-launch-BY0BrOLh.mjs +65 -0
  84. package/dist/{spawn-claims-PT8aQpCa.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
  85. package/dist/spawn-entry-DncpJvWL.mjs +16 -0
  86. package/dist/stage-CzaiwFsI.mjs +143 -0
  87. package/dist/start-CPO4wRg7.mjs +2 -0
  88. package/dist/{start-BZhItkiL.mjs → start-CUD9nasA.mjs} +108 -61
  89. package/dist/state-B4PQFXMW.d.mts +12 -0
  90. package/dist/state-BxCgeTQu.mjs +41 -0
  91. package/dist/{state-DwAU2_-D.mjs → state-CaeaHbDA.mjs} +7 -4
  92. package/dist/{state-l1pYwOre.mjs → state-DFm77wRn.mjs} +67 -5
  93. package/dist/stats-DBLLFmKd.mjs +66 -0
  94. package/dist/{status-TJlL0HWo.mjs → status-DYXxNOT0.mjs} +425 -260
  95. package/dist/{status-sszP4XKU.mjs → status-l84Gbvp2.mjs} +122 -20
  96. package/dist/stdout-reader-C8yjre1x.mjs +23 -0
  97. package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
  98. package/dist/stim-footprint.swift +32 -0
  99. package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
  100. package/dist/{stop-BZ2qMiBP.mjs → stop-Bad61z_2.mjs} +128 -19
  101. package/dist/stop-Bvf1MflS.mjs +48 -0
  102. package/dist/{stop-DskDKNTU.mjs → stop-CceYh1yG.mjs} +2 -2
  103. package/dist/stop-cause-CZ_Mr-v9.mjs +665 -0
  104. package/dist/supervisor-run.d.mts +44 -12
  105. package/dist/supervisor-run.mjs +276 -33
  106. package/dist/support-CDQbDkL5.mjs +315 -0
  107. package/dist/support-WLOzVKUv.mjs +176 -0
  108. package/dist/teardown-BYuQafiA.mjs +718 -0
  109. package/dist/{xcode-LM6A8cNZ.mjs → toolchain-DmeYjXD9.mjs} +363 -13
  110. package/dist/warm-progress-DVHLN4uC.mjs +463 -0
  111. package/dist/{idle-DxLi5OkN.mjs → watchman-Bx0wceoF.mjs} +55 -8
  112. package/dist/{web-sWXd5iek.mjs → web-k6w8-HnE.mjs} +39 -22
  113. package/dist/web-run.mjs +326 -13
  114. package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
  115. package/dist/workspace-state-CL2V2Ons.mjs +371 -0
  116. package/dist/worktree-BT_uULFx.mjs +2 -0
  117. package/dist/{worktree-DeBQSVtR.mjs → worktree-C0c5Udwa.mjs} +55 -54
  118. package/dist/{worktree-D4Do_XiQ.mjs → worktree-qXKzADSC.mjs} +161 -255
  119. package/package.json +10 -5
  120. package/shim/bundle-response.cjs +55 -10
  121. package/shim/bundle-response.d.cts +1 -0
  122. package/shim/expo-metro-config.cjs +10 -2
  123. package/dist/build-progress-l7d-X-ZP.mjs +0 -247
  124. package/dist/in-use-wYEaKQM9.mjs +0 -249
  125. package/dist/ownership-PUP4Dwai.mjs +0 -1138
  126. package/dist/server-expo-BeYwP_Xy.mjs +0 -2
  127. package/dist/slot-launch-BIWEW-wI.mjs +0 -19
  128. package/dist/start-Rw2GD3nV.mjs +0 -2
  129. package/dist/stats-CaSwmsSZ.mjs +0 -62
  130. package/dist/stats-D8rUTPHd.mjs +0 -368
  131. package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
  132. package/dist/worktree-3cBa-bQt.mjs +0 -2
  133. package/dist/{metro-store-DzeWkfqW.mjs → metro-store-CDDZHtPl.mjs} +1 -1
@@ -1,19 +1,43 @@
1
1
  import { t as NdjsonWriter } from "./ndjson-Du3j0AQ9.mjs";
2
- import "@stim-cli/core/state";
2
+ import { a as readPidFile, i as clearWorkspaceSupervisor, n as MODE_EXPO, o as writePidFile, r as clearExpoMetroTunnel, t as MODE_BARE } from "./state-B4PQFXMW.mjs";
3
+ import { DevServerStopRecord, DeviceActivity, IOS_SIMULATOR_APPS } from "@stim-cli/core/state";
3
4
  //#region src/supervisor/idle-stop.d.ts
4
5
  interface IdleProbe {
5
6
  lastActivityAt(): number;
6
7
  blocker(): string | null;
7
8
  }
8
- import * as import__stim_cli_core_process_identity from "@stim-cli/core/process-identity";
9
9
  //#endregion
10
- //#region src/supervisor/state.d.ts
11
- declare const MODE_BARE = "bare-inproc";
12
- declare const MODE_EXPO = "expo-child";
13
- declare function clearWorkspaceSupervisor(root: string, expected?: process_identity_d_exports.ProcessRecord | null): boolean;
14
- declare function clearExpoMetroTunnel(root: string): void;
15
- declare function writePidFile(root: string, pid: number): string;
16
- declare function readPidFile(root: string): number | null;
10
+ //#region src/commands/gc/idle.d.ts
11
+ interface OwnedDeviceActivity {
12
+ kind: "ios" | "android";
13
+ id: string;
14
+ name: string;
15
+ project: string;
16
+ slot: string;
17
+ activity: DeviceActivity;
18
+ }
19
+ //#endregion
20
+ //#region src/devices/idle-shutdown.d.ts
21
+ interface DueDevice {
22
+ device: OwnedDeviceActivity;
23
+ idleForMs: number;
24
+ }
25
+ /** The workspace's booted owned simulators and emulators that have been idle for `idleMs` or more. */
26
+ declare function dueIdleDevices(root: string, idleMs: number, now?: number): DueDevice[];
27
+ interface IdleShutdownEvent {
28
+ level: "info" | "warn";
29
+ event: "device_idle_shutdown" | "device_idle_shutdown_failed";
30
+ msg: string;
31
+ }
32
+ /**
33
+ * Shuts down, through centralized teardown, each of the workspace's owned devices that is still idle for `idleMs`,
34
+ * and records why. The caller holds the workspace's native-run lock, so no `ios` or `android` run is booting or
35
+ * installing on them.
36
+ */
37
+ declare function shutDownIdleDevices(root: string, idleMs: number, log: (event: IdleShutdownEvent) => void, now?: number): number;
38
+ //#endregion
39
+ //#region src/watchman.d.ts
40
+ type WatchmanCommand = (args: string[], timeoutMs: number) => Promise<unknown>;
17
41
  //#endregion
18
42
  //#region src/supervisor/run.d.ts
19
43
  interface ParsedSupervisorArgs {
@@ -22,6 +46,7 @@ interface ParsedSupervisorArgs {
22
46
  tunnel?: boolean;
23
47
  resetCache?: boolean;
24
48
  idleStopMinutes?: number;
49
+ deviceIdleMinutes?: number;
25
50
  error?: string;
26
51
  }
27
52
  declare function parseArgs(argv: string[]): ParsedSupervisorArgs;
@@ -45,6 +70,10 @@ type ServerStarter = (opts: {
45
70
  resetCache?: boolean;
46
71
  onTunnelUrl?: ((url: string) => void) | null;
47
72
  }) => Promise<ServerHandle>;
73
+ interface DeviceIdleOps {
74
+ due: typeof dueIdleDevices;
75
+ shutDown: typeof shutDownIdleDevices;
76
+ }
48
77
  interface RunSupervisorOptions {
49
78
  root: string;
50
79
  port: number;
@@ -52,6 +81,8 @@ interface RunSupervisorOptions {
52
81
  resetCache?: boolean;
53
82
  idleStopMinutes?: number;
54
83
  idleProbe?: IdleProbe;
84
+ deviceIdleMinutes?: number;
85
+ deviceIdle?: DeviceIdleOps;
55
86
  isExpo?: (projectRoot: string) => boolean;
56
87
  startBare?: ServerStarter | null;
57
88
  startExpo?: ServerStarter | null;
@@ -59,13 +90,14 @@ interface RunSupervisorOptions {
59
90
  onExit?: (code: number) => void;
60
91
  attachSignals?: boolean;
61
92
  stderr?: (line: string) => void;
93
+ watchman?: WatchmanCommand;
62
94
  }
63
- declare function runSupervisor({ root, port, tunnel, resetCache, idleStopMinutes, idleProbe, isExpo, startBare, startExpo, now, onExit, attachSignals, stderr }: RunSupervisorOptions): Promise<{
95
+ declare function runSupervisor({ root, port, tunnel, resetCache, idleStopMinutes, idleProbe, deviceIdleMinutes, deviceIdle, isExpo, startBare, startExpo, now, onExit, attachSignals, stderr, watchman }: RunSupervisorOptions): Promise<{
64
96
  mode: string;
65
97
  server: ServerHandle | undefined;
66
- shutdown: (code: number, event: string, msg: string) => Promise<void>;
98
+ shutdown: (code: number, event: string, msg: string, stop?: DevServerStopRecord) => Promise<void>;
67
99
  startedAt: string;
68
100
  } | null>;
69
101
  declare function main(argv?: string[]): Promise<void>;
70
102
  //#endregion
71
- export { MODE_BARE, MODE_EXPO, RunSupervisorOptions, ServerExitInfo, ServerHandle, clearExpoMetroTunnel, clearWorkspaceSupervisor, main, parseArgs, readPidFile, runSupervisor, writePidFile };
103
+ export { DeviceIdleOps, MODE_BARE, MODE_EXPO, RunSupervisorOptions, ServerExitInfo, ServerHandle, clearExpoMetroTunnel, clearWorkspaceSupervisor, main, parseArgs, readPidFile, runSupervisor, writePidFile };
@@ -1,20 +1,23 @@
1
- import { O as process_identity_exports, U as projectDeviceSlots, V as parseDeviceSlotKey, o as clearSupervisor, p as getProject, w as setSupervisor } from "./config-0jCcMM0E.mjs";
1
+ import { B as parseDeviceSlotKey, H as projectDeviceSlots, T as setSupervisor, o as clearSupervisor, p as getProject } from "./config-D0DbpZtM.mjs";
2
+ import { t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
2
3
  import { r as createNdjsonWriter } from "./ndjson-CDY9IkVC.mjs";
3
- import { M as workspaceLogsDir, b as writeWorkspaceState, h as readWorkspaceState$1, m as clearWorkspaceStateKeys, v as withWorkspaceStateLock } from "./metro-BPeY1YMh.mjs";
4
- import { r as detectIsExpo } from "./project-DGeclhvO.mjs";
4
+ import { C as workspaceLogsDir, c as writeWorkspaceState, n as clearWorkspaceStateKeys, o as withWorkspaceStateLock, r as readWorkspaceState$1 } from "./workspace-state-CL2V2Ons.mjs";
5
+ import { r as detectIsExpo } from "./project-CV3tDFHl.mjs";
5
6
  import { t as describeError } from "./errors-B6W7FkxQ.mjs";
6
- import { t as relaunchWithLogFile } from "./detached-entry-DJHoca49.mjs";
7
- import { l as readPidFile, n as MODE_EXPO, p as writePidFile, r as clearExpoMetroTunnel, s as clearWorkspaceSupervisor, t as MODE_BARE } from "./state-DwAU2_-D.mjs";
8
- import { a as withWorkspaceProcessLock } from "./named-ports-n7tnJsI8.mjs";
9
- import { a as parseLease, r as leaseIsExpired, s as parseWorkspaceLeases, t as deviceLeasePath } from "./device-lease-CL9dZf73.mjs";
10
- import { n as createActivityReader } from "./activity-DfVKZTEB.mjs";
11
- import { R as ownedAvdSerialResolver } from "./android-qx_31wJo.mjs";
12
- import { s as workspaceBuildInProgress } from "./idle-DxLi5OkN.mjs";
13
- import { n as withIdleWorkspace } from "./in-use-wYEaKQM9.mjs";
7
+ import { t as relaunchWithLogFile } from "./detached-entry-CiTEgMR1.mjs";
8
+ import { l as readPidFile, n as MODE_EXPO, p as writePidFile, r as clearExpoMetroTunnel, s as clearWorkspaceSupervisor, t as MODE_BARE } from "./state-CaeaHbDA.mjs";
9
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BL7Q0HCO.mjs";
10
+ import { n as createActivityReader } from "./activity-B2-kb7k0.mjs";
11
+ import { a as parseLease, r as leaseIsExpired, s as parseWorkspaceLeases, t as deviceLeasePath } from "./device-lease-W5Pi1q38.mjs";
12
+ import { z as ownedAvdSerialResolver } from "./android-Db2eEMaf.mjs";
13
+ import { n as parseTriggerCount, o as runWatchman, p as workspaceBuildInProgress, r as parseWatchRoots, t as parseSubscriberNames } from "./watchman-Bx0wceoF.mjs";
14
+ import { n as withIdleWorkspace } from "./in-use-CndPMilN.mjs";
15
+ import { n as dueIdleDevices, r as shutDownIdleDevices } from "./idle-shutdown-B5LPaNyY.mjs";
16
+ import { c as vanishedSupervisorMessage, i as devServerStopRecord, r as devServerStopLevel, t as describeDevServerStop } from "./stop-cause-CZ_Mr-v9.mjs";
14
17
  import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
18
+ import { dirname, isAbsolute, join, resolve, sep } from "node:path";
15
19
  import { LOG_ROTATE_BYTES } from "@stim-cli/core";
16
- import { IDLE_STOP_KEY } from "@stim-cli/core/state";
17
- import { dirname, isAbsolute, join, resolve } from "node:path";
20
+ import { DEV_SERVER_STOP_REQUEST_KEY, IDLE_STOP_KEY, readDevServerStopRequest } from "@stim-cli/core/state";
18
21
  import { fileURLToPath } from "node:url";
19
22
  //#region src/supervisor/idle-stop.ts
20
23
  const IDLE_CHECK_MS = 6e4;
@@ -125,15 +128,163 @@ function watchIdleDevServer({ idleStopMs, now, serverActivityAt, probe, onIdle,
125
128
  timer.unref?.();
126
129
  return () => clearInterval(timer);
127
130
  }
131
+ /**
132
+ * Checks each minute, once the workspace has gone `idleMs` without a Stim command, whether an owned device is due
133
+ * for idle shutdown, and runs `onDue` for it.
134
+ */
135
+ function watchIdleDevices({ idleMs, now, lastUseAt, hasDue, onDue, checkMs = IDLE_CHECK_MS }) {
136
+ let deciding = false;
137
+ const timer = setInterval(() => {
138
+ if (deciding || now() - lastUseAt() < idleMs) return;
139
+ let due;
140
+ try {
141
+ due = hasDue();
142
+ } catch {
143
+ return;
144
+ }
145
+ if (!due) return;
146
+ deciding = true;
147
+ onDue().finally(() => {
148
+ deciding = false;
149
+ });
150
+ }, Math.min(checkMs, idleMs));
151
+ timer.unref?.();
152
+ return () => clearInterval(timer);
153
+ }
154
+ //#endregion
155
+ //#region src/supervisor/watchman-roots.ts
156
+ const WATCHMAN_TIMEOUT_MS = 2e3;
157
+ const SHUTDOWN_BUDGET_MS = 3e3;
158
+ const PROBE_DELAYS_MS = [
159
+ 3e3,
160
+ 6e3,
161
+ 1e4,
162
+ 15e3,
163
+ 2e4,
164
+ 3e4,
165
+ 45e3,
166
+ 6e4,
167
+ 12e4,
168
+ 3e5
169
+ ];
170
+ function canonical(path) {
171
+ try {
172
+ return realpathSync(path);
173
+ } catch {
174
+ return null;
175
+ }
176
+ }
177
+ function newRootsContaining(before, current, workspaceRoot) {
178
+ const seen = new Set(before.map((root) => canonical(root) ?? root));
179
+ return current.filter((root) => {
180
+ const resolved = canonical(root);
181
+ if (resolved === null || seen.has(resolved)) return false;
182
+ return workspaceRoot === resolved || workspaceRoot.startsWith(resolved.endsWith(sep) ? resolved : resolved + sep);
183
+ });
184
+ }
185
+ function metroSubscriptionPrefix(pid) {
186
+ return `metro-file-map-${pid}-`;
187
+ }
188
+ async function watchRoots(watchman) {
189
+ try {
190
+ return parseWatchRoots(await watchman(["watch-list"]));
191
+ } catch {
192
+ return null;
193
+ }
194
+ }
195
+ async function subscriberNames(watchman, root) {
196
+ try {
197
+ return parseSubscriberNames(await watchman(["debug-get-subscriptions", root]));
198
+ } catch {
199
+ return null;
200
+ }
201
+ }
202
+ async function triggerCount(watchman, root) {
203
+ try {
204
+ return parseTriggerCount(await watchman(["trigger-list", root]));
205
+ } catch {
206
+ return null;
207
+ }
208
+ }
209
+ function trackMetroWatchRoots({ workspaceRoot, writer, watchman = runWatchman, probeDelaysMs = PROBE_DELAYS_MS }) {
210
+ let deadline = Infinity;
211
+ const call = async (args) => {
212
+ const left = deadline - Date.now();
213
+ if (left <= 0) throw new Error("the watchman cleanup ran out of time");
214
+ return watchman(args, Math.min(WATCHMAN_TIMEOUT_MS, left));
215
+ };
216
+ const before = watchRoots(call);
217
+ const owned = /* @__PURE__ */ new Set();
218
+ const timers = [];
219
+ let prefix = null;
220
+ const probe = async () => {
221
+ const earlier = await before;
222
+ if (prefix === null || earlier === null) return;
223
+ const current = await watchRoots(call);
224
+ if (current === null) return;
225
+ for (const root of newRootsContaining(earlier, current, workspaceRoot)) {
226
+ if (owned.has(root)) continue;
227
+ if ((await subscriberNames(call, root))?.some((name) => name.startsWith(prefix))) owned.add(root);
228
+ }
229
+ };
230
+ return {
231
+ started(metroPid) {
232
+ prefix = metroSubscriptionPrefix(metroPid);
233
+ for (const delay of probeDelaysMs) {
234
+ const timer = setTimeout(() => void probe(), delay);
235
+ timer.unref?.();
236
+ timers.push(timer);
237
+ }
238
+ },
239
+ async beforeClose() {
240
+ for (const timer of timers) clearTimeout(timer);
241
+ deadline = Date.now() + SHUTDOWN_BUDGET_MS;
242
+ await probe();
243
+ },
244
+ async afterClose() {
245
+ for (const root of owned) {
246
+ const others = (await subscriberNames(call, root))?.filter((name) => !name.startsWith(prefix));
247
+ const triggers = others?.length === 0 ? await triggerCount(call, root) : null;
248
+ const reason = !others ? "could not list its subscriptions" : others.length > 0 ? `${others.length} other subscription(s) still use it` : triggers === null ? "could not list its triggers" : triggers > 0 ? `it has ${triggers} trigger(s)` : null;
249
+ if (reason) {
250
+ writer.write({
251
+ src: "metro",
252
+ level: "debug",
253
+ event: "watchman_root_kept",
254
+ msg: `kept the watchman root ${root}: ${reason}`
255
+ });
256
+ continue;
257
+ }
258
+ try {
259
+ await call(["watch-del", root]);
260
+ writer.write({
261
+ src: "metro",
262
+ level: "info",
263
+ event: "watchman_root_removed",
264
+ msg: `removed the watchman root ${root} that this dev server registered`
265
+ });
266
+ } catch (err) {
267
+ writer.write({
268
+ src: "metro",
269
+ level: "warn",
270
+ event: "watchman_root_kept",
271
+ msg: `could not remove the watchman root ${root}: ${describeError(err)}`
272
+ });
273
+ }
274
+ }
275
+ }
276
+ };
277
+ }
128
278
  //#endregion
129
279
  //#region src/supervisor/run.ts
130
- const USAGE = "Usage: run.js --root <path> --port <n> [--tunnel] [--reset-cache] [--idle-stop-minutes <n>]";
280
+ const USAGE = "Usage: run.js --root <path> --port <n> [--tunnel] [--reset-cache] [--idle-stop-minutes <n>] [--device-idle-minutes <n>]";
131
281
  function parseArgs(argv) {
132
282
  let root;
133
283
  let port;
134
284
  let tunnel = false;
135
285
  let resetCache = false;
136
286
  let idleStopMinutes = "0";
287
+ let deviceIdleMinutes = "0";
137
288
  for (let i = 0; i < argv.length; i++) {
138
289
  const arg = argv[i];
139
290
  if (arg === "--root") {
@@ -156,22 +307,31 @@ function parseArgs(argv) {
156
307
  idleStopMinutes = argv[++i] ?? "";
157
308
  continue;
158
309
  }
310
+ if (arg === "--device-idle-minutes") {
311
+ deviceIdleMinutes = argv[++i] ?? "";
312
+ continue;
313
+ }
159
314
  return { error: `Unknown supervisor argument "${arg}". ${USAGE}` };
160
315
  }
161
316
  if (!root) return { error: `Missing --root. ${USAGE}` };
162
317
  if (!isAbsolute(root)) return { error: `--root must be an absolute path, got "${root}".` };
163
318
  const parsedPort = Number(port);
164
319
  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}".` };
320
+ for (const [flag, value] of [["--idle-stop-minutes", idleStopMinutes], ["--device-idle-minutes", deviceIdleMinutes]]) if (!/^\d+$/.test(value)) return { error: `${flag} must be a whole number of minutes, got "${value}".` };
166
321
  return {
167
322
  root: resolve(root),
168
323
  port: parsedPort,
169
324
  tunnel,
170
325
  resetCache,
171
- idleStopMinutes: Number(idleStopMinutes)
326
+ idleStopMinutes: Number(idleStopMinutes),
327
+ deviceIdleMinutes: Number(deviceIdleMinutes)
172
328
  };
173
329
  }
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) }) {
330
+ const DEVICE_IDLE_OPS = {
331
+ due: dueIdleDevices,
332
+ shutDown: shutDownIdleDevices
333
+ };
334
+ async function runSupervisor({ root, port, tunnel = false, resetCache = false, idleStopMinutes = 0, idleProbe, deviceIdleMinutes = 0, deviceIdle = DEVICE_IDLE_OPS, isExpo = detectIsExpo, startBare = null, startExpo = null, now = Date.now, onExit = (code) => process.exit(code), attachSignals = true, stderr = (line) => console.error(line), watchman = runWatchman }) {
175
335
  root = realpathSync(root);
176
336
  const logsDir = workspaceLogsDir(root);
177
337
  const writer = createNdjsonWriter(join(logsDir, "metro.ndjson"), { maxBytes: LOG_ROTATE_BYTES });
@@ -191,6 +351,13 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
191
351
  mode,
192
352
  startedAt
193
353
  };
354
+ const vanished = vanishedSupervisorMessage(readWorkspaceState$1(root)?.supervisor);
355
+ if (vanished) writer.write({
356
+ src: "metro",
357
+ level: "warn",
358
+ event: "supervisor_vanished",
359
+ msg: vanished
360
+ });
194
361
  clearExpoMetroTunnel(root);
195
362
  writePidFile(root, process.pid);
196
363
  writeWorkspaceState(root, { supervisor: record });
@@ -214,14 +381,29 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
214
381
  });
215
382
  let stopping = false;
216
383
  let stopWatchingIdle = null;
217
- const finish = (code, event, level, msg) => {
384
+ let stopWatchingDevices = null;
385
+ const stopCause = (trigger) => {
386
+ const request = readDevServerStopRequest(readWorkspaceState$1(root));
387
+ return devServerStopRecord(trigger, request?.processToken === processToken ? request : null, new Date(now()).toISOString());
388
+ };
389
+ const finish = (code, event, level, msg, stop) => {
218
390
  stopWatchingIdle?.();
391
+ stopWatchingDevices?.();
219
392
  writer.write({
220
393
  src: "metro",
221
394
  level,
222
395
  event,
223
- msg
396
+ msg,
397
+ ...stop ? { stop } : {}
224
398
  });
399
+ try {
400
+ withWorkspaceStateLock(root, () => {
401
+ const current = readWorkspaceState$1(root)?.supervisor;
402
+ if (current && current.processToken !== processToken) return;
403
+ if (stop) writeWorkspaceState(root, { [IDLE_STOP_KEY]: stop });
404
+ clearWorkspaceStateKeys(root, [DEV_SERVER_STOP_REQUEST_KEY]);
405
+ });
406
+ } catch {}
225
407
  try {
226
408
  clearSupervisor(root, record);
227
409
  } catch {}
@@ -233,10 +415,17 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
233
415
  onExit(code);
234
416
  };
235
417
  let server;
236
- const shutdown = async (code, event, msg) => {
418
+ const watchRoots = trackMetroWatchRoots({
419
+ workspaceRoot: root,
420
+ writer,
421
+ watchman
422
+ });
423
+ const shutdown = async (code, event, msg, stop) => {
237
424
  if (stopping || !server) return;
238
425
  stopping = true;
239
426
  stopWatchingIdle?.();
427
+ stopWatchingDevices?.();
428
+ await watchRoots.beforeClose();
240
429
  try {
241
430
  await server.close();
242
431
  } catch (err) {
@@ -247,16 +436,25 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
247
436
  msg: describeError(err)
248
437
  });
249
438
  }
250
- finish(code, event, code === 0 ? "info" : "error", msg);
439
+ await watchRoots.afterClose();
440
+ finish(code, event, stop ? devServerStopLevel(stop) : code === 0 ? "info" : "error", msg, stop);
251
441
  };
252
442
  if (attachSignals) for (const signal of ["SIGTERM", "SIGINT"]) process.on(signal, () => {
253
443
  if (server) {
254
- shutdown(0, "supervisor_stopped", `received ${signal}; stopping the ${mode} dev server`);
444
+ const stop = stopCause({
445
+ kind: "signal",
446
+ signal
447
+ });
448
+ shutdown(0, "supervisor_stopped", `stopping the ${mode} dev server: ${describeDevServerStop(stop)}`, stop);
255
449
  return;
256
450
  }
257
451
  if (stopping) return;
258
452
  stopping = true;
259
- finish(signal === "SIGINT" ? 130 : 143, "supervisor_stopped", "warn", `received ${signal} before the ${mode} dev server started; stopping`);
453
+ const stop = stopCause({
454
+ kind: "signal",
455
+ signal
456
+ });
457
+ finish(signal === "SIGINT" ? 130 : 143, "supervisor_stopped", "warn", `stopping before the ${mode} dev server started: ${describeDevServerStop(stop)}`, stop);
260
458
  });
261
459
  const serverActivity = trackDevServerActivity(now);
262
460
  const serverWriter = Object.assign(Object.create(writer), { write(entry) {
@@ -264,7 +462,7 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
264
462
  return writer.write(entry);
265
463
  } });
266
464
  try {
267
- server = await (mode === "expo-child" ? startExpo || (await import("./server-expo-BeYwP_Xy.mjs")).startExpoServer : startBare || (await import("./server-bare-Bq8BpSIo.mjs")).startBareServer)({
465
+ server = await (mode === "expo-child" ? startExpo || (await import("./server-expo-KJeD109I.mjs")).startExpoServer : startBare || (await import("./server-bare-X7PUgizL.mjs")).startBareServer)({
268
466
  root,
269
467
  port,
270
468
  logsDir,
@@ -313,12 +511,45 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
313
511
  } });
314
512
  });
315
513
  }
514
+ watchRoots.started(serverPid ?? process.pid);
316
515
  writer.write({
317
516
  src: "metro",
318
517
  level: "info",
319
518
  event: "server_started",
320
519
  msg: `${mode} dev server listening on port ${port}`
321
520
  });
521
+ const deviceIdleMs = deviceIdleMinutes * 6e4;
522
+ const workspaceProbe = workspaceIdleProbe(root);
523
+ const shutDownIdleDevices = (idleMs) => deviceIdle.shutDown(root, idleMs, (entry) => writer.write({
524
+ src: "metro",
525
+ ...entry
526
+ }));
527
+ if (deviceIdleMinutes > 0) stopWatchingDevices = watchIdleDevices({
528
+ idleMs: deviceIdleMs,
529
+ now,
530
+ lastUseAt: () => {
531
+ let deviceLog = NaN;
532
+ try {
533
+ deviceLog = statSync(join(logsDir, "device.ndjson")).mtimeMs;
534
+ } catch {}
535
+ const times = [workspaceProbe.lastActivityAt(), deviceLog].filter(Number.isFinite);
536
+ return times.length ? Math.max(...times) : NaN;
537
+ },
538
+ hasDue: () => deviceIdle.due(root, deviceIdleMs).length > 0,
539
+ onDue: async () => {
540
+ try {
541
+ await withIdleWorkspace(root, () => {
542
+ if (!stopping) shutDownIdleDevices(deviceIdleMs);
543
+ }, {
544
+ purpose: "device idle shutdown",
545
+ supervisor: false,
546
+ managedLocks: false
547
+ });
548
+ } catch (err) {
549
+ stderr(`Stim supervisor: could not shut down idle devices: ${describeError(err)}`);
550
+ }
551
+ }
552
+ });
322
553
  if (idleStopMinutes > 0) stopWatchingIdle = watchIdleDevServer({
323
554
  idleStopMs: idleStopMinutes * 6e4,
324
555
  now,
@@ -328,14 +559,20 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
328
559
  const stopIfStillIdle = async () => {
329
560
  const idleMinutes = idleMinutesNow();
330
561
  if (stopping || idleMinutes === null) return;
562
+ if (deviceIdleMinutes > 0) try {
563
+ shutDownIdleDevices(Math.min(deviceIdleMs, idleStopMinutes * 6e4));
564
+ } catch (err) {
565
+ stderr(`Stim supervisor: could not shut down idle devices: ${describeError(err)}`);
566
+ }
567
+ const stop = {
568
+ reason: "idle",
569
+ at: new Date(now()).toISOString(),
570
+ idleMinutes
571
+ };
331
572
  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
- } });
573
+ if (readWorkspaceState$1(root)?.supervisor?.processToken === processToken) writeWorkspaceState(root, { [IDLE_STOP_KEY]: stop });
337
574
  });
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`);
575
+ 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`, stop);
339
576
  };
340
577
  try {
341
578
  await withIdleWorkspace(root, () => withWorkspaceProcessLock(dirname(logsDir), "metro-start", stopIfStillIdle, {
@@ -354,8 +591,13 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
354
591
  });
355
592
  server.onExit?.((info) => {
356
593
  if (stopping) return;
357
- const detail = info?.signal ? `signal ${info.signal}` : `exit code ${info?.code ?? "unknown"}`;
358
- shutdown(1, "supervisor_stopped", `the ${mode} dev server exited unexpectedly (${detail}); shutting the supervisor down`);
594
+ const stop = stopCause({
595
+ kind: "server-exit",
596
+ mode,
597
+ code: info?.code ?? null,
598
+ signal: info?.signal ?? null
599
+ });
600
+ shutdown(stop.reason === "requested" ? 0 : 1, "supervisor_stopped", stop.reason === "requested" ? `the ${mode} dev server exited; ${describeDevServerStop(stop)}` : `${describeDevServerStop(stop)}; shutting the supervisor down`, stop);
359
601
  });
360
602
  return {
361
603
  mode,
@@ -387,7 +629,8 @@ async function main(argv = process.argv.slice(2)) {
387
629
  port: parsed.port,
388
630
  tunnel: parsed.tunnel ?? false,
389
631
  resetCache: parsed.resetCache ?? false,
390
- idleStopMinutes: parsed.idleStopMinutes ?? 0
632
+ idleStopMinutes: parsed.idleStopMinutes ?? 0,
633
+ deviceIdleMinutes: parsed.deviceIdleMinutes ?? 0
391
634
  });
392
635
  }
393
636
  function invokedDirectly() {