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,14 +1,18 @@
1
- import { B as deviceSlotPlatforms, V as parseDeviceSlotKey, p as getProject } from "./config-lvc7d3C2.mjs";
2
- import { c as resolveProjectMetro, g as recordWorkspaceUse } from "./metro-CcEo8Fl8.mjs";
3
- import { i as findCommandWorkspace } from "./project-CWlvEaeN.mjs";
4
- import { f as readWorkspaceLaunches } from "./state-DHqvsbqC.mjs";
5
- import { f as refuseNoProject, l as phaseLine } from "./command-output-DVcpw6eE.mjs";
6
- import { q as resolveOwnedAvdSerial } from "./android-d6rGq1pA.mjs";
7
- import { a as readWebRecord, l as webFacts, r as cdpEndpoint } from "./state-Dk9RVXI7.mjs";
8
- import { S as resolveOwnedIosSim } from "./ios-BwDPrnYQ.mjs";
9
- import { h as iosAppProcess, o as androidAppProcess } from "./app-install-BpgSgkJj.mjs";
10
- import { n as nativeRunCommand } from "./slot-launch-D5t8EiMf.mjs";
11
- import { n as sendToOwnedPage, t as liveWebRecord } from "./page-BHUzTDFt.mjs";
1
+ import { B as parseDeviceSlotKey, p as getProject, z as deviceSlotPlatforms } from "./config-D0DbpZtM.mjs";
2
+ import { f as refuseNoProject, l as phaseLine } from "./command-output-DnJMdEY-.mjs";
3
+ import { a as findCommandWorkspace } from "./project-Du91Ilvc.mjs";
4
+ import { l as resolveProjectMetro } from "./metro-BeK1ZPz1.mjs";
5
+ import { i as recordWorkspaceUse, r as readWorkspaceState$1 } from "./workspace-state-B8-aJ1_5.mjs";
6
+ import { o as readWebRecord, r as cdpEndpoint, u as webFacts } from "./state-C1jB7Q8q.mjs";
7
+ import { f as readWorkspaceLaunches } from "./state-DcW6vOd_.mjs";
8
+ import { J as resolveOwnedAvdSerial } from "./android-B_NumVyQ.mjs";
9
+ import { w as resolveOwnedIosSim } from "./ios-BaInn3sW.mjs";
10
+ import { l as probeHostedSession } from "./hosted-client-a-WmqE2L.mjs";
11
+ import { p as ensureMetroReverse, s as androidAppProcess, v as iosAppProcess } from "./app-install-CKWdbXIX.mjs";
12
+ import { n as nativeRunCommand } from "./slot-launch-B8VrHxn1.mjs";
13
+ import { t as readHostedIos } from "./ios-state-DS5UpYTH.mjs";
14
+ import { n as sendToOwnedPage, t as liveWebRecord } from "./page-QF_Ke10s.mjs";
15
+ import { hostedIosRecords } from "@stim-cli/core/state";
12
16
  import chalk from "chalk";
13
17
  import { WebSocket } from "ws";
14
18
  //#region src/engine/reload.ts
@@ -31,7 +35,11 @@ function peerMatches(query, role, appId) {
31
35
  if (role === "ios") return query.get("role")?.toLowerCase() === "ios";
32
36
  return query.get("app") === appId;
33
37
  }
