stim 1.14.0 → 1.16.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 (152) hide show
  1. package/README.md +2 -0
  2. package/dist/activity-BxwdD81S.mjs +609 -0
  3. package/dist/agent-device-usage-output-Dpih1Tfa.mjs +411 -0
  4. package/dist/{android-d6rGq1pA.mjs → android-B_NumVyQ.mjs} +169 -81
  5. package/dist/{android-LbPFRWVL.mjs → android-Ift4kVvt.mjs} +517 -413
  6. package/dist/android-cas-8U4DPqeR.mjs +167 -0
  7. package/dist/android-cas-compiler.mjs +2 -2
  8. package/dist/{android-XpvCrV7q.mjs → android-r2rBL8kV.mjs} +2 -2
  9. package/dist/{app-install-BpgSgkJj.mjs → app-install-CKWdbXIX.mjs} +128 -151
  10. package/dist/attempt-Bk2ouQVL.mjs +39 -0
  11. package/dist/budget-DjrURfQn.mjs +2700 -0
  12. package/dist/{build-plan-Dcr-3ie0.mjs → build-plan-DjeSb2Xl.mjs} +192 -653
  13. package/dist/build-slots-rUk77xqh.mjs +132 -0
  14. package/dist/{cache-manifest-C68QQ0ua.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
  15. package/dist/cache-manifest.mjs +1 -1
  16. package/dist/{chrome-BPCiC3ZX.mjs → chrome-BrcVWQyD.mjs} +1 -1
  17. package/dist/cli-BkYZ_6hy.mjs +51 -0
  18. package/dist/cli.mjs +6 -42
  19. package/dist/client-BTY686R6.mjs +1368 -0
  20. package/dist/collector-run.mjs +9 -195
  21. package/dist/{command-output-DVcpw6eE.mjs → command-output-DnJMdEY-.mjs} +1 -1
  22. package/dist/{config-lvc7d3C2.mjs → config-D0DbpZtM.mjs} +28 -29
  23. package/dist/{created-devices-CllH_8eb.mjs → created-devices-D5owjN4C.mjs} +2 -2
  24. package/dist/{dependency-state-DsT7aKeo.mjs → dependency-state-B6cMXZab.mjs} +2 -2
  25. package/dist/{deps-D9IcKqXS.mjs → deps-urbmfXig.mjs} +3 -3
  26. package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
  27. package/dist/dev-client-CroAIjjj.mjs +43 -0
  28. package/dist/{device-CjuuAJjQ.mjs → device-DZQjF7H5.mjs} +8 -7
  29. package/dist/device-capacity-BHwxNBlu.mjs +136 -0
  30. package/dist/device-host-worker.d.mts +1 -0
  31. package/dist/device-host-worker.mjs +964 -0
  32. package/dist/device-ios-BadmeL2V.mjs +381 -0
  33. package/dist/{device-lease-5c3WrVRm.mjs → device-lease-D-hLqx1b.mjs} +43 -36
  34. package/dist/{device-pool-BiyDyPP-.mjs → device-lease-run-CqjShqWW.mjs} +6 -126
  35. package/dist/device-pool-CUaEWMMm.mjs +125 -0
  36. package/dist/{device-remote-D87zEzaq.mjs → device-remote-D5WVWqOq.mjs} +69 -163
  37. package/dist/{doctor-x9pnU9fq.mjs → doctor-Cimf-gGB.mjs} +241 -103
  38. package/dist/{error-diagnostics-C5HzcwCW.mjs → error-diagnostics-B9L4Ae0x.mjs} +15 -18
  39. package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
  40. package/dist/{gc-ko6mUAYD.mjs → gc-Bqy4bjEu.mjs} +599 -108
  41. package/dist/{android-cas-iZK3DOCr.mjs → gradle-BsO1MdsH.mjs} +8 -294
  42. package/dist/{guide-6R0VNFqI.mjs → guide-B_uyKTxW.mjs} +2511 -239
  43. package/dist/{guide-status-BZrFPIA_.mjs → guide-status-CjEZMAD4.mjs} +3 -3
  44. package/dist/hosted-client-a-WmqE2L.mjs +197 -0
  45. package/dist/hosted-ios-CIWelIqf.mjs +220 -0
  46. package/dist/hosted-logs-D5g041Q3.mjs +187 -0
  47. package/dist/hosted-macos-BSZyIb3y.mjs +135 -0
  48. package/dist/idle-DYVymG92.mjs +1099 -0
  49. package/dist/idle-shutdown-CTsDnAde.mjs +122 -0
  50. package/dist/in-use-DvBw0Kxo.mjs +130 -0
  51. package/dist/{ios-BwDPrnYQ.mjs → ios-BaInn3sW.mjs} +201 -10
  52. package/dist/ios-Cr1h5gnO.mjs +189 -0
  53. package/dist/{ios-P1f5nm2n.mjs → ios-aq3QoGI3.mjs} +1159 -633
  54. package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
  55. package/dist/{ios-device-DGglU3E_.mjs → ios-device-FfCN_C8p.mjs} +12 -7
  56. package/dist/ios-state-DS5UpYTH.mjs +48 -0
  57. package/dist/{launch-verify-Deg4tB3Z.mjs → launch-verify-2DBRizjo.mjs} +5 -5
  58. package/dist/logs-CHEVK8zD.mjs +371 -0
  59. package/dist/machines-BIBUdhpj.mjs +265 -0
  60. package/dist/macos-CSYEAFmF.mjs +473 -0
  61. package/dist/macos-run.d.mts +5 -0
  62. package/dist/macos-run.mjs +2 -0
  63. package/dist/maintenance-run.d.mts +4 -0
  64. package/dist/maintenance-run.mjs +254 -0
  65. package/dist/{metro-CcEo8Fl8.mjs → metro-BeK1ZPz1.mjs} +20 -120
  66. package/dist/metro-gateway-BJ-rxQAB.mjs +84 -0
  67. package/dist/{named-ports-Blg8IxOh.mjs → named-ports-Bk_YXEmE.mjs} +6 -59
  68. package/dist/native-run-NaBrRFtQ.mjs +147 -0
  69. package/dist/{native-runtime-DjBHRg5m.mjs → native-runtime-DBzRnEKS.mjs} +9 -5
  70. package/dist/{ndjson-CDY9IkVC.mjs → ndjson-PclOIwVY.mjs} +2 -2
  71. package/dist/offload-worker.d.mts +122 -0
  72. package/dist/offload-worker.mjs +606 -0
  73. package/dist/{ownership-DyYDF5ww.mjs → ownership-DiA16AsN.mjs} +3 -2
  74. package/dist/ownership-hCkPzGmr.mjs +571 -0
  75. package/dist/{page-BHUzTDFt.mjs → page-QF_Ke10s.mjs} +7 -2
  76. package/dist/{ports-BguS83ex.mjs → ports-R2k4Knz3.mjs} +3 -3
  77. package/dist/prebuild-D2fsca_Z.mjs +213 -0
  78. package/dist/preview-DhVbvPAZ.mjs +318 -0
  79. package/dist/process-identity-BmRm1qV0.mjs +12 -0
  80. package/dist/{project-CWlvEaeN.mjs → project-Du91Ilvc.mjs} +74 -19
  81. package/dist/pull-request-pxnDCQzn.mjs +322 -0
  82. package/dist/pull-requests.d.mts +120 -0
  83. package/dist/pull-requests.mjs +2 -0
  84. package/dist/recordings-BIXOzVnr.mjs +37 -0
  85. package/dist/{reload-BiniwsSZ.mjs → reload-rM9jV1fz.mjs} +109 -24
  86. package/dist/{remote-cache-CcR6o6lI.mjs → remote-cache-C8-V13XN.mjs} +5 -5
  87. package/dist/run-f4vI6EnK.mjs +119 -0
  88. package/dist/{server-bare-DGfgyJ5J.mjs → server-bare-CpPGmnmO.mjs} +33 -15
  89. package/dist/server-expo-CVqO2c35.mjs +2 -0
  90. package/dist/{server-expo-BVuwQ65v.mjs → server-expo-MC0rHlOC.mjs} +8 -12
  91. package/dist/settings-BBpMcDbD.mjs +25 -0
  92. package/dist/{settings-B-fsC-Om.mjs → settings-ZkDzkzvB.mjs} +22 -10
  93. package/dist/settings-oTSkkNrM.mjs +1439 -0
  94. package/dist/settings.schema.json +845 -97
  95. package/dist/{simslim-DrDN4WuV.mjs → simslim-B8-7AoTI.mjs} +8 -132
  96. package/dist/{slot-launch-D5t8EiMf.mjs → slot-launch-B8VrHxn1.mjs} +11 -8
  97. package/dist/{spawn-claims-Drw_o-Fb.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
  98. package/dist/spawn-entry-jw19m8Nl.mjs +17 -0
  99. package/dist/stage-CzaiwFsI.mjs +143 -0
  100. package/dist/start-CX8Ig-U2.mjs +2 -0
  101. package/dist/{start-s4RXp1wy.mjs → start-ClDfwXa4.mjs} +116 -65
  102. package/dist/state-3DwSQIBK.mjs +41 -0
  103. package/dist/state-B4PQFXMW.d.mts +12 -0
  104. package/dist/{state-Dk9RVXI7.mjs → state-C1jB7Q8q.mjs} +70 -8
  105. package/dist/{state-DHqvsbqC.mjs → state-DcW6vOd_.mjs} +7 -4
  106. package/dist/stats-CEZ_i5je.mjs +77 -0
  107. package/dist/{status-BgAuxESg.mjs → status-BuvBFeII.mjs} +118 -20
  108. package/dist/{status-BsPAnfOI.mjs → status-Cs7VLg9D.mjs} +542 -283
  109. package/dist/status-DlKvVJBE.mjs +49 -0
  110. package/dist/stdout-reader-C8yjre1x.mjs +23 -0
  111. package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
  112. package/dist/stim-footprint.swift +32 -0
  113. package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
  114. package/dist/stop-B0Yxtmiy.mjs +48 -0
  115. package/dist/{stop-BzreOWJp.mjs → stop-Bffozsac.mjs} +83 -16
  116. package/dist/{stop-BWIF_6BK.mjs → stop-Dt_dM16A.mjs} +2 -2
  117. package/dist/stop-cause-CidxIqup.mjs +97 -0
  118. package/dist/supervisor-run.d.mts +43 -11
  119. package/dist/supervisor-run.mjs +363 -24
  120. package/dist/support-CAP8qDUF.mjs +316 -0
  121. package/dist/support-D4aYJniO.mjs +201 -0
  122. package/dist/teardown-CL6vDs2Y.mjs +727 -0
  123. package/dist/{xcode-Cp5KvK9x.mjs → toolchain-DkUflduf.mjs} +364 -14
  124. package/dist/trigger-Bemu8ytw.mjs +65 -0
  125. package/dist/trigger-CXts6qt1.mjs +2 -0
  126. package/dist/warm-progress-CpGv3Wb6.mjs +732 -0
  127. package/dist/{web-BsQs1WlO.mjs → web-DWUaMp8P.mjs} +37 -21
  128. package/dist/web-run.mjs +310 -13
  129. package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
  130. package/dist/workspace-state-B8-aJ1_5.mjs +371 -0
  131. package/dist/workspaces-BV4pdHFz.mjs +306 -0
  132. package/dist/{worktree-UrQWXSeb.mjs → worktree-K-xj9Db0.mjs} +625 -256
  133. package/dist/worktree-jnG8Iph5.mjs +2 -0
  134. package/package.json +10 -5
  135. package/shim/bundle-response.cjs +28 -12
  136. package/dist/activity-CYVDcrt3.mjs +0 -356
  137. package/dist/budget-CGDkhvv_.mjs +0 -1745
  138. package/dist/build-progress-DxKpwnaS.mjs +0 -247
  139. package/dist/idle-DXtx3RbX.mjs +0 -191
  140. package/dist/in-use-DlqBDKqY.mjs +0 -249
  141. package/dist/logs-Civ4gbBH.mjs +0 -486
  142. package/dist/merge-state-ytFQCeh4.mjs +0 -262
  143. package/dist/server-expo-DIHDhvGg.mjs +0 -2
  144. package/dist/settings-D61iNOsB.mjs +0 -629
  145. package/dist/start-Cetfvy8l.mjs +0 -2
  146. package/dist/stats-CFpFGlJV.mjs +0 -62
  147. package/dist/stats-CX1Z8Tca.mjs +0 -368
  148. package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
  149. package/dist/stop-cause-hjGBOZTG.mjs +0 -1231
  150. package/dist/worktree-BVrHsYZG.mjs +0 -752
  151. package/dist/worktree-CHWWvm5v.mjs +0 -2
  152. package/dist/{metro-store-DzeWkfqW.mjs → metro-store-CDDZHtPl.mjs} +1 -1
@@ -1,22 +1,150 @@
1
- import { O as process_identity_exports, U as projectDeviceSlots, V as parseDeviceSlotKey, o as clearSupervisor, p as getProject, w as setSupervisor } from "./config-lvc7d3C2.mjs";
2
- 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-CcEo8Fl8.mjs";
4
- import { r as detectIsExpo } from "./project-CWlvEaeN.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";
3
+ import { t as relaunchWithLogFile } from "./detached-entry-CiTEgMR1.mjs";
4
+ import { r as detectIsExpo } from "./project-Du91Ilvc.mjs";
5
+ import { C as workspaceStateFile, a as updateWorkspaceState, c as writeWorkspaceState, n as clearWorkspaceStateKeys, o as withWorkspaceStateLock, r as readWorkspaceState$1, x as workspaceLogsDir$1 } from "./workspace-state-B8-aJ1_5.mjs";
6
+ import { n as withIdleWorkspace } from "./in-use-DvBw0Kxo.mjs";
7
+ import { r as createActivityReader } from "./activity-BxwdD81S.mjs";
8
+ import { a as parseLease, r as leaseIsExpired, s as parseWorkspaceLeases, t as deviceLeasePath } from "./device-lease-D-hLqx1b.mjs";
9
+ import { l as readPidFile, n as MODE_EXPO, p as writePidFile, r as clearExpoMetroTunnel, s as clearWorkspaceSupervisor, t as MODE_BARE } from "./state-DcW6vOd_.mjs";
10
+ import { C as parseSubscriberNames, O as runWatchman, T as parseWatchRoots, s as workspaceBuildInProgress, w as parseTriggerCount } from "./idle-DYVymG92.mjs";
11
+ import { z as ownedAvdSerialResolver } from "./android-B_NumVyQ.mjs";
12
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BL7Q0HCO.mjs";
5
13
  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-DHqvsbqC.mjs";
8
- import { a as withWorkspaceProcessLock } from "./named-ports-Blg8IxOh.mjs";
9
- import { a as parseLease, r as leaseIsExpired, s as parseWorkspaceLeases, t as deviceLeasePath } from "./device-lease-5c3WrVRm.mjs";
10
- import { n as createActivityReader } from "./activity-CYVDcrt3.mjs";
11
- import { R as ownedAvdSerialResolver } from "./android-d6rGq1pA.mjs";
12
- import { c as vanishedSupervisorMessage, i as devServerStopRecord, r as devServerStopLevel, t as describeDevServerStop } from "./stop-cause-hjGBOZTG.mjs";
13
- import { s as workspaceBuildInProgress } from "./idle-DXtx3RbX.mjs";
14
- import { n as withIdleWorkspace } from "./in-use-DlqBDKqY.mjs";
14
+ import { r as createNdjsonWriter } from "./ndjson-PclOIwVY.mjs";
15
+ import { c as vanishedSupervisorMessage, i as devServerStopRecord, r as devServerStopLevel, t as describeDevServerStop } from "./stop-cause-CidxIqup.mjs";
16
+ import { l as probeHostedSession } from "./hosted-client-a-WmqE2L.mjs";
17
+ import { t as clearHostedMetro } from "./metro-gateway-BJ-rxQAB.mjs";
18
+ import { n as dueIdleDevices, r as shutDownIdleDevices } from "./idle-shutdown-CTsDnAde.mjs";
15
19
  import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
16
- import { LOG_ROTATE_BYTES } from "@stim-cli/core";
17
- import { DEV_SERVER_STOP_REQUEST_KEY, IDLE_STOP_KEY, readDevServerStopRequest } from "@stim-cli/core/state";
18
- import { dirname, isAbsolute, join, resolve } from "node:path";
20
+ import { dirname, isAbsolute, join, resolve, sep } from "node:path";
21
+ import { LOG_ROTATE_BYTES, createMetroGateway } from "@stim-cli/core";
22
+ import { DEV_SERVER_STOP_REQUEST_KEY, HOSTED_METRO_GATEWAYS_KEY, HOSTED_METRO_REQUESTS_KEY, IDLE_STOP_KEY, hostedIosPlacements, hostedMetroGateways, hostedMetroRequests, readDevServerStopRequest } from "@stim-cli/core/state";
19
23
  import { fileURLToPath } from "node:url";
24
+ //#region src/supervisor/hosted-metro.ts
25
+ function reconcileHostedMetro(requests, active) {
26
+ const same = (session) => JSON.stringify(requests[session]) === JSON.stringify(active[session]);
27
+ return {
28
+ close: Object.keys(active).filter((session) => !same(session)),
29
+ open: Object.keys(requests).filter((session) => !same(session))
30
+ };
31
+ }
32
+ function watchHostedMetro(root, metroPort, processToken) {
33
+ const active = {};
34
+ const bridges = /* @__PURE__ */ new Map();
35
+ let closed = false;
36
+ let running = Promise.resolve();
37
+ let probingAt = 0;
38
+ let probing = false;
39
+ let stamp = "";
40
+ let cached = null;
41
+ updateWorkspaceState(root, (state) => state.supervisor?.processToken === processToken ? {
42
+ ...state,
43
+ supervisor: {
44
+ ...state.supervisor,
45
+ hostedMetro: true
46
+ }
47
+ } : state);
48
+ const readState = () => {
49
+ const file = statSync(workspaceStateFile(root));
50
+ const next = `${file.ino}:${file.mtimeMs}:${file.ctimeMs}:${file.size}`;
51
+ if (next !== stamp) {
52
+ cached = readWorkspaceState$1(root);
53
+ stamp = next;
54
+ }
55
+ return cached;
56
+ };
57
+ const publish = (session, gateway) => updateWorkspaceState(root, (state) => {
58
+ const gateways = { ...hostedMetroGateways(state) };
59
+ if (state.supervisor?.processToken !== processToken) return state;
60
+ if (gateway) gateways[session] = gateway;
61
+ else delete gateways[session];
62
+ return {
63
+ ...state,
64
+ [HOSTED_METRO_GATEWAYS_KEY]: gateways
65
+ };
66
+ });
67
+ const reconcile = async () => {
68
+ if (closed) return;
69
+ const state = readState();
70
+ if (state?.supervisor?.processToken !== processToken) return;
71
+ const placements = Object.values(hostedIosPlacements(state));
72
+ const sessions = new Set(placements.map((placement) => placement.session));
73
+ const requests = Object.fromEntries(Object.entries(hostedMetroRequests(state)).filter(([session]) => sessions.has(session)));
74
+ if (Object.keys(requests).length !== Object.keys(hostedMetroRequests(state)).length) updateWorkspaceState(root, (current) => {
75
+ if (current.supervisor?.processToken !== processToken) return current;
76
+ const recorded = new Set(Object.values(hostedIosPlacements(current)).map((placement) => placement.session));
77
+ return {
78
+ ...current,
79
+ [HOSTED_METRO_REQUESTS_KEY]: Object.fromEntries(Object.entries(hostedMetroRequests(current)).filter(([session]) => recorded.has(session)))
80
+ };
81
+ });
82
+ const actions = reconcileHostedMetro(requests, active);
83
+ for (const session of actions.close) {
84
+ await bridges.get(session)?.close();
85
+ bridges.delete(session);
86
+ delete active[session];
87
+ publish(session, null);
88
+ }
89
+ for (const session of actions.open) {
90
+ const request = requests[session];
91
+ active[session] = request;
92
+ const bridge = createMetroGateway({
93
+ metroPort,
94
+ peer: request.peer,
95
+ secret: request.secret
96
+ });
97
+ bridges.set(session, bridge);
98
+ try {
99
+ const saved = hostedMetroGateways(state)[session];
100
+ await new Promise((resolve, reject) => {
101
+ bridge.server.once("error", reject);
102
+ bridge.server.listen(saved?.id === request.id ? saved.port ?? 0 : 0, request.address, () => {
103
+ bridge.server.removeListener("error", reject);
104
+ resolve();
105
+ });
106
+ });
107
+ const address = bridge.server.address();
108
+ if (!address || typeof address === "string") throw new Error("No gateway port.");
109
+ publish(session, {
110
+ id: request.id,
111
+ processToken,
112
+ port: address.port
113
+ });
114
+ } catch {
115
+ await bridge.close();
116
+ bridges.delete(session);
117
+ publish(session, {
118
+ id: request.id,
119
+ processToken,
120
+ error: `Could not bind the private Metro gateway for ${request.machine}. Check Tailscale, then restart Metro.`
121
+ });
122
+ }
123
+ }
124
+ if (!probing && Date.now() >= probingAt) {
125
+ probingAt = Date.now() + 1e4;
126
+ probing = true;
127
+ Promise.all(placements.map(async (placement) => {
128
+ if ((await probeHostedSession(placement)).state === "stopped" && !closed) clearHostedMetro(root, placement.session);
129
+ })).catch(() => {}).finally(() => {
130
+ probing = false;
131
+ });
132
+ }
133
+ };
134
+ const tick = () => {
135
+ running = running.then(reconcile).catch(() => {});
136
+ };
137
+ const timer = setInterval(tick, 500);
138
+ timer.unref();
139
+ tick();
140
+ return async () => {
141
+ closed = true;
142
+ clearInterval(timer);
143
+ await running;
144
+ await Promise.all([...bridges.values()].map((bridge) => bridge.close()));
145
+ };
146
+ }
147
+ //#endregion
20
148
  //#region src/supervisor/idle-stop.ts
21
149
  const IDLE_CHECK_MS = 6e4;
22
150
  const MINUTE_MS = 6e4;
@@ -80,13 +208,15 @@ function workspaceIdleProbe(root, { platform = process.platform } = {}) {
80
208
  const used = Date.parse(String(readWorkspaceState$1(root)?.lastUsedAt ?? ""));
81
209
  let clientLog = NaN;
82
210
  try {
83
- clientLog = statSync(join(workspaceLogsDir(root), "client.ndjson")).mtimeMs;
211
+ clientLog = statSync(join(workspaceLogsDir$1(root), "client.ndjson")).mtimeMs;
84
212
  } catch {}
85
213
  const times = [used, clientLog].filter(Number.isFinite);
86
214
  return times.length ? Math.max(...times) : NaN;
87
215
  },
88
216
  blocker() {
89
217
  if (workspaceBuildInProgress(root)) return "a build is in progress";
218
+ const hosted = Object.values(hostedIosPlacements(readWorkspaceState$1(root)))[0];
219
+ if (hosted) return `its iOS simulator runs on ${hosted.machine}`;
90
220
  const lease = heldDeviceLease(root, Date.now());
91
221
  if (lease) return lease;
92
222
  const readActivity = createActivityReader();
@@ -126,15 +256,163 @@ function watchIdleDevServer({ idleStopMs, now, serverActivityAt, probe, onIdle,
126
256
  timer.unref?.();
127
257
  return () => clearInterval(timer);
128
258
  }
259
+ /**
260
+ * Checks each minute, once the workspace has gone `idleMs` without a Stim command, whether an owned device is due
261
+ * for idle shutdown, and runs `onDue` for it.
262
+ */
263
+ function watchIdleDevices({ idleMs, now, lastUseAt, hasDue, onDue, checkMs = IDLE_CHECK_MS }) {
264
+ let deciding = false;
265
+ const timer = setInterval(() => {
266
+ if (deciding || now() - lastUseAt() < idleMs) return;
267
+ let due;
268
+ try {
269
+ due = hasDue();
270
+ } catch {
271
+ return;
272
+ }
273
+ if (!due) return;
274
+ deciding = true;
275
+ onDue().finally(() => {
276
+ deciding = false;
277
+ });
278
+ }, Math.min(checkMs, idleMs));
279
+ timer.unref?.();
280
+ return () => clearInterval(timer);
281
+ }
282
+ //#endregion
283
+ //#region src/supervisor/watchman-roots.ts
284
+ const WATCHMAN_TIMEOUT_MS = 2e3;
285
+ const SHUTDOWN_BUDGET_MS = 3e3;
286
+ const PROBE_DELAYS_MS = [
287
+ 3e3,
288
+ 6e3,
289
+ 1e4,
290
+ 15e3,
291
+ 2e4,
292
+ 3e4,
293
+ 45e3,
294
+ 6e4,
295
+ 12e4,
296
+ 3e5
297
+ ];
298
+ function canonical(path) {
299
+ try {
300
+ return realpathSync(path);
301
+ } catch {
302
+ return null;
303
+ }
304
+ }
305
+ function newRootsContaining(before, current, workspaceRoot) {
306
+ const seen = new Set(before.map((root) => canonical(root) ?? root));
307
+ return current.filter((root) => {
308
+ const resolved = canonical(root);
309
+ if (resolved === null || seen.has(resolved)) return false;
310
+ return workspaceRoot === resolved || workspaceRoot.startsWith(resolved.endsWith(sep) ? resolved : resolved + sep);
311
+ });
312
+ }
313
+ function metroSubscriptionPrefix(pid) {
314
+ return `metro-file-map-${pid}-`;
315
+ }
316
+ async function watchRoots(watchman) {
317
+ try {
318
+ return parseWatchRoots(await watchman(["watch-list"]));
319
+ } catch {
320
+ return null;
321
+ }
322
+ }
323
+ async function subscriberNames(watchman, root) {
324
+ try {
325
+ return parseSubscriberNames(await watchman(["debug-get-subscriptions", root]));
326
+ } catch {
327
+ return null;
328
+ }
329
+ }
330
+ async function triggerCount(watchman, root) {
331
+ try {
332
+ return parseTriggerCount(await watchman(["trigger-list", root]));
333
+ } catch {
334
+ return null;
335
+ }
336
+ }
337
+ function trackMetroWatchRoots({ workspaceRoot, writer, watchman = runWatchman, probeDelaysMs = PROBE_DELAYS_MS }) {
338
+ let deadline = Infinity;
339
+ const call = async (args) => {
340
+ const left = deadline - Date.now();
341
+ if (left <= 0) throw new Error("the watchman cleanup ran out of time");
342
+ return watchman(args, Math.min(WATCHMAN_TIMEOUT_MS, left));
343
+ };
344
+ const before = watchRoots(call);
345
+ const owned = /* @__PURE__ */ new Set();
346
+ const timers = [];
347
+ let prefix = null;
348
+ const probe = async () => {
349
+ const earlier = await before;
350
+ if (prefix === null || earlier === null) return;
351
+ const current = await watchRoots(call);
352
+ if (current === null) return;
353
+ for (const root of newRootsContaining(earlier, current, workspaceRoot)) {
354
+ if (owned.has(root)) continue;
355
+ if ((await subscriberNames(call, root))?.some((name) => name.startsWith(prefix))) owned.add(root);
356
+ }
357
+ };
358
+ return {
359
+ started(metroPid) {
360
+ prefix = metroSubscriptionPrefix(metroPid);
361
+ for (const delay of probeDelaysMs) {
362
+ const timer = setTimeout(() => void probe(), delay);
363
+ timer.unref?.();
364
+ timers.push(timer);
365
+ }
366
+ },
367
+ async beforeClose() {
368
+ for (const timer of timers) clearTimeout(timer);
369
+ deadline = Date.now() + SHUTDOWN_BUDGET_MS;
370
+ await probe();
371
+ },
372
+ async afterClose() {
373
+ for (const root of owned) {
374
+ const others = (await subscriberNames(call, root))?.filter((name) => !name.startsWith(prefix));
375
+ const triggers = others?.length === 0 ? await triggerCount(call, root) : null;
376
+ 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;
377
+ if (reason) {
378
+ writer.write({
379
+ src: "metro",
380
+ level: "debug",
381
+ event: "watchman_root_kept",
382
+ msg: `kept the watchman root ${root}: ${reason}`
383
+ });
384
+ continue;
385
+ }
386
+ try {
387
+ await call(["watch-del", root]);
388
+ writer.write({
389
+ src: "metro",
390
+ level: "info",
391
+ event: "watchman_root_removed",
392
+ msg: `removed the watchman root ${root} that this dev server registered`
393
+ });
394
+ } catch (err) {
395
+ writer.write({
396
+ src: "metro",
397
+ level: "warn",
398
+ event: "watchman_root_kept",
399
+ msg: `could not remove the watchman root ${root}: ${describeError(err)}`
400
+ });
401
+ }
402
+ }
403
+ }
404
+ };
405
+ }
129
406
  //#endregion
130
407
  //#region src/supervisor/run.ts
131
- const USAGE = "Usage: run.js --root <path> --port <n> [--tunnel] [--reset-cache] [--idle-stop-minutes <n>]";
408
+ const USAGE = "Usage: run.js --root <path> --port <n> [--tunnel] [--reset-cache] [--idle-stop-minutes <n>] [--device-idle-minutes <n>]";
132
409
  function parseArgs(argv) {
133
410
  let root;
134
411
  let port;
135
412
  let tunnel = false;
136
413
  let resetCache = false;
137
414
  let idleStopMinutes = "0";
415
+ let deviceIdleMinutes = "0";
138
416
  for (let i = 0; i < argv.length; i++) {
139
417
  const arg = argv[i];
140
418
  if (arg === "--root") {
@@ -157,24 +435,33 @@ function parseArgs(argv) {
157
435
  idleStopMinutes = argv[++i] ?? "";
158
436
  continue;
159
437
  }
438
+ if (arg === "--device-idle-minutes") {
439
+ deviceIdleMinutes = argv[++i] ?? "";
440
+ continue;
441
+ }
160
442
  return { error: `Unknown supervisor argument "${arg}". ${USAGE}` };
161
443
  }
162
444
  if (!root) return { error: `Missing --root. ${USAGE}` };
163
445
  if (!isAbsolute(root)) return { error: `--root must be an absolute path, got "${root}".` };
164
446
  const parsedPort = Number(port);
165
447
  if (!Number.isInteger(parsedPort) || parsedPort <= 0 || parsedPort > 65535) return { error: `--port must be a TCP port number, got "${port}".` };
166
- if (!/^\d+$/.test(idleStopMinutes)) return { error: `--idle-stop-minutes must be a whole number of minutes, got "${idleStopMinutes}".` };
448
+ 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}".` };
167
449
  return {
168
450
  root: resolve(root),
169
451
  port: parsedPort,
170
452
  tunnel,
171
453
  resetCache,
172
- idleStopMinutes: Number(idleStopMinutes)
454
+ idleStopMinutes: Number(idleStopMinutes),
455
+ deviceIdleMinutes: Number(deviceIdleMinutes)
173
456
  };
174
457
  }
175
- 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) }) {
458
+ const DEVICE_IDLE_OPS = {
459
+ due: dueIdleDevices,
460
+ shutDown: shutDownIdleDevices
461
+ };
462
+ 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 }) {
176
463
  root = realpathSync(root);
177
- const logsDir = workspaceLogsDir(root);
464
+ const logsDir = workspaceLogsDir$1(root);
178
465
  const writer = createNdjsonWriter(join(logsDir, "metro.ndjson"), { maxBytes: LOG_ROTATE_BYTES });
179
466
  const mode = isExpo(root) ? MODE_EXPO : MODE_BARE;
180
467
  const startedAt = new Date(now()).toISOString();
@@ -222,12 +509,15 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
222
509
  });
