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.
- package/README.md +2 -0
- package/dist/activity-BxwdD81S.mjs +609 -0
- package/dist/agent-device-usage-output-Dpih1Tfa.mjs +411 -0
- package/dist/{android-d6rGq1pA.mjs → android-B_NumVyQ.mjs} +169 -81
- package/dist/{android-LbPFRWVL.mjs → android-Ift4kVvt.mjs} +517 -413
- package/dist/android-cas-8U4DPqeR.mjs +167 -0
- package/dist/android-cas-compiler.mjs +2 -2
- package/dist/{android-XpvCrV7q.mjs → android-r2rBL8kV.mjs} +2 -2
- package/dist/{app-install-BpgSgkJj.mjs → app-install-CKWdbXIX.mjs} +128 -151
- package/dist/attempt-Bk2ouQVL.mjs +39 -0
- package/dist/budget-DjrURfQn.mjs +2700 -0
- package/dist/{build-plan-Dcr-3ie0.mjs → build-plan-DjeSb2Xl.mjs} +192 -653
- package/dist/build-slots-rUk77xqh.mjs +132 -0
- package/dist/{cache-manifest-C68QQ0ua.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
- package/dist/cache-manifest.mjs +1 -1
- package/dist/{chrome-BPCiC3ZX.mjs → chrome-BrcVWQyD.mjs} +1 -1
- package/dist/cli-BkYZ_6hy.mjs +51 -0
- package/dist/cli.mjs +6 -42
- package/dist/client-BTY686R6.mjs +1368 -0
- package/dist/collector-run.mjs +9 -195
- package/dist/{command-output-DVcpw6eE.mjs → command-output-DnJMdEY-.mjs} +1 -1
- package/dist/{config-lvc7d3C2.mjs → config-D0DbpZtM.mjs} +28 -29
- package/dist/{created-devices-CllH_8eb.mjs → created-devices-D5owjN4C.mjs} +2 -2
- package/dist/{dependency-state-DsT7aKeo.mjs → dependency-state-B6cMXZab.mjs} +2 -2
- package/dist/{deps-D9IcKqXS.mjs → deps-urbmfXig.mjs} +3 -3
- package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
- package/dist/dev-client-CroAIjjj.mjs +43 -0
- package/dist/{device-CjuuAJjQ.mjs → device-DZQjF7H5.mjs} +8 -7
- package/dist/device-capacity-BHwxNBlu.mjs +136 -0
- package/dist/device-host-worker.d.mts +1 -0
- package/dist/device-host-worker.mjs +964 -0
- package/dist/device-ios-BadmeL2V.mjs +381 -0
- package/dist/{device-lease-5c3WrVRm.mjs → device-lease-D-hLqx1b.mjs} +43 -36
- package/dist/{device-pool-BiyDyPP-.mjs → device-lease-run-CqjShqWW.mjs} +6 -126
- package/dist/device-pool-CUaEWMMm.mjs +125 -0
- package/dist/{device-remote-D87zEzaq.mjs → device-remote-D5WVWqOq.mjs} +69 -163
- package/dist/{doctor-x9pnU9fq.mjs → doctor-Cimf-gGB.mjs} +241 -103
- package/dist/{error-diagnostics-C5HzcwCW.mjs → error-diagnostics-B9L4Ae0x.mjs} +15 -18
- package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
- package/dist/{gc-ko6mUAYD.mjs → gc-Bqy4bjEu.mjs} +599 -108
- package/dist/{android-cas-iZK3DOCr.mjs → gradle-BsO1MdsH.mjs} +8 -294
- package/dist/{guide-6R0VNFqI.mjs → guide-B_uyKTxW.mjs} +2511 -239
- package/dist/{guide-status-BZrFPIA_.mjs → guide-status-CjEZMAD4.mjs} +3 -3
- package/dist/hosted-client-a-WmqE2L.mjs +197 -0
- package/dist/hosted-ios-CIWelIqf.mjs +220 -0
- package/dist/hosted-logs-D5g041Q3.mjs +187 -0
- package/dist/hosted-macos-BSZyIb3y.mjs +135 -0
- package/dist/idle-DYVymG92.mjs +1099 -0
- package/dist/idle-shutdown-CTsDnAde.mjs +122 -0
- package/dist/in-use-DvBw0Kxo.mjs +130 -0
- package/dist/{ios-BwDPrnYQ.mjs → ios-BaInn3sW.mjs} +201 -10
- package/dist/ios-Cr1h5gnO.mjs +189 -0
- package/dist/{ios-P1f5nm2n.mjs → ios-aq3QoGI3.mjs} +1159 -633
- package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
- package/dist/{ios-device-DGglU3E_.mjs → ios-device-FfCN_C8p.mjs} +12 -7
- package/dist/ios-state-DS5UpYTH.mjs +48 -0
- package/dist/{launch-verify-Deg4tB3Z.mjs → launch-verify-2DBRizjo.mjs} +5 -5
- package/dist/logs-CHEVK8zD.mjs +371 -0
- package/dist/machines-BIBUdhpj.mjs +265 -0
- package/dist/macos-CSYEAFmF.mjs +473 -0
- package/dist/macos-run.d.mts +5 -0
- package/dist/macos-run.mjs +2 -0
- package/dist/maintenance-run.d.mts +4 -0
- package/dist/maintenance-run.mjs +254 -0
- package/dist/{metro-CcEo8Fl8.mjs → metro-BeK1ZPz1.mjs} +20 -120
- package/dist/metro-gateway-BJ-rxQAB.mjs +84 -0
- package/dist/{named-ports-Blg8IxOh.mjs → named-ports-Bk_YXEmE.mjs} +6 -59
- package/dist/native-run-NaBrRFtQ.mjs +147 -0
- package/dist/{native-runtime-DjBHRg5m.mjs → native-runtime-DBzRnEKS.mjs} +9 -5
- package/dist/{ndjson-CDY9IkVC.mjs → ndjson-PclOIwVY.mjs} +2 -2
- package/dist/offload-worker.d.mts +122 -0
- package/dist/offload-worker.mjs +606 -0
- package/dist/{ownership-DyYDF5ww.mjs → ownership-DiA16AsN.mjs} +3 -2
- package/dist/ownership-hCkPzGmr.mjs +571 -0
- package/dist/{page-BHUzTDFt.mjs → page-QF_Ke10s.mjs} +7 -2
- package/dist/{ports-BguS83ex.mjs → ports-R2k4Knz3.mjs} +3 -3
- package/dist/prebuild-D2fsca_Z.mjs +213 -0
- package/dist/preview-DhVbvPAZ.mjs +318 -0
- package/dist/process-identity-BmRm1qV0.mjs +12 -0
- package/dist/{project-CWlvEaeN.mjs → project-Du91Ilvc.mjs} +74 -19
- package/dist/pull-request-pxnDCQzn.mjs +322 -0
- package/dist/pull-requests.d.mts +120 -0
- package/dist/pull-requests.mjs +2 -0
- package/dist/recordings-BIXOzVnr.mjs +37 -0
- package/dist/{reload-BiniwsSZ.mjs → reload-rM9jV1fz.mjs} +109 -24
- package/dist/{remote-cache-CcR6o6lI.mjs → remote-cache-C8-V13XN.mjs} +5 -5
- package/dist/run-f4vI6EnK.mjs +119 -0
- package/dist/{server-bare-DGfgyJ5J.mjs → server-bare-CpPGmnmO.mjs} +33 -15
- package/dist/server-expo-CVqO2c35.mjs +2 -0
- package/dist/{server-expo-BVuwQ65v.mjs → server-expo-MC0rHlOC.mjs} +8 -12
- package/dist/settings-BBpMcDbD.mjs +25 -0
- package/dist/{settings-B-fsC-Om.mjs → settings-ZkDzkzvB.mjs} +22 -10
- package/dist/settings-oTSkkNrM.mjs +1439 -0
- package/dist/settings.schema.json +845 -97
- package/dist/{simslim-DrDN4WuV.mjs → simslim-B8-7AoTI.mjs} +8 -132
- package/dist/{slot-launch-D5t8EiMf.mjs → slot-launch-B8VrHxn1.mjs} +11 -8
- package/dist/{spawn-claims-Drw_o-Fb.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
- package/dist/spawn-entry-jw19m8Nl.mjs +17 -0
- package/dist/stage-CzaiwFsI.mjs +143 -0
- package/dist/start-CX8Ig-U2.mjs +2 -0
- package/dist/{start-s4RXp1wy.mjs → start-ClDfwXa4.mjs} +116 -65
- package/dist/state-3DwSQIBK.mjs +41 -0
- package/dist/state-B4PQFXMW.d.mts +12 -0
- package/dist/{state-Dk9RVXI7.mjs → state-C1jB7Q8q.mjs} +70 -8
- package/dist/{state-DHqvsbqC.mjs → state-DcW6vOd_.mjs} +7 -4
- package/dist/stats-CEZ_i5je.mjs +77 -0
- package/dist/{status-BgAuxESg.mjs → status-BuvBFeII.mjs} +118 -20
- package/dist/{status-BsPAnfOI.mjs → status-Cs7VLg9D.mjs} +542 -283
- package/dist/status-DlKvVJBE.mjs +49 -0
- package/dist/stdout-reader-C8yjre1x.mjs +23 -0
- package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
- package/dist/stim-footprint.swift +32 -0
- package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
- package/dist/stop-B0Yxtmiy.mjs +48 -0
- package/dist/{stop-BzreOWJp.mjs → stop-Bffozsac.mjs} +83 -16
- package/dist/{stop-BWIF_6BK.mjs → stop-Dt_dM16A.mjs} +2 -2
- package/dist/stop-cause-CidxIqup.mjs +97 -0
- package/dist/supervisor-run.d.mts +43 -11
- package/dist/supervisor-run.mjs +363 -24
- package/dist/support-CAP8qDUF.mjs +316 -0
- package/dist/support-D4aYJniO.mjs +201 -0
- package/dist/teardown-CL6vDs2Y.mjs +727 -0
- package/dist/{xcode-Cp5KvK9x.mjs → toolchain-DkUflduf.mjs} +364 -14
- package/dist/trigger-Bemu8ytw.mjs +65 -0
- package/dist/trigger-CXts6qt1.mjs +2 -0
- package/dist/warm-progress-CpGv3Wb6.mjs +732 -0
- package/dist/{web-BsQs1WlO.mjs → web-DWUaMp8P.mjs} +37 -21
- package/dist/web-run.mjs +310 -13
- package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
- package/dist/workspace-state-B8-aJ1_5.mjs +371 -0
- package/dist/workspaces-BV4pdHFz.mjs +306 -0
- package/dist/{worktree-UrQWXSeb.mjs → worktree-K-xj9Db0.mjs} +625 -256
- package/dist/worktree-jnG8Iph5.mjs +2 -0
- package/package.json +10 -5
- package/shim/bundle-response.cjs +28 -12
- package/dist/activity-CYVDcrt3.mjs +0 -356
- package/dist/budget-CGDkhvv_.mjs +0 -1745
- package/dist/build-progress-DxKpwnaS.mjs +0 -247
- package/dist/idle-DXtx3RbX.mjs +0 -191
- package/dist/in-use-DlqBDKqY.mjs +0 -249
- package/dist/logs-Civ4gbBH.mjs +0 -486
- package/dist/merge-state-ytFQCeh4.mjs +0 -262
- package/dist/server-expo-DIHDhvGg.mjs +0 -2
- package/dist/settings-D61iNOsB.mjs +0 -629
- package/dist/start-Cetfvy8l.mjs +0 -2
- package/dist/stats-CFpFGlJV.mjs +0 -62
- package/dist/stats-CX1Z8Tca.mjs +0 -368
- package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
- package/dist/stop-cause-hjGBOZTG.mjs +0 -1231
- package/dist/worktree-BVrHsYZG.mjs +0 -752
- package/dist/worktree-CHWWvm5v.mjs +0 -2
- 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 {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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 {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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 {
|
|
168
|
+
export { reserveBrowserPort as i, getNamedPort as n, releaseBrowserPort as r, clearNamedPorts as t };
|