34
- function reloadThroughMetro(port, { role, appId, timeoutMs = METRO_TIMEOUT_MS }) {
38
+ /**
39
+ * Asks Metro for its peers and reloads the ones matching `role` and `appId`. With `peersOnly`, it sends nothing and
40
+ * only reports the outcome a reload would have, so a caller can wait for the app to connect.
41
+ */
42
+ function reloadThroughMetro(port, { role, appId, timeoutMs = METRO_TIMEOUT_MS, peersOnly = false }) {
35
43
  const roleName = ROLE_NAMES[role];
36
44
  return new Promise((resolve) => {
37
45
  const requestId = `stim-reload-${process.pid}-${Date.now()}`;
@@ -72,6 +80,15 @@ function reloadThroughMetro(port, { role, appId, timeoutMs = METRO_TIMEOUT_MS })
72
80
  const query = peerQuery(metadata);
73
81
  return query ? peerMatches(query, role, appId) : false;
74
82
  });
83
+ if (rolePeers.length === 0 && peersOnly) {
84
+ finish({
85
+ failed: true,
86
+ noPeer: true,
87
+ peers,
88
+ reason: `No ${roleName} React Native app is connected to Metro on port ${port}.`
89
+ });
90
+ return;
91
+ }
75
92
  if (rolePeers.length === 0) {
76
93
  socket.send(JSON.stringify({
77
94
  version: 2,
@@ -86,7 +103,7 @@ function reloadThroughMetro(port, { role, appId, timeoutMs = METRO_TIMEOUT_MS })
86
103
  });
87
104
  return;
88
105
  }
89
- for (const [id] of rolePeers) socket.send(JSON.stringify({
106
+ if (!peersOnly) for (const [id] of rolePeers) socket.send(JSON.stringify({
90
107
  version: 2,
91
108
  method: "reload",
92
109
  target: id
@@ -98,7 +115,7 @@ function reloadThroughMetro(port, { role, appId, timeoutMs = METRO_TIMEOUT_MS })
98
115
  });
99
116
  return;
100
117
  }
101
- socket.send(JSON.stringify({
118
+ if (!peersOnly) socket.send(JSON.stringify({
102
119
  version: 2,
103
120
  method: "reload"
104
121
  }));
@@ -130,8 +147,16 @@ const DEFAULT_DEPS = {
130
147
  const facts = webFacts(readWebRecord(root));
131
148
  return facts?.status === "unverified" ? "unverified" : liveWebRecord(facts?.record ?? null);
132
149
  },
133
- reloadPage: (record) => sendToOwnedPage(record, "Page.reload")
150
+ reloadPage: (record) => sendToOwnedPage(record, "Page.reload"),
151
+ ensureReverse: (serial, metroPort) => ensureMetroReverse({
152
+ serial,
153
+ metroPort
154
+ }),
155
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms))
134
156
  };
157
+ const RECONNECT_POLL_MS = 500;
158
+ const RECONNECT_POLLS = 16;
159
+ const RECONNECT_UNOBSERVED_MS = 2500;
135
160
  function failure(code, message, remedy) {
136
161
  return {
137
162
  code,
@@ -143,8 +168,9 @@ function describe(error) {
143
168
  return error instanceof Error ? error.message : String(error);
144
169
  }
145
170
  async function reloadBrowser(browser, metroPort, d) {
171
+ let pageUrl;
146
172
  try {
147
- await d.reloadPage(browser);
173
+ pageUrl = await d.reloadPage(browser);
148
174
  } catch (error) {
149
175
  return {
150
176
  ok: false,
@@ -157,10 +183,11 @@ async function reloadBrowser(browser, metroPort, d) {
157
183
  platform: "web",
158
184
  deviceId: cdpEndpoint(browser.cdpPort),
159
185
  deviceName: browser.version ?? "Chrome",
160
- appId: browser.url,
186
+ appId: pageUrl,
161
187
  metroPort: metroPort !== null && new URL(browser.url).port === String(metroPort) ? metroPort : null,
162
188
  strategy: "cdp",
163
- targets: 1
189
+ targets: 1,
190
+ reverseRestored: []
164
191
  }
165
192
  };
166
193
  }
@@ -259,15 +286,45 @@ async function runReload({ root, platform = null, deps = {} }) {
259
286
  ok: false,
260
287
  error: failure("STIM_RELOAD_STOPPED", "No owned Chrome is running for this workspace.", "Run `stim web` first.")
261
288
  };
289
+ const hosted = platform === "android" ? {} : readHostedIos(root);
290
+ const hostedFailures = {};
291
+ if (platform !== "android") for (const slot of Object.keys(hostedIosRecords(readWorkspaceState$1(root)))) {
292
+ if (hosted[slot]) continue;
293
+ try {
294
+ readHostedIos(root, slot);
295
+ } catch (error) {
296
+ hostedFailures[slot] = {
297
+ platform: "ios",
298
+ error: failure("STIM_HOSTING_REFUSED", describe(error), error instanceof Error && "remedy" in error && typeof error.remedy === "string" ? error.remedy : null)
299
+ };
300
+ }
301
+ }
302
+ const hostedProbes = Object.fromEntries(await Promise.all(Object.entries(hosted).map(async ([slot, placement]) => [slot, await probeHostedSession(placement)])));
262
303
  const launches = d.readLaunches(root);
263
304
  const inspected = Object.entries(launches).flatMap(([key, record]) => {
264
305
  const parsed = parseDeviceSlotKey(key);
265
306
  if (!parsed || platform && parsed.platform !== platform) return [];
307
+ if (parsed.platform === "ios" && hostedFailures[parsed.slot]) return [];
308
+ const placement = parsed.platform === "ios" ? hosted[parsed.slot] : void 0;
309
+ if (placement && record.deviceId === placement.session) {
310
+ const probe = hostedProbes[parsed.slot];
311
+ return [probe.state === "ready" ? {
312
+ platform: "ios",
313
+ slot: parsed.slot,
314
+ record,
315
+ hosted: true,
316
+ deviceName: `${placement.device?.name ?? "iOS simulator"} on ${placement.machine}`
317
+ } : {
318
+ platform: "ios",
319
+ error: failure("STIM_RELOAD_PROBE_FAILED", `The iOS session on ${placement.machine} is ${probe.state}.`, `Run stim ios --remote ${placement.machine} or stim stop to reconcile it.`)
320
+ }];
321
+ }
266
322
  return [inspectTarget(parsed.platform, record, {
267
323
  ...project,
268
324
  platforms: deviceSlotPlatforms(project, parsed.slot)
269
325
  }, d, parsed.slot)];
270
326
  });
327
+ inspected.push(...Object.values(hostedFailures));
271
328
  const live = inspected.filter((target) => !isTargetFailure(target));
272
329
  const livePlatforms = new Set(live.map((target) => target.platform));
273
330
  if (browser) livePlatforms.add("web");
@@ -307,21 +364,47 @@ async function runReload({ root, platform = null, deps = {} }) {
307
364
  ok: false,
308
365
  error: failure("STIM_NO_METRO", `Port ${port} is not serving this workspace's Metro.`, "Run `stim start`, then retry.")
309
366
  };
310
- const stopped = processFailure(target.platform, target.record, d, target.slot);
367
+ const stopped = target.hosted ? null : processFailure(target.platform, target.record, d, target.slot);
311
368
  if (stopped) return {
312
369
  ok: false,
313
370
  error: stopped
314
371
  };
315
- const reloaded = await d.reloadMetro(port, {
372
+ const reverseRestored = [];
373
+ const reverseChecked = target.platform === "android" ? live.filter((c) => c.platform === "android" && !c.record.release && c.record.metroPort === port) : [];
374
+ for (const candidate of reverseChecked) {
375
+ const serial = candidate.record.deviceId;
376
+ const ensured = d.ensureReverse(serial, port);
377
+ if (ensured.failed) return {
378
+ ok: false,
379
+ error: failure("STIM_RELOAD_FAILED", `Stim could not check or restore the adb reverse for Metro port ${port} on ${serial}: ${ensured.reason}`, `Run \`adb -s ${serial} reverse tcp:${port} tcp:${port}\`, then \`stim reload android\` again.`)
380
+ };
381
+ if (ensured.restored) reverseRestored.push(serial);
382
+ }
383
+ const request = {
316
384
  role: target.platform,
317
385
  appId: target.record.appId
318
- });
386
+ };
387
+ const expectedPeers = reverseChecked.filter((c) => c.record.appId === target.record.appId).length;
388
+ for (let poll = 0; reverseRestored.length && poll < RECONNECT_POLLS; poll++) {
389
+ const peers = await d.reloadMetro(port, {
390
+ ...request,
391
+ peersOnly: true
392
+ });
393
+ if (peers.broadcast) await d.sleep(RECONNECT_UNOBSERVED_MS);
394
+ if (peers.unreachable || peers.broadcast || peers.ok && peers.targets >= expectedPeers) break;
395
+ await d.sleep(RECONNECT_POLL_MS);
396
+ }
397
+ const reloaded = await d.reloadMetro(port, request);
319
398
  if (!reloaded.ok) {
320
- const stoppedAfterMetro = processFailure(target.platform, target.record, d, target.slot);
399
+ const stoppedAfterMetro = target.hosted ? null : processFailure(target.platform, target.record, d, target.slot);
321
400
  if (stoppedAfterMetro) return {
322
401
  ok: false,
323
402
  error: stoppedAfterMetro
324
403
  };
404
+ if (target.hosted) return {
405
+ ok: false,
406
+ error: failure("STIM_RELOAD_FAILED", reloaded.reason, `Metro could not confirm a peer for the hosted iOS app. Check the app on its hosting Mac and run stim reload ios again.`)
407
+ };
325
408
  const snapshot = `agent-device snapshot -i --platform ${target.platform} --${target.platform === "ios" ? "udid" : "serial"} ${target.record.deviceId}`;
326
409
  const relaunch = `agent-device open ${target.record.appId} --platform ${target.platform} --${target.platform === "ios" ? "udid" : "serial"} ${target.record.deviceId} --metro-port ${port} --relaunch`;
327
410
  const firstBundle = target.platform === "ios" ? " If it stays unreachable, an error in the first bundle leaves iOS without a packager connection at all, and no retry will make it a peer." : "";
@@ -342,7 +425,8 @@ async function runReload({ root, platform = null, deps = {} }) {
342
425
  appId: target.record.appId,
343
426
  metroPort: port,
344
427
  strategy,
345
- targets: reloaded.targets ?? null
428
+ targets: reloaded.targets ?? null,
429
+ reverseRestored
346
430
  }
347
431
  };
348
432
  }
@@ -386,7 +470,8 @@ function registerReload(program, deps = {}) {
386
470
  return;
387
471
  }
388
472
  const scope = facts.strategy === "metro-broadcast" ? ` This Metro cannot name its connected apps, so the reload request was broadcast to all of them and Stim cannot confirm ${facts.appId} was one. Check the expected UI on ${facts.deviceId}; if nothing changed, reload from the app's own error screen or dev menu.` : facts.targets && facts.targets > 1 ? ` ${facts.targets} devices are running this app on that Metro and the reload request addressed all of them, not only ${facts.deviceId}.` : "";
389
- console.log(`Reload requested for ${facts.appId} on ${facts.deviceName} (${facts.deviceId}) via ${facts.strategy}; ${facts.platform} Metro port ${facts.metroPort}.${scope} Completion is not observed; verify the expected UI and run stim logs --errors.`);
473
+ const restored = facts.reverseRestored.length ? ` The adb reverse for tcp:${facts.metroPort} was missing on ${facts.reverseRestored.join(", ")}, so Stim restored it first.` : "";
474
+ console.log(`Reload requested for ${facts.appId} on ${facts.deviceName} (${facts.deviceId}) via ${facts.strategy}; ${facts.platform} Metro port ${facts.metroPort}.${restored}${scope} Completion is not observed; verify the expected UI and run stim logs --errors.`);
390
475
  }
391
476
  });
392
477
  }
@@ -1,9 +1,9 @@
1
- import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
2
- import { r as createNdjsonWriter } from "./ndjson-CDY9IkVC.mjs";
3
- import { M as workspaceLogsDir } from "./metro-CcEo8Fl8.mjs";
4
- import { f as resolvePackageJson } from "./project-CWlvEaeN.mjs";
1
+ import { t as getExecutor } from "./exec-CaErVv7t.mjs";
5
2
  import { n as stripAnsi } from "./process-output-Fpl8PYi8.mjs";
6
- import { n as expoBinFromPackage, r as expoBinPath, s as findBinUpward } from "./server-expo-BVuwQ65v.mjs";
3
+ import { p as resolvePackageJson } from "./project-Du91Ilvc.mjs";
4
+ import { x as workspaceLogsDir } from "./workspace-state-B8-aJ1_5.mjs";
5
+ import { r as createNdjsonWriter } from "./ndjson-PclOIwVY.mjs";
6
+ import { n as expoBinFromPackage, r as expoBinPath, s as findBinUpward } from "./server-expo-MC0rHlOC.mjs";
7
7
  import { createRequire } from "node:module";
8
8
  import { existsSync, readFileSync } from "node:fs";
9
9
  import { join } from "node:path";
@@ -0,0 +1,119 @@
1
+ import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
+ import { n as ownership_claim_exports, t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
3
+ import { i as macosRuntimeClaim, n as macosLogFile, o as updateMacosRecord, r as macosProcess } from "./state-3DwSQIBK.mjs";
4
+ import { r as createNdjsonWriter } from "./ndjson-PclOIwVY.mjs";
5
+ import { LOG_ROTATE_BYTES } from "@stim-cli/core";
6
+ import { readMacosRecord } from "@stim-cli/core/state";
7
+ import { StringDecoder } from "node:string_decoder";
8
+ //#region src/macos/run.ts
9
+ function logLines(stream, write) {
10
+ const decoder = new StringDecoder("utf8");
11
+ let pending = "";
12
+ stream.on("data", (chunk) => {
13
+ pending += decoder.write(chunk);
14
+ while (pending.length > 65536 && !pending.includes("\n")) {
15
+ write(pending.slice(0, 65536));
16
+ pending = pending.slice(65536);
17
+ }
18
+ const lines = pending.split("\n");
19
+ pending = lines.pop() ?? "";
20
+ for (const line of lines) write(line.replace(/\r$/, ""));
21
+ });
22
+ stream.on("end", () => {
23
+ pending += decoder.end();
24
+ if (pending) write(pending);
25
+ });
26
+ }
27
+ async function runMacosSupervisor(root, launchId) {
28
+ const record = readMacosRecord(root);
29
+ if (!record || record.launchId !== launchId || record.build.state !== "ok") throw new Error("The macOS launch record changed before its supervisor started.");
30
+ const owner = macosProcess(process.pid);
31
+ const attempt = (0, ownership_claim_exports.tryAcquireClaim)({
32
+ root: macosRuntimeClaim(root),
33
+ mode: "exclusive",
34
+ label: "macOS app"
35
+ });
36
+ if (attempt.pending) (0, ownership_claim_exports.releaseClaim)(attempt.pending);
37
+ const claim = attempt.acquired;
38
+ if (!claim) throw new Error(`The macOS runtime is already held at ${attempt.held?.path ?? macosRuntimeClaim(root)}.`);
39
+ const writer = createNdjsonWriter(macosLogFile(root), { maxBytes: LOG_ROTATE_BYTES });
40
+ let app;
41
+ let stopping = false;
42
+ let spawned = false;
43
+ let childExited = false;
44
+ try {
45
+ if (!(0, process_identity_exports.sameProcessRecord)(readMacosRecord(root)?.supervisor, record.supervisor) || !updateMacosRecord(root, record.supervisor, { supervisor: owner })) throw new Error("The macOS launch owner changed.");
46
+ (0, ownership_claim_exports.markClaimChildPending)(claim);
47
+ const child = getExecutor().spawn(record.executable, record.arguments, {
48
+ cwd: root,
49
+ stdio: [
50
+ "ignore",
51
+ "pipe",
52
+ "pipe"
53
+ ],
54
+ env: {
55
+ ...process.env,
56
+ NSUnbufferedIO: "YES"
57
+ }
58
+ });
59
+ spawned = child.pid !== void 0;
60
+ const ended = new Promise((resolve, reject) => {
61
+ child.once("error", reject);
62
+ child.once("close", () => {
63
+ childExited = true;
64
+ resolve();
65
+ });
66
+ });
67
+ if (!child.pid) {
68
+ await ended;
69
+ throw new Error("The macOS app did not start.");
70
+ }
71
+ app = macosProcess(child.pid);
72
+ (0, ownership_claim_exports.setClaimChild)(claim, app);
73
+ if (!updateMacosRecord(root, owner, { app })) {
74
+ if ((0, process_identity_exports.inspectProcessIdentity)(app) === "same") process.kill(app.pid, "SIGTERM");
75
+ throw new Error("The macOS launch owner changed.");
76
+ }
77
+ const log = (level, msg) => writer.write({
78
+ src: "client",
79
+ platform: "macos",
80
+ level,
81
+ msg
82
+ });
83
+ if (child.stdout) logLines(child.stdout, (line) => log("info", line));
84
+ if (child.stderr) logLines(child.stderr, (line) => log("warn", line));
85
+ const stop = async () => {
86
+ if (stopping) return;
87
+ stopping = true;
88
+ for (const signal of ["SIGTERM", "SIGKILL"]) {
89
+ if (!app || (0, process_identity_exports.inspectProcessIdentity)(app) !== "same") return;
90
+ process.kill(app.pid, signal);
91
+ if (await (0, process_identity_exports.waitForProcessExit)(app, 5e3)) return;
92
+ }
93
+ };
94
+ process.on("SIGTERM", stop);
95
+ process.on("SIGINT", stop);
96
+ log("info", `Started ${record.product} (pid ${app.pid}).`);
97
+ await ended;
98
+ process.off("SIGTERM", stop);
99
+ process.off("SIGINT", stop);
100
+ log(stopping || child.exitCode === 0 ? "info" : "error", `Exited ${record.product} (${child.exitCode ?? child.signalCode ?? "unknown"}).`);
101
+ updateMacosRecord(root, owner, {
102
+ app: void 0,
103
+ supervisor: void 0
104
+ });
105
+ } finally {
106
+ if (!spawned || childExited || app && ["gone", "different"].includes((0, process_identity_exports.inspectProcessIdentity)(app))) {
107
+ (0, ownership_claim_exports.clearClaimChild)(claim);
108
+ (0, ownership_claim_exports.releaseClaim)(claim);
109
+ }
110
+ writer.close();
111
+ }
112
+ }
113
+ if (process.argv[1]?.endsWith("/macos-run.mjs") || process.argv[1]?.endsWith("/macos/run.ts")) {
114
+ const [root, launchId] = process.argv.slice(2);
115
+ if (!root || !launchId) throw new Error("macos-run needs a workspace and launch identity.");
116
+ await runMacosSupervisor(root, launchId);
117
+ }
118
+ //#endregion
119
+ export { runMacosSupervisor as n, logLines as t };
@@ -1,8 +1,8 @@
1
1
  import { i as __require, t as __commonJSMin } from "./rolldown-runtime-BsOwBuO_.mjs";
2
- import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
2
+ import { t as getExecutor } from "./exec-CaErVv7t.mjs";
3
+ import { D as projectMetroSharedCache } from "./settings-oTSkkNrM.mjs";
3
4
  import { n as supervisorError } from "./errors-B6W7FkxQ.mjs";
4
- import { w as projectMetroSharedCache } from "./settings-D61iNOsB.mjs";
5
- import { a as metroStoreRoot, o as registerMetroStore, t as appendCacheStore } from "./metro-store-DzeWkfqW.mjs";
5
+ import { a as metroStoreRoot, o as registerMetroStore, t as appendCacheStore } from "./metro-store-CDDZHtPl.mjs";
6
6
  import { createRequire } from "node:module";
7
7
  import { isAbsolute, join, relative, sep } from "node:path";
8
8
  //#endregion
@@ -14,6 +14,8 @@ var import_bundle_response = (/* @__PURE__ */ __commonJSMin(((exports, module) =
14
14
  "::1",
15
15
  "::ffff:127.0.0.1"
16
16
  ]);
17
+ const PROGRESS_PART = /^\{"done":(\d+),"total":(\d+),"percent":(\d+)\}$/;
18
+ const PROGRESS_INTERVAL_MS = 1e3;
17
19
  function clientPidFromLsof(output, clientPort, serverPort) {
18
20
  const connection = new RegExp(`:${clientPort}->\\S*:${serverPort}$`);
19
21
  let pid = null;
@@ -36,7 +38,7 @@ var import_bundle_response = (/* @__PURE__ */ __commonJSMin(((exports, module) =
36
38
  res.end("ready");
37
39
  return;
38
40
  }
39
- if (req.headers["x-stim-metro-warmup"] === "1") return next();
41
+ const prefetch = req.headers["x-stim-metro-warmup"] === "1";
40
42
  let url;
41
43
  try {
42
44
  url = new URL(req.url, "http://localhost");
@@ -46,34 +48,50 @@ var import_bundle_response = (/* @__PURE__ */ __commonJSMin(((exports, module) =
46
48
  const platform = url.searchParams.get("platform");
47
49
  if (req.method !== "GET" || !/\.bundle\/*$/.test(url.pathname) || !["ios", "android"].includes(platform)) return next();
48
50
  const requestId = randomUUID();
49
- const clientPid = platform === "ios" ? lookupClientPid(req, runLsof) : null;
50
- const send = (ts, event, statusCode, pid) => {
51
+ const kind = prefetch ? "prefetch" : "response";
52
+ const clientPid = platform === "ios" && !prefetch ? lookupClientPid(req, runLsof) : null;
53
+ const send = (ts, stage, statusCode, pid, progress) => {
51
54
  try {
52
55
  write({
53
56
  ts,
54
57
  src: "metro",
55
- level: event === "bundle_response_failed" ? "error" : "debug",
56
- event,
58
+ level: stage === "failed" && !prefetch ? "error" : "debug",
59
+ event: `bundle_${kind}_${stage}`,
57
60
  platform,
58
61
  requestId,
59
- statusCode,
60
- msg: `${platform} bundle response ${event.slice(16)}`,
62
+ ...progress ?? { statusCode },
63
+ msg: progress ? `${platform} bundle ${progress.percent}%` : `${platform} bundle ${kind} ${stage}`,
61
64
  ...pid ? { clientPid: pid } : {}
62
65
  });
63
66
  } catch {}
64
67
  };
65
- const emit = (event, statusCode) => {
68
+ const emit = (stage, statusCode, progress) => {
66
69
  const ts = Date.now();
67
- if (clientPid) clientPid.then((pid) => send(ts, event, statusCode, pid));
68
- else send(ts, event, statusCode, null);
70
+ if (clientPid) clientPid.then((pid) => send(ts, stage, statusCode, pid, progress));
71
+ else send(ts, stage, statusCode, null, progress);
72
+ };
73
+ emit("started");
74
+ const writeChunk = res.write;
75
+ let progressAt = 0;
76
+ res.write = function(chunk, ...rest) {
77
+ const part = typeof chunk === "string" && chunk.length < 100 ? PROGRESS_PART.exec(chunk) : null;
78
+ const now = part ? Date.now() : 0;
79
+ if (part && now - progressAt >= PROGRESS_INTERVAL_MS) {
80
+ progressAt = now;
81
+ emit("progress", void 0, {
82
+ done: Number(part[1]),
83
+ total: Number(part[2]),
84
+ percent: Number(part[3])
85
+ });
86
+ }
87
+ return writeChunk.call(this, chunk, ...rest);
69
88
  };
70
- emit("bundle_response_started");
71
89
  let ended = false;
72
90
  const finish = (complete) => {
73
91
  if (ended) return;
74
92
  ended = true;
75
93
  const success = complete && (res.statusCode === 200 || res.statusCode === 304);
76
- emit(success ? "bundle_response_finished" : "bundle_response_failed", res.statusCode);
94
+ emit(success ? "finished" : "failed", res.statusCode);
77
95
  };
78
96
  res.once("finish", () => finish(true));
79
97
  res.once("close", () => finish(false));
@@ -0,0 +1,2 @@
1
+ import { p as startExpoServer } from "./server-expo-MC0rHlOC.mjs";
2
+ export { startExpoServer };
@@ -1,13 +1,13 @@
1
- import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
2
- import { r as createNdjsonWriter } from "./ndjson-CDY9IkVC.mjs";
3
- import { f as resolvePackageJson } from "./project-CWlvEaeN.mjs";
4
- import { n as supervisorError } from "./errors-B6W7FkxQ.mjs";
1
+ import { t as getExecutor } from "./exec-CaErVv7t.mjs";
5
2
  import { n as stripAnsi, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
6
- import { w as projectMetroSharedCache } from "./settings-D61iNOsB.mjs";
7
- import { a as metroStoreRoot, i as metroStoreConfirmedRoot, n as expoMetroConfigPath, o as registerMetroStore, r as expoMetroStoreEnv } from "./metro-store-DzeWkfqW.mjs";
3
+ import { D as projectMetroSharedCache } from "./settings-oTSkkNrM.mjs";
4
+ import { p as resolvePackageJson } from "./project-Du91Ilvc.mjs";
5
+ import { n as supervisorError } from "./errors-B6W7FkxQ.mjs";
6
+ import { r as createNdjsonWriter } from "./ndjson-PclOIwVY.mjs";
7
+ import { a as metroStoreRoot, i as metroStoreConfirmedRoot, n as expoMetroConfigPath, o as registerMetroStore, r as expoMetroStoreEnv } from "./metro-store-CDDZHtPl.mjs";
8
8
  import { accessSync, constants, existsSync, readFileSync } from "node:fs";
9
- import { LOG_ROTATE_BYTES } from "@stim-cli/core";
10
9
  import { dirname, join, parse } from "node:path";
10
+ import { LOG_ROTATE_BYTES } from "@stim-cli/core";
11
11
  //#region src/supervisor/server-expo.ts
12
12
  function delay(ms) {
13
13
  return new Promise((resolve) => {
@@ -120,11 +120,7 @@ function recordFromLine(line, { stream = "stdout" } = {}) {
120
120
  if (!msg.trim()) return null;
121
121
  if (stream === "stderr" && msg.startsWith("stim-bundle-response: ")) try {
122
122
  const record = JSON.parse(msg.slice(22));
123
- if (record.src === "metro" && [
124
- "bundle_response_started",
125
- "bundle_response_finished",
126
- "bundle_response_failed"
127
- ].includes(record.event) && ["ios", "android"].includes(record.platform) && typeof record.requestId === "string" && Number.isFinite(record.ts)) return record;
123
+ if (record.src === "metro" && /^bundle_(response|prefetch)_(started|progress|finished|failed)$/.test(record.event) && ["ios", "android"].includes(record.platform) && typeof record.requestId === "string" && Number.isFinite(record.ts)) return record;
128
124
  } catch {}
129
125
  const confirmed = metroStoreConfirmedRoot(msg);
130
126
  if (confirmed) return {
@@ -0,0 +1,25 @@
1
+ import { SETTINGS, coerceSettingText, loadConfig, settingValueError } from "@stim-cli/core/state";
2
+ //#region src/maintenance/settings.ts
3
+ function resolveMaintenanceSettings(config = loadConfig(), env = process.env) {
4
+ const result = {};
5
+ const invalid = [];
6
+ let disabled = false;
7
+ for (const setting of SETTINGS.filter((entry) => entry.key.startsWith("maintenance.") || /^caches\..*MaxGb$/.test(entry.key))) {
8
+ const [group, name] = setting.key.split(".");
9
+ const rawEnv = setting.env ? env[setting.env] : void 0;
10
+ const layer = config?.[group];
11
+ const configured = layer && typeof layer === "object" ? layer[name] : void 0;
12
+ const value = rawEnv !== void 0 ? coerceSettingText(setting, rawEnv) : env.STIM_HOME && setting.scopedHomeValue !== void 0 ? setting.scopedHomeValue : env.CI && setting.ciValue !== void 0 ? setting.ciValue : configured === void 0 ? setting.default : configured;
13
+ const error = value === void 0 ? null : settingValueError(setting, value);
14
+ if (error) {
15
+ invalid.push(`${setting.key}: expected ${error}`);
16
+ if (group === "maintenance") disabled = true;
17
+ }
18
+ result[name] = error ? setting.default : value;
19
+ }
20
+ if (disabled) result.mode = "off";
21
+ if (invalid.length) result.invalid = invalid.join("; ");
22
+ return result;
23
+ }
24
+ //#endregion
25
+ export { resolveMaintenanceSettings as t };
@@ -1,11 +1,11 @@
1
- import { D as writeConfigSetting, E as withConfigLock$1, _ as loadConfig$1, f as getConfigPath$1, h as getRepoSettings, m as getProjectSettings } from "./config-lvc7d3C2.mjs";
2
- import { s as gitCommonDir, y as repoRoot } from "./worktree-BVrHsYZG.mjs";
3
- import { i as findCommandWorkspace, t as NO_PROJECT_REFUSAL } from "./project-CWlvEaeN.mjs";
4
- import { n as settingDefault, r as stimDesktopInstalled } from "./stim-desktop-CXt8-Z3J.mjs";
5
- import { D as readCommittedSettings, L as unknownSettingKeys } from "./settings-D61iNOsB.mjs";
1
+ import { D as withConfigLock$1, O as writeConfigSetting, _ as loadConfig$1, f as getConfigPath$1, h as getRepoSettings, m as getProjectSettings } from "./config-D0DbpZtM.mjs";
2
+ import { A as readCommittedSettings, H as unknownSettingKeys, gt as repoRoot, it as gitCommonDir } from "./settings-oTSkkNrM.mjs";
3
+ import { a as findCommandWorkspace, t as NO_PROJECT_REFUSAL } from "./project-Du91Ilvc.mjs";
4
+ import { n as settingDefault, r as stimDesktopInstalled } from "./stim-desktop-Cnz8CPnl.mjs";
5
+ import { n as recordingLayers, t as deleteTurnedOffRecordings } from "./recordings-BIXOzVnr.mjs";
6
6
  import { existsSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
7
- import { SETTINGS, SETTING_SCOPES, coerceSettingText, isJsonObject, isSensitiveReference, settingDefinition, settingValueError } from "@stim-cli/core/state";
8
7
  import { join } from "node:path";
8
+ import { SETTINGS, SETTING_SCOPES, coerceSettingText, isJsonObject, isSensitiveReference, settingDefinition, settingValueError } from "@stim-cli/core/state";
9
9
  import chalk from "chalk";
10
10
  //#region src/commands/settings.ts
11
11
  const MASK = "********";
@@ -85,6 +85,7 @@ function settingEntry(context, setting) {
85
85
  value: String(masked(setting, envValue))
86
86
  } : void 0;
87
87
  const scopedHome = !env && setting.scopedHomeValue !== void 0 && context.env.STIM_HOME;
88
+ const ci = !env && setting.ciValue !== void 0 && context.env.CI;
88
89
  const winner = PRECEDENCE.find((scope) => scope in layers);
89
90
  let value = null;
90
91
  let origin = null;
@@ -95,6 +96,9 @@ function settingEntry(context, setting) {
95
96
  } else if (scopedHome) {
96
97
  value = setting.scopedHomeValue;
97
98
  origin = "env";
99
+ } else if (ci) {
100
+ value = setting.ciValue;
101
+ origin = "env";
98
102
  } else if (winner) {
99
103
  value = layers[winner];
100
104
  origin = winner;
@@ -112,6 +116,9 @@ function settingEntry(context, setting) {
112
116
  ...env ? { env } : scopedHome ? { env: {
113
117
  name: "STIM_HOME",
114
118
  value: context.env.STIM_HOME
119
+ } } : ci ? { env: {
120
+ name: "CI",
121
+ value: context.env.CI
115
122
  } } : {},
116
123
  ...setting.sensitive ? { sensitive: true } : {},
117
124
  ...defaultReason ? { defaultReason } : {}
@@ -344,37 +351,42 @@ function registerSettings(program, io = CONSOLE, env = process.env) {
344
351
  if (setting.sensitive && scope === "committed" && !isSensitiveReference(value)) throw new Refusal("STIM_BAD_ARG", `${key} is sensitive, so a committed .stim.json takes only an env: or file: reference, never the secret.`, `Set the secret with --scope workspace or --scope repo, or commit a reference such as env:MY_KEYSTORE_PASSWORD.`);
345
352
  const context = readContext(env, io.note);
346
353
  const file = requireLayer(context, scope, setting);
354
+ const recording = setting.key === "recording.enabled" ? recordingLayers() : null;
347
355
  const changed = writeSetting(context, setting, scope, value);
348
356
  report(io, wantsJson(opts, command), {
349
357
  key,
350
358
  scope,
351
359
  file,
352
360
  changed
353
- }, setting, readContext(env), "set");
361
+ }, setting, readContext(env), "set", recording);
354
362
  }));
355
363
  settings.command("unset <key>").description("Remove a setting from one layer").option("--scope <scope>", `the layer to clear: ${SETTING_SCOPES.join(", ")}`).option("--json", "print the setting after removal as JSON").action((key, opts, command) => run(wantsJson(opts, command), io, () => {
356
364
  const setting = requireSetting(key);
357
365
  const scope = readScope(opts.scope, setting);
358
366
  const context = readContext(env, io.note);
359
367
  const file = requireLayer(context, scope, setting);
368
+ const recording = setting.key === "recording.enabled" ? recordingLayers() : null;
360
369
  const changed = writeSetting(context, setting, scope, void 0);
361
370
  report(io, wantsJson(opts, command), {
362
371
  key,
363
372
  scope,
364
373
  file,
365
374
  changed
366
- }, setting, readContext(env), "unset");
375
+ }, setting, readContext(env), "unset", recording);
367
376
  }));
368
377
  }
369
- function report(io, json, write, setting, context, verb) {
378
+ function report(io, json, write, setting, context, verb, recordingBefore) {
370
379
  const entry = settingEntry(context, setting);
380
+ const recordingsDeleted = recordingBefore ? deleteTurnedOffRecordings(recordingBefore) : null;
371
381
  if (json) {
372
382
  io.out(JSON.stringify({
373
383
  ...write,
374
- setting: entry
384
+ setting: entry,
385
+ ...recordingsDeleted ? { recordingsDeleted } : {}
375
386
  }));
376
387
  return;
377
388
  }
389
+ for (const workspace of recordingsDeleted ?? []) io.note(`Deleted the device recordings of ${workspace}.`);
378
390
  const effective = entry.origin === null ? "unset" : `${formatValue(entry.value)} (${formatOrigin(entry)})`;
379
391
  if (!write.changed) {
380
392
  io.out(`${write.key} was not set in ${write.file}; effective value: ${effective}`);