223
510
  let stopping = false;
224
511
  let stopWatchingIdle = null;
512
+ let stopWatchingDevices = null;
513
+ let closeHostedMetro = null;
225
514
  const stopCause = (trigger) => {
226
515
  const request = readDevServerStopRequest(readWorkspaceState$1(root));
227
516
  return devServerStopRecord(trigger, request?.processToken === processToken ? request : null, new Date(now()).toISOString());
228
517
  };
229
518
  const finish = (code, event, level, msg, stop) => {
230
519
  stopWatchingIdle?.();
520
+ stopWatchingDevices?.();
231
521
  writer.write({
232
522
  src: "metro",
233
523
  level,
@@ -254,10 +544,18 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
254
544
  onExit(code);
255
545
  };
256
546
  let server;
547
+ const watchRoots = trackMetroWatchRoots({
548
+ workspaceRoot: root,
549
+ writer,
550
+ watchman
551
+ });
257
552
  const shutdown = async (code, event, msg, stop) => {
258
553
  if (stopping || !server) return;
259
554
  stopping = true;
260
555
  stopWatchingIdle?.();
556
+ stopWatchingDevices?.();
557
+ await closeHostedMetro?.();
558
+ await watchRoots.beforeClose();
261
559
  try {
262
560
  await server.close();
263
561
  } catch (err) {
@@ -268,6 +566,7 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
268
566
  msg: describeError(err)
269
567
  });
270
568
  }
569
+ await watchRoots.afterClose();
271
570
  finish(code, event, stop ? devServerStopLevel(stop) : code === 0 ? "info" : "error", msg, stop);
272
571
  };
