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
@@ -0,0 +1,254 @@
1
+ import { j as formatBytes } from "./config-D0DbpZtM.mjs";
2
+ import { t as relaunchWithLogFile } from "./detached-entry-CiTEgMR1.mjs";
3
+ import { s as resolveBudget } from "./budget-DjrURfQn.mjs";
4
+ import { r as createNdjsonWriter } from "./ndjson-PclOIwVY.mjs";
5
+ import { t as resolveMaintenanceSettings } from "./settings-BBpMcDbD.mjs";
6
+ import { r as due, t as capChildLog } from "./attempt-Bk2ouQVL.mjs";
7
+ import { a as sizeScanDeferred, i as measureSizes, r as measurePressure, t as plannedMaintenance } from "./preview-DhVbvPAZ.mjs";
8
+ import { appendFileSync, mkdirSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
9
+ import { basename, join, resolve } from "node:path";
10
+ import { LOG_ROTATE_BYTES, rotatedLogPath, withDirLock } from "@stim-cli/core";
11
+ import { maintenanceChildLogFile, maintenanceDir, maintenanceNdjsonFile, maintenanceRunClaims, maintenanceStateFile, readMaintenanceState, workspaceLogsDir } from "@stim-cli/core/state";
12
+ import { releaseClaim, tryAcquireClaim } from "@stim-cli/core/ownership-claim";
13
+ import { fileURLToPath } from "node:url";
14
+ //#region src/maintenance/log.ts
15
+ function maintenanceLogger(settings, pass, trigger) {
16
+ const file = maintenanceNdjsonFile();
17
+ const rotated = rotatedLogPath(file);
18
+ withDirLock(`${file}.lock`, () => {
19
+ try {
20
+ if (Date.now() - statSync(rotated).mtimeMs > settings.logRetentionDays * 864e5) unlinkSync(rotated);
21
+ } catch {}
22
+ });
23
+ const writer = createNdjsonWriter(file, { maxBytes: settings.logMaxMb * 1024 ** 2 });
24
+ return {
25
+ write(event, level, msg, fields = {}) {
26
+ if (level === "debug" && !settings.logChecks) return null;
27
+ const record = {
28
+ ...fields,
29
+ ts: Date.now(),
30
+ src: "maintenance",
31
+ level,
32
+ msg,
33
+ event,
34
+ pass,
35
+ trigger,
36
+ mode: "report"
37
+ };
38
+ if (!writer.write(record)) throw writer.lastError ?? /* @__PURE__ */ new Error(`Could not append ${file}`);
39
+ if (typeof fields.workspace === "string" && [
40
+ "maintenance_action",
41
+ "maintenance_failure",
42
+ "maintenance_skip"
43
+ ].includes(event)) {
44
+ const workspace = createNdjsonWriter(join(workspaceLogsDir(fields.workspace), "maintenance.ndjson"), { maxBytes: LOG_ROTATE_BYTES });
45
+ try {
46
+ if (!workspace.write(record)) throw workspace.lastError ?? /* @__PURE__ */ new Error("Could not append workspace maintenance log");
47
+ } finally {
48
+ workspace.close();
49
+ }
50
+ }
51
+ return record;
52
+ },
53
+ close: () => writer.close()
54
+ };
55
+ }
56
+ //#endregion
57
+ //#region src/maintenance/run.ts
58
+ function writeState(state) {
59
+ mkdirSync(maintenanceDir(), { recursive: true });
60
+ withDirLock(join(maintenanceDir(), "state.lock"), () => {
61
+ const file = maintenanceStateFile();
62
+ const tmp = `${file}.${process.pid}.tmp`;
63
+ writeFileSync(tmp, `${JSON.stringify(state)}\n`);
64
+ try {
65
+ renameSync(tmp, file);
66
+ } finally {
67
+ try {
68
+ unlinkSync(tmp);
69
+ } catch {}
70
+ }
71
+ });
72
+ }
73
+ const actionKey = (action) => JSON.stringify([action.kind, action.target]);
74
+ const stableText = (text) => text.replace(/\d+(?:\.\d+)?/g, "#");
75
+ const sameSet = (before, after) => before.size === after.size && [...before].every((key) => after.has(key));
76
+ async function runMaintenance(trigger) {
77
+ const startedAt = Date.now();
78
+ let claim;
79
+ try {
80
+ const attempt = tryAcquireClaim({
81
+ root: maintenanceRunClaims(),
82
+ mode: "exclusive",
83
+ label: "maintenance",
84
+ details: { trigger }
85
+ });
86
+ if (attempt.pending) releaseClaim(attempt.pending);
87
+ claim = attempt.acquired;
88
+ } catch (error) {
89
+ crashLine(error);
90
+ return;
91
+ }
92
+ if (!claim) return;
93
+ let logger;
94
+ try {
95
+ const settings = resolveMaintenanceSettings();
96
+ if (settings.mode === "off") return;
97
+ const state = readMaintenanceState() ?? {
98
+ version: 1,
99
+ lastAt: {},
100
+ pressure: null,
101
+ sizes: [],
102
+ lastPass: null,
103
+ recent: [],
104
+ plan: []
105
+ };
106
+ const checks = due(state, settings, startedAt);
107
+ if (checks.length === 0) return;
108
+ logger = maintenanceLogger(settings, `p-${claim.claimId}`, trigger);
109
+ const record = (event, level, msg, fields = {}) => {
110
+ const entry = logger.write(event, level, msg, fields);
111
+ if (entry && (event === "maintenance_action" || event === "maintenance_failure" || Array.isArray(fields.blocked) && fields.blocked.length > 0)) state.recent = [...state.recent, entry].slice(-20);
112
+ };
113
+ const failures = [];
114
+ let measuredSizes = false;
115
+ const budget = resolveBudget().budget;
116
+ for (const check of checks) {
117
+ if (check === "size" && sizeScanDeferred(settings)) {
118
+ record("maintenance_skip", "debug", "Size check deferred: host load exceeds maintenance.maxLoadPerCore", {
119
+ target: "size",
120
+ reason: "host load exceeds maintenance.maxLoadPerCore"
121
+ });
122
+ state.deferredAt = {
123
+ ...state.deferredAt,
124
+ [check]: Date.now()
125
+ };
126
+ writeState(state);
127
+ continue;
128
+ }
129
+ state.lastAt[check] = Date.now();
130
+ delete state.deferredAt?.[check];
131
+ writeState(state);
132
+ try {
133
+ if (check === "pressure") {
134
+ state.pressure = measurePressure(settings, state.pressure, state.lastAt[check]);
135
+ const floor = Math.max(budget.minFreeDiskMb, budget.hardFloorDiskMb);
136
+ for (const disk of state.pressure.disk) record("maintenance_check", "debug", `Disk free on ${disk.volume}: ${(disk.freeMb / 1024).toFixed(1)}G`, { check: {
137
+ kind: "disk-free",
138
+ value: disk.freeMb / 1024,
139
+ threshold: floor / 1024,
140
+ unit: "GiB"
141
+ } });
142
+ record("maintenance_check", "debug", `Memory pressure: ${state.pressure.memory.level ?? "unavailable"}`, { check: {
143
+ kind: state.pressure.memory.availableBytes === null ? "memory-pressure" : "memory-available",
144
+ value: state.pressure.memory.availableBytes === null ? state.pressure.memory.level : state.pressure.memory.availableBytes / 1024 ** 3,
145
+ threshold: state.pressure.memory.availableBytes === null ? settings.memoryPressureLevel : settings.minAvailableMemoryGb ?? "10% of RAM",
146
+ unit: state.pressure.memory.availableBytes === null ? "level" : "GiB"
147
+ } });
148
+ } else {
149
+ measuredSizes = true;
150
+ state.sizes = measureSizes(state.lastAt[check], (target, workspace) => {
151
+ failures.push(`Could not measure ${target}`);
152
+ record("maintenance_failure", "error", `Could not measure ${target}`, {
153
+ target,
154
+ error: "du failed or timed out",
155
+ ...workspace ? { workspace } : {}
156
+ });
157
+ });
158
+ for (const size of state.sizes) record("maintenance_check", "debug", `${size.name}: ${formatBytes(size.bytes)}`, {
159
+ check: {
160
+ kind: `size:${size.category}`,
161
+ value: size.bytes,
162
+ unit: "bytes"
163
+ },
164
+ target: size.dir
165
+ });
166
+ }
167
+ } catch (error) {
168
+ const message = error instanceof Error ? error.message : String(error);
169
+ failures.push(message);
170
+ record("maintenance_failure", "error", `${check} check failed: ${message}`, {
171
+ target: check,
172
+ error: message
173
+ });
174
+ }
175
+ }
176
+ let result;
177
+ try {
178
+ result = await plannedMaintenance(state.pressure, state.sizes, settings);
179
+ } catch (error) {
180
+ const message = error instanceof Error ? error.message : String(error);
181
+ failures.push(message);
182
+ record("maintenance_failure", "error", `Could not plan maintenance: ${message}`, {
183
+ target: "plan",
184
+ error: message
185
+ });
186
+ result = {
187
+ actions: [],
188
+ blocked: [],
189
+ skips: []
190
+ };
191
+ }
192
+ const previousActions = new Set(state.plan.map(actionKey));
193
+ const nextActions = new Set(result.actions.map(actionKey));
194
+ const previousSkips = new Set(state.skipKeys ?? []);
195
+ const nextSkips = new Set(result.skips.map((skip) => skip.target));
196
+ const blocked = [...result.blocked, ...failures];
197
+ const changed = !state.lastPass || !sameSet(previousActions, nextActions) || !sameSet(previousSkips, nextSkips) || !sameSet(new Set(state.lastPass.blocked.map(stableText)), new Set(blocked.map(stableText)));
198
+ for (const action of result.actions) {
199
+ if (previousActions.has(actionKey(action))) continue;
200
+ previousActions.add(actionKey(action));
201
+ record("maintenance_action", "info", `Would ${action.kind === "would-clear-outputs" ? `clear build outputs of ${basename(action.workspace ?? action.target)}` : `${action.kind.slice(6).replaceAll("-", " ")} ${action.target}`} (${formatBytes(action.bytes)}): ${action.reason}`, {
202
+ action: {
203
+ ...action,
204
+ durationMs: 0
205
+ },
206
+ ...action.workspace ? { workspace: action.workspace } : {}
207
+ });
208
+ }
209
+ for (const skip of result.skips) {
210
+ if (previousSkips.has(skip.target)) continue;
211
+ previousSkips.add(skip.target);
212
+ record("maintenance_skip", "info", `Kept ${skip.target}: ${skip.reason}`, skip);
213
+ }
214
+ state.plan = result.actions;
215
+ state.skipKeys = [...nextSkips];
216
+ state.lastPass = {
217
+ startedAt,
218
+ durationMs: Date.now() - startedAt,
219
+ trigger,
220
+ mode: "report",
221
+ freedBytes: 0,
222
+ actions: result.actions.length,
223
+ stopped: 0,
224
+ blocked
225
+ };
226
+ if (measuredSizes || changed) record("maintenance_pass", state.lastPass.blocked.length ? "warn" : "info", `Report-only pass: ${result.actions.length} planned actions${state.lastPass.blocked.length ? `; ${state.lastPass.blocked.join("; ")}` : ""}`, { ...state.lastPass });
227
+ writeState(state);
228
+ } catch (error) {
229
+ const message = error instanceof Error ? error.message : String(error);
230
+ try {
231
+ logger?.write("maintenance_failure", "error", `Maintenance failed: ${message}`, {
232
+ target: "pass",
233
+ error: message
234
+ });
235
+ } catch {}
236
+ crashLine(error);
237
+ } finally {
238
+ logger?.close();
239
+ releaseClaim(claim);
240
+ }
241
+ }
242
+ function crashLine(error) {
243
+ try {
244
+ mkdirSync(maintenanceDir(), { recursive: true });
245
+ const file = maintenanceChildLogFile();
246
+ capChildLog();
247
+ appendFileSync(file, `${String(error).replaceAll("\n", " ")}\n`);
248
+ } catch {}
249
+ }
250
+ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
251
+ if (!relaunchWithLogFile(process.argv.slice(2))) await runMaintenance(process.argv[2] ?? "command");
252
+ }
253
+ //#endregion
254
+ export { runMaintenance };
@@ -1,116 +1,11 @@
1
- import { N as isOnMountedVolume, O as process_identity_exports, P as listMountedVolumes, _ as loadConfig$1, b as releaseMetroPort, n as allMetroPorts, r as claimMetroPort } from "./config-lvc7d3C2.mjs";
2
- import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
- import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
4
- import { existsSync as existsSync$1, readlinkSync as readlinkSync$1, realpathSync as realpathSync$1 } from "fs";
5
- import { join, resolve, sep } from "path";
6
- import { buildCacheRoot as sharedBuildCache, withDirLock, workspaceId, workspaceName as workspaceName$1, workspaceStateDir as workspaceDir } from "@stim-cli/core";
7
- import { emulatorLogFile, readJsonObject, readWorkspaceState, readWorkspaceState as readWorkspaceState$1, sharedCcache, sharedCompilationCache, supervisorLogFile, supervisorPidFile, workspaceDerivedData, workspaceLastUsed, workspaceLogErrorIndex, workspaceLogsDir, workspaceMetadataFile, workspaceStateFile, workspaceStateLock } from "@stim-cli/core/state";
8
- import { dirname as dirname$1 } from "node:path";
1
+ import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
+ import { M as isOnMountedVolume, N as listMountedVolumes, _ as loadConfig, n as allMetroPorts, r as claimMetroPort, x as releaseMetroPort } from "./config-D0DbpZtM.mjs";
3
+ import { t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
4
+ import { r as readWorkspaceState } from "./workspace-state-B8-aJ1_5.mjs";
5
+ import { existsSync, readlinkSync, realpathSync } from "fs";
6
+ import { sep } from "path";
9
7
  import { request } from "http";
10
8
  import { connect } from "net";
11
- //#region src/workspace/paths.ts
12
- function ensureWorkspaceStorage(projectRoot) {
13
- const canonicalRoot = resolve(projectRoot);
14
- const dir = workspaceDir(canonicalRoot);
15
- const file = workspaceMetadataFile(canonicalRoot);
16
- mkdirSync(dir, { recursive: true });
17
- return withDirLock(join(dir, "metadata.lock"), () => {
18
- if (!existsSync(file)) {
19
- const metadata = {
20
- projectRoot: canonicalRoot,
21
- workspace: workspaceName$1(canonicalRoot),
22
- version: 1
23
- };
24
- const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
25
- writeFileSync(tmp, `${JSON.stringify(metadata, null, 2)}\n`);
26
- try {
27
- renameSync(tmp, file);
28
- } catch (error) {
29
- rmSync(tmp, { force: true });
30
- throw error;
31
- }
32
- return dir;
33
- }
34
- if (readJsonObject(file)?.projectRoot === canonicalRoot) return dir;
35
- const error = /* @__PURE__ */ new Error(`Stim workspace collision at ${dir}: its workspace.json does not belong to ${canonicalRoot}.`);
36
- error.code = "STIM_WORKSPACE_COLLISION";
37
- throw error;
38
- });
39
- }
40
- //#endregion
41
- //#region src/workspace/workspace-state.ts
42
- function withWorkspaceStateLock(root, fn) {
43
- const file = workspaceStateFile(root);
44
- return withDirLock(workspaceStateLock(root), fn, { ensureParent: () => {
45
- ensureWorkspaceStorage(root);
46
- mkdirSync(dirname$1(file), { recursive: true });
47
- } });
48
- }
49
- function writeWorkspaceState(root, patch) {
50
- return updateWorkspaceState(root, (state) => ({
51
- ...state,
52
- ...patch
53
- }));
54
- }
55
- function updateWorkspaceState(root, update) {
56
- return withWorkspaceStateLock(root, () => replaceWorkspaceState(root, update(readWorkspaceState(root) ?? {})));
57
- }
58
- function replaceWorkspaceState(root, state) {
59
- const file = workspaceStateFile(root);
60
- mkdirSync(dirname$1(file), { recursive: true });
61
- const tmp = `${file}.tmp-${process.pid}`;
62
- writeFileSync(tmp, `${JSON.stringify(state, null, 2)}\n`);
63
- renameSync(tmp, file);
64
- return state;
65
- }
66
- function clearWorkspaceStateKeys(root, keys) {
67
- if (!existsSync(workspaceStateFile(root))) return;
68
- withWorkspaceStateLock(root, () => {
69
- const state = readWorkspaceState(root);
70
- const file = workspaceStateFile(root);
71
- if (!state) {
72
- try {
73
- rmSync(file, { force: true });
74
- } catch {}
75
- return;
76
- }
77
- let changed = false;
78
- for (const key of keys) {
79
- if (!(key in state)) continue;
80
- delete state[key];
81
- changed = true;
82
- }
83
- if (!changed) return;
84
- if (Object.keys(state).length === 0) {
85
- rmSync(file, { force: true });
86
- return;
87
- }
88
- replaceWorkspaceState(root, state);
89
- });
90
- }
91
- function clearWorkspaceStateKey(root, key, shouldClear) {
92
- return withWorkspaceStateLock(root, () => {
93
- const state = readWorkspaceState(root);
94
- if (!state || !(key in state)) return true;
95
- if (!shouldClear(state[key])) return false;
96
- delete state[key];
97
- const file = workspaceStateFile(root);
98
- if (Object.keys(state).length === 0) {
99
- try {
100
- rmSync(file, { force: true });
101
- } catch {}
102
- return true;
103
- }
104
- replaceWorkspaceState(root, state);
105
- return true;
106
- });
107
- }
108
- function recordWorkspaceUse(root, now = /* @__PURE__ */ new Date()) {
109
- try {
110
- writeWorkspaceState(root, { lastUsedAt: now.toISOString() });
111
- } catch {}
112
- }
113
- //#endregion
114
9
  //#region src/ports.ts
115
10
  function isMetroRunning(port) {
116
11
  return new Promise((resolve) => {
@@ -161,14 +56,14 @@ async function computeNextPort(isFree = isPortFree) {
161
56
  throw new Error(`Found no free Metro port between ${FIRST_PORT} and 8281. Free one up, or stop a stale bundler (\`stim status\`, \`stim stop <port>\`).`);
162
57
  }
163
58
  async function findReclaimablePort(excludeProjectPath, probe = isMetroRunning, { isMounted = isOnMountedVolume, mountedVolumes } = {}) {
164
- const cfg = loadConfig$1();
59
+ const cfg = loadConfig();
165
60
  if (!cfg?.projects) return null;
166
61
  const mounted = mountedVolumes || listMountedVolumes();
167
62
  const candidates = [];
168
63
  for (const [path, proj] of Object.entries(cfg.projects)) {
169
64
  if (path === excludeProjectPath) continue;
170
65
  if (Object.keys(proj.ports ?? {}).length) continue;
171
- if (existsSync$1(path)) continue;
66
+ if (existsSync(path)) continue;
172
67
  if (!isMounted(path, mounted)) continue;
173
68
  if (typeof proj.metroPort === "number") candidates.push({
174
69
  port: proj.metroPort,
@@ -187,7 +82,11 @@ async function allocatePort(projectPath, probe = isMetroRunning, isFree = isPort
187
82
  return computeNextPort(isFree);
188
83
  }
189
84
  const RESERVE_ATTEMPTS = 5;
190
- async function reserveMetroPort(projectPath, probe = isMetroRunning, isFree = isPortFree) {
85
+ async function reserveMetroPort(projectPath, probe = isMetroRunning, isFree = isPortFree, pinned = null) {
86
+ if (pinned !== null) {
87
+ if (claimMetroPort(projectPath, pinned) !== null) return pinned;
88
+ throw new Error(`metro.port ${pinned} is already reserved by another workspace's Metro or named port.`);
89
+ }
191
90
  for (let attempt = 0; attempt < RESERVE_ATTEMPTS; attempt++) {
192
91
  const port = await allocatePort(projectPath, probe, isFree);
193
92
  const claimed = claimMetroPort(projectPath, port);
@@ -296,7 +195,7 @@ async function processCwds(pids) {
296
195
  for (const pid of new Set(pids)) {
297
196
  let cwd = null;
298
197
  if (process.platform === "linux") try {
299
- cwd = readlinkSync$1(`/proc/${pid}/cwd`);
198
+ cwd = readlinkSync(`/proc/${pid}/cwd`);
300
199
  } catch {}
301
200
  if (cwd) cwds.set(pid, cwd);
302
201
  else viaLsof.push(pid);
@@ -317,13 +216,13 @@ async function processCwds(pids) {
317
216
  }
318
217
  function processCwd(pid) {
319
218
  if (process.platform === "linux") try {
320
- return readlinkSync$1(`/proc/${pid}/cwd`);
219
+ return readlinkSync(`/proc/${pid}/cwd`);
321
220
  } catch {}
322
221
  return parseLsofCwd(getExecutor().runQuiet(`lsof -a -p ${pid} -d cwd -Fn`));
323
222
  }
324
223
  function canonicalPath(path) {
325
224
  try {
326
- return realpathSync$1(path);
225
+ return realpathSync(path);
327
226
  } catch {
328
227
  return path;
329
228
  }
@@ -338,7 +237,7 @@ const NOT_OURS_UNRESPONSIVE = "unresponsive";
338
237
  const NOT_OURS_UNREADABLE_CWD = "unreadable-cwd";
339
238
  const NOT_OURS_FOREIGN_CWD = "foreign-cwd";
340
239
  function ownedSupervisor(projectPath, port) {
341
- const supervisor = readWorkspaceState$1(canonicalPath(projectPath))?.supervisor;
240
+ const supervisor = readWorkspaceState(canonicalPath(projectPath))?.supervisor;
342
241
  if (supervisor?.port !== port || (0, process_identity_exports.inspectProcessIdentity)(supervisor) !== "same") return null;
343
242
  return supervisor;
344
243
  }
@@ -368,7 +267,8 @@ async function resolveProjectMetro(port, projectPath, { probe = isMetroRunning,
368
267
  if (!isInsideProject(cwd, projectPath)) return {
369
268
  notOurs: `pid ${pid} on port ${port} runs from ${cwd}, outside ${projectPath}`,
370
269
  kind: NOT_OURS_FOREIGN_CWD,
371
- pid
270
+ pid,
271
+ cwd
372
272
  };
373
273
  const owner = ownedSupervisor(projectPath, port);
374
274
  return { metro: {
@@ -406,4 +306,4 @@ function killMetroTree(leader, processToken) {
406
306
  }
407
307
  }
408
308
  //#endregion
409
- export { workspaceId as A, sharedBuildCache as C, supervisorPidFile as D, supervisorLogFile as E, withDirLock as F, workspaceLogsDir as M, workspaceName$1 as N, workspaceDerivedData as O, workspaceStateFile as P, ensureWorkspaceStorage as S, sharedCompilationCache as T, updateWorkspaceState as _, pidExists as a, writeWorkspaceState as b, resolveProjectMetro as c, isPortFree as d, reserveMetroPort as f, recordWorkspaceUse as g, readWorkspaceState$1 as h, listeningPidsByPort as i, workspaceLogErrorIndex as j, workspaceDir as k, signalProcessTree as l, clearWorkspaceStateKeys as m, killMetroTree as n, processCwd as o, clearWorkspaceStateKey as p, listeningPids as r, processCwds as s, NOT_OURS_FOREIGN_CWD as t, isMetroRunning as u, withWorkspaceStateLock as v, sharedCcache as w, emulatorLogFile as x, workspaceLastUsed as y };
309
+ export { listeningPidsByPort as a, processCwds as c, isMetroRunning as d, isPortFree as f, listeningPids as i, resolveProjectMetro as l, isInsideProject as n, pidExists as o, reserveMetroPort as p, killMetroTree as r, processCwd as s, NOT_OURS_FOREIGN_CWD as t, signalProcessTree as u };
@@ -0,0 +1,84 @@
1
+ import { t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
2
+ import { a as updateWorkspaceState, r as readWorkspaceState$1 } from "./workspace-state-B8-aJ1_5.mjs";
3
+ import { b as realIo } from "./client-BTY686R6.mjs";
4
+ import { randomBytes, randomUUID } from "node:crypto";
5
+ import { HOSTED_METRO_REQUESTS_KEY, findPeer, hostedMetroGateways, hostedMetroRequests, isJsonObject, parseMachine } from "@stim-cli/core/state";
6
+ import { isIP } from "node:net";
7
+ //#region src/device-host/metro-gateway.ts
8
+ function gatewayAddresses(credential, status) {
9
+ const machine = parseMachine(credential.machine);
10
+ const peer = machine ? findPeer(status, machine.name) : "missing";
11
+ if (typeof peer === "string" || peer.nodeId !== credential.nodeId) throw new Error(`${credential.machine} no longer resolves to its pinned tailnet node. Run stim doctor.`);
12
+ const self = isJsonObject(status) && isJsonObject(status.Self) ? status.Self : null;
13
+ const address = (Array.isArray(self?.TailscaleIPs) ? self.TailscaleIPs : []).find((ip) => typeof ip === "string" && isIP(ip) === isIP(peer.address) && isIP(ip) !== 0);
14
+ if (!address) throw new Error("This Mac has no Tailscale address compatible with the hosting Mac.");
15
+ return {
16
+ address,
17
+ peer: peer.address
18
+ };
19
+ }
20
+ function requireHostedMetro(root) {
21
+ const supervisor = readWorkspaceState$1(root)?.supervisor;
22
+ if (supervisor?.hostedMetro === true && (0, process_identity_exports.inspectProcessIdentity)(supervisor) === "same") return;
23
+ throw Object.assign(/* @__PURE__ */ new Error("Hosted Debug runs require a running Metro supervisor with private gateway support. Run stim stop; stim start, then retry."), {
24
+ code: "STIM_HOSTING_REFUSED",
25
+ remedy: "Run stim stop; stim start, then retry."
26
+ });
27
+ }
28
+ async function requestHostedMetro(root, session, credential) {
29
+ requireHostedMetro(root);
30
+ const addresses = gatewayAddresses(credential, realIo.status());
31
+ const previous = hostedMetroRequests(readWorkspaceState$1(root))[session];
32
+ const request = previous && previous.address === addresses.address && previous.peer === addresses.peer ? previous : {
33
+ id: randomUUID(),
34
+ machine: credential.machine,
35
+ ...addresses,
36
+ secret: randomBytes(32).toString("hex")
37
+ };
38
+ updateWorkspaceState(root, (state) => ({
39
+ ...state,
40
+ [HOSTED_METRO_REQUESTS_KEY]: {
41
+ ...hostedMetroRequests(state),
42
+ [session]: request
43
+ }
44
+ }));
45
+ const deadline = Date.now() + 15e3;
46
+ while (Date.now() < deadline) {
47
+ const state = readWorkspaceState$1(root);
48
+ const gateway = hostedMetroGateways(state)[session];
49
+ if (gateway?.id === request.id && gateway.processToken === state?.supervisor?.processToken) {
50
+ if (gateway.error) throw new Error(gateway.error);
51
+ if (gateway.port) return {
52
+ gatewayPort: gateway.port,
53
+ secret: request.secret
54
+ };
55
+ }
56
+ await new Promise((done) => setTimeout(done, 100));
57
+ }
58
+ throw new Error(`The Metro supervisor did not open a private gateway for ${credential.machine}. Run stim stop; stim start, then retry.`);
59
+ }
60
+ function clearHostedMetro(root, session) {
61
+ updateWorkspaceState(root, (state) => {
62
+ const requests = { ...hostedMetroRequests(state) };
63
+ delete requests[session];
64
+ return {
65
+ ...state,
66
+ [HOSTED_METRO_REQUESTS_KEY]: requests
67
+ };
68
+ });
69
+ }
70
+ async function closeHostedMetro(root, session) {
71
+ clearHostedMetro(root, session);
72
+ const deadline = Date.now() + 5e3;
73
+ while (true) {
74
+ const state = readWorkspaceState$1(root);
75
+ const gateway = hostedMetroGateways(state)[session];
76
+ const supervisor = state?.supervisor;
77
+ if (!gateway?.port || !supervisor || gateway.processToken !== supervisor.processToken) return;
78
+ if ((0, process_identity_exports.inspectProcessIdentity)(supervisor) === "gone") return;
79
+ if (Date.now() >= deadline) throw new Error("The Metro supervisor did not close its private gateway. Restart Metro, then retry stim stop.");
80
+ await new Promise((done) => setTimeout(done, 100));
81
+ }
82
+ }
83
+ //#endregion
84
+ export { requireHostedMetro as i, closeHostedMetro as n, requestHostedMetro as r, clearHostedMetro as t };
@@ -1,62 +1,9 @@
1
- import { A as ownership_claim_exports, E as withConfigLock, O as process_identity_exports, S as saveConfig, _ as loadConfig, c as ensureConfig, d as getConfigDir, p as getProject } from "./config-lvc7d3C2.mjs";
2
- import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
- import { d as isPortFree, l as signalProcessTree, r as listeningPids } from "./metro-CcEo8Fl8.mjs";
4
- import { n as declareSpawnsOn, o as stopDeclaringSpawnsOn } from "./spawn-claims-Drw_o-Fb.mjs";
1
+ import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
+ import { C as saveConfig, D as withConfigLock, _ as loadConfig, c as ensureConfig, d as getConfigDir, p as getProject } from "./config-D0DbpZtM.mjs";
3
+ import { t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
4
+ import { f as isPortFree, i as listeningPids, u as signalProcessTree } from "./metro-BeK1ZPz1.mjs";
5
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BL7Q0HCO.mjs";
5
6
  import { realpathSync } from "node:fs";
6
- import { join } from "node:path";
7
- //#region src/engine/workspace-process-lock.ts
8
- const DEFAULT_WAIT_MS = 6e4;
9
- const POLL_MS = 25;
10
- const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
11
- function workspaceProcessLockError(err) {
12
- const code = err?.code;
13
- if (code === "STIM_LOCK_REFUSED") return "refused";
14
- if (code === "STIM_LOCK_TIMEOUT") return "timeout";
15
- return null;
16
- }
17
- function workspaceProcessLockPath(root, name, external) {
18
- return external ? join(root, `${name}.lock`) : join(root, ".stim", `${name}.lock`);
19
- }
20
- async function withWorkspaceProcessLock(root, name, fn, { now = Date.now, sleep = defaultSleep, waitMs = DEFAULT_WAIT_MS, ownerPurpose, details = {}, onHeld, rejectOwnerPurposes = [], external = false, declareSpawns = false } = {}) {
21
- const path = workspaceProcessLockPath(root, name, external);
22
- const deadline = now() + waitMs;
23
- for (;;) {
24
- const attempt = (0, ownership_claim_exports.tryAcquireClaim)({
25
- root: path,
26
- mode: "exclusive",
27
- label: `${name} lock`,
28
- details: ownerPurpose ? {
29
- ...details,
30
- purpose: ownerPurpose
31
- } : details
32
- });
33
- if (attempt.pending) (0, ownership_claim_exports.releaseClaim)(attempt.pending);
34
- if (attempt.acquired) {
35
- if (declareSpawns) declareSpawnsOn(attempt.acquired);
36
- try {
37
- return await fn(attempt.acquired);
38
- } finally {
39
- stopDeclaringSpawnsOn(attempt.acquired);
40
- (0, ownership_claim_exports.releaseClaim)(attempt.acquired);
41
- }
42
- }
43
- const holder = attempt.held ?? attempt.waitingFor?.[0];
44
- const purpose = holder?.details.purpose;
45
- if (typeof purpose === "string" && rejectOwnerPurposes.includes(purpose)) {
46
- const error = /* @__PURE__ */ new Error(`The ${name} lock at ${path} is held for ${purpose}.`);
47
- error.code = "STIM_LOCK_REFUSED";
48
- throw error;
49
- }
50
- if (holder && onHeld) await onHeld(holder);
51
- if (now() >= deadline) {
52
- const error = /* @__PURE__ */ new Error(`Timed out waiting for the ${name} lock at ${path}.`);
53
- error.code = "STIM_LOCK_TIMEOUT";
54
- throw error;
55
- }
56
- await sleep(POLL_MS);
57
- }
58
- }
59
- //#endregion
60
7
  //#region src/named-ports.ts
61
8
  const FIRST_PORT = 8900;
62
9
  const LAST_PORT = 8999;
@@ -218,4 +165,4 @@ async function clearNamedPorts(projectPath, { label, stop = false, dryRun = fals
218
165
  });
219
166
  }
220
167
  //#endregion
221
- export { withWorkspaceProcessLock as a, reserveBrowserPort as i, getNamedPort as n, workspaceProcessLockError as o, releaseBrowserPort as r, workspaceProcessLockPath as s, clearNamedPorts as t };
168
+ export { reserveBrowserPort as i, getNamedPort as n, releaseBrowserPort as r, clearNamedPorts as t };