273
572
  if (attachSignals) for (const signal of ["SIGTERM", "SIGINT"]) process.on(signal, () => {
@@ -293,7 +592,7 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
293
592
  return writer.write(entry);
294
593
  } });
295
594
  try {
296
- server = await (mode === "expo-child" ? startExpo || (await import("./server-expo-DIHDhvGg.mjs")).startExpoServer : startBare || (await import("./server-bare-DGfgyJ5J.mjs")).startBareServer)({
595
+ server = await (mode === "expo-child" ? startExpo || (await import("./server-expo-CVqO2c35.mjs")).startExpoServer : startBare || (await import("./server-bare-CpPGmnmO.mjs")).startBareServer)({
297
596
  root,
298
597
  port,
299
598
  logsDir,
@@ -342,12 +641,46 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
342
641
  } });
343
642
  });
344
643
  }
644
+ watchRoots.started(serverPid ?? process.pid);
345
645
  writer.write({
346
646
  src: "metro",
347
647
  level: "info",
348
648
  event: "server_started",
349
649
  msg: `${mode} dev server listening on port ${port}`
350
650
  });
651
+ closeHostedMetro = watchHostedMetro(root, port, processToken);
652
+ const deviceIdleMs = deviceIdleMinutes * 6e4;
653
+ const workspaceProbe = workspaceIdleProbe(root);
654
+ const shutDownIdleDevices = (idleMs) => deviceIdle.shutDown(root, idleMs, (entry) => writer.write({
655
+ src: "metro",
656
+ ...entry
657
+ }));
658
+ if (deviceIdleMinutes > 0) stopWatchingDevices = watchIdleDevices({
659
+ idleMs: deviceIdleMs,
660
+ now,
661
+ lastUseAt: () => {
662
+ let deviceLog = NaN;
663
+ try {
664
+ deviceLog = statSync(join(logsDir, "device.ndjson")).mtimeMs;
665
+ } catch {}
666
+ const times = [workspaceProbe.lastActivityAt(), deviceLog].filter(Number.isFinite);
667
+ return times.length ? Math.max(...times) : NaN;
668
+ },
669
+ hasDue: () => deviceIdle.due(root, deviceIdleMs).length > 0,
670
+ onDue: async () => {
671
+ try {
672
+ await withIdleWorkspace(root, () => {
673
+ if (!stopping) shutDownIdleDevices(deviceIdleMs);
674
+ }, {
675
+ purpose: "device idle shutdown",
676
+ supervisor: false,
677
+ managedLocks: false
678
+ });
679
+ } catch (err) {
680
+ stderr(`Stim supervisor: could not shut down idle devices: ${describeError(err)}`);
681
+ }
682
+ }
683
+ });
351
684
  if (idleStopMinutes > 0) stopWatchingIdle = watchIdleDevServer({
352
685
  idleStopMs: idleStopMinutes * 6e4,
353
686
  now,
@@ -357,6 +690,11 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
357
690
  const stopIfStillIdle = async () => {
358
691
  const idleMinutes = idleMinutesNow();
359
692
  if (stopping || idleMinutes === null) return;
693
+ if (deviceIdleMinutes > 0) try {
694
+ shutDownIdleDevices(Math.min(deviceIdleMs, idleStopMinutes * 6e4));
695
+ } catch (err) {
696
+ stderr(`Stim supervisor: could not shut down idle devices: ${describeError(err)}`);
697
+ }
360
698
  const stop = {
361
699
  reason: "idle",
362
700
  at: new Date(now()).toISOString(),
@@ -422,7 +760,8 @@ async function main(argv = process.argv.slice(2)) {
422
760
  port: parsed.port,
423
761
  tunnel: parsed.tunnel ?? false,
424
762
  resetCache: parsed.resetCache ?? false,
425
- idleStopMinutes: parsed.idleStopMinutes ?? 0
763
+ idleStopMinutes: parsed.idleStopMinutes ?? 0,
764
+ deviceIdleMinutes: parsed.deviceIdleMinutes ?? 0
426
765
  });
427
766
  }
428
767
  function invokedDirectly() {