stim 1.0.0 → 1.2.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 +29 -10
- package/dist/android-BW8i_YxU.mjs +204 -0
- package/dist/android-Bcmev2s2.mjs +2334 -0
- package/dist/android-K63_0UfF.mjs +727 -0
- package/dist/android-cas-D6HPZ6UQ.mjs +1078 -0
- package/dist/android-cas-compiler.mjs +2 -2
- package/dist/app-install-NFxUahnH.mjs +1149 -0
- package/dist/build-lock-BsVoLDaq.mjs +645 -0
- package/dist/build-slots-D0b6sJyo.mjs +152 -0
- package/dist/{cache-manifest-4tH64LQ9.mjs → cache-manifest-Cu4_Znw9.mjs} +2 -2
- package/dist/cache-manifest.mjs +1 -1
- package/dist/cli.mjs +24 -27166
- package/dist/collector-run.d.mts +1 -0
- package/dist/collector-run.mjs +7 -5
- package/dist/command-output-8wHMEde0.mjs +289 -0
- package/dist/{config-7kmtuhO2.mjs → config-CMtIaTk9.mjs} +44 -13
- package/dist/deps-BLBsTNDS.mjs +435 -0
- package/dist/dev-client-RpvXxCXV.mjs +620 -0
- package/dist/device-CoCPmazS.mjs +250 -0
- package/dist/device-lease-Dpik41RY.mjs +335 -0
- package/dist/device-pool-B5pEVa3t.mjs +408 -0
- package/dist/device-remote-E81KAdXf.mjs +1209 -0
- package/dist/doctor-CqPeMsSm.mjs +1130 -0
- package/dist/doctor-D_CX7ib5.mjs +530 -0
- package/dist/error-diagnostics-Dw8Gn3yw.mjs +636 -0
- package/dist/{exec-bsN9MJXb.mjs → exec-CyylIdq9.mjs} +4 -2
- package/dist/gc-B7KkDinF.mjs +1665 -0
- package/dist/guide-BS2Xc24o.mjs +4503 -0
- package/dist/ios-B7KLgIp5.mjs +2733 -0
- package/dist/ios-D1h1Di39.mjs +451 -0
- package/dist/ios-device-D3pzTKQE.mjs +81 -0
- package/dist/ios-device-xNt0Lg6v.mjs +413 -0
- package/dist/logs-DcX9AvRF.mjs +176 -0
- package/dist/logs-query-oqn47Ffx.mjs +273 -0
- package/dist/metro-COslRK_F.mjs +184 -0
- package/dist/metro-store-CDDZHtPl.mjs +92 -0
- package/dist/ownership-CGPart6T.mjs +60 -0
- package/dist/ownership-CYOyqxUo.mjs +475 -0
- package/dist/ownership-claim-7zRxxTfZ.mjs +569 -0
- package/dist/{project-Dit1CckW.mjs → project-BBTVABUz.mjs} +12 -3
- package/dist/reclaim-C5Jmuahx.mjs +348 -0
- package/dist/reload-Z8nKifAk.mjs +332 -0
- package/dist/remote-cache-DDmJ1Roq.mjs +994 -0
- package/dist/{server-bare-BmTVVmBB.mjs → server-bare-DEk37Mz2.mjs} +8 -2
- package/dist/{server-expo-CHJe1r6j.mjs → server-expo-C3ktAwUG.mjs} +6 -5
- package/dist/server-expo-Drd7ksHu.mjs +2 -0
- package/dist/settings-DwU1_U_q.mjs +708 -0
- package/dist/spawn-entry-nCE6m770.mjs +14 -0
- package/dist/start-BrY4EuIg.mjs +822 -0
- package/dist/{state-2nush3MK.mjs → state-CZ-ER-8d.mjs} +21 -22
- package/dist/stats-BCWEVLrM.mjs +307 -0
- package/dist/stats-BsF15Bti.mjs +62 -0
- package/dist/status-DFI1Hfpg.mjs +393 -0
- package/dist/stop-B7DR4awK.mjs +3 -0
- package/dist/stop-Btg770vV.mjs +591 -0
- package/dist/supervisor-run.d.mts +0 -1
- package/dist/supervisor-run.mjs +4 -4
- package/dist/workspace-process-lock-BKfdys1q.mjs +47 -0
- package/dist/worktree-BPTbI-LN.mjs +1322 -0
- package/dist/worktree-C79xx_kM.mjs +543 -0
- package/dist/xcode-DeKCribl.mjs +1832 -0
- package/package.json +5 -5
- package/shim/bundle-response.cjs +5 -0
- package/dist/android-jc8MnUyL.mjs +0 -514
- package/dist/metro-store-COLl1pOk.mjs +0 -1249
- package/dist/server-expo-BZJm--wA.mjs +0 -2
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import { a as formatElapsed, r as clockTime } from "./command-output-8wHMEde0.mjs";
|
|
2
|
+
import { A as APP_READINESS_TIMEOUT_MS, l as VERIFY_TIMEOUT_MS } from "./app-install-NFxUahnH.mjs";
|
|
3
|
+
import { a as parseLease, c as releaseRunLease, d as selectPoolDevice, f as takeLease, n as fileLeaseIo, r as leaseIsExpired, s as raiseLease, t as deviceLeasePath } from "./device-lease-Dpik41RY.mjs";
|
|
4
|
+
//#region src/engine/device-lease-run.ts
|
|
5
|
+
const LEASE_STEP_FLOOR_MS = 6e4;
|
|
6
|
+
const DEBUG_VERIFY_STEP_MS = VERIFY_TIMEOUT_MS + APP_READINESS_TIMEOUT_MS;
|
|
7
|
+
const DEVICE_WAIT_POLL_MS = 2e3;
|
|
8
|
+
const DEVICE_BUSY = "STIM_DEVICE_BUSY";
|
|
9
|
+
const DEVICE_LOST = "STIM_DEVICE_LOST";
|
|
10
|
+
function leaseStepMs(boundMs = 0) {
|
|
11
|
+
return Math.max(LEASE_STEP_FLOOR_MS, boundMs);
|
|
12
|
+
}
|
|
13
|
+
function parseDeviceWait(value) {
|
|
14
|
+
if (value === void 0 || value === null) return { seconds: 60 };
|
|
15
|
+
const seconds = String(value).trim() === "" ? NaN : Number(value);
|
|
16
|
+
if (!Number.isInteger(seconds) || seconds < 0) return { error: `Invalid --wait value ${JSON.stringify(value)}. Pass a whole number of seconds, e.g. --wait 90.` };
|
|
17
|
+
return { seconds };
|
|
18
|
+
}
|
|
19
|
+
function waitFlagConflict(argv) {
|
|
20
|
+
return argv.some((arg) => arg === "--wait" || arg.startsWith("--wait=")) && argv.includes("--no-wait");
|
|
21
|
+
}
|
|
22
|
+
function leaseExpiryText(expiresAt, now) {
|
|
23
|
+
const remaining = Date.parse(expiresAt) - now;
|
|
24
|
+
return `${clockTime(expiresAt)} (${formatElapsed(Math.max(0, remaining))} from now)`;
|
|
25
|
+
}
|
|
26
|
+
function describeDevice(lease) {
|
|
27
|
+
return lease.deviceName ? `${lease.deviceName} (${lease.id})` : lease.id;
|
|
28
|
+
}
|
|
29
|
+
function facts(lease) {
|
|
30
|
+
return {
|
|
31
|
+
platform: lease.platform,
|
|
32
|
+
id: lease.id,
|
|
33
|
+
deviceName: lease.deviceName,
|
|
34
|
+
holder: lease.holder,
|
|
35
|
+
expiresAt: lease.expiresAt
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function waitingLine(lease, now) {
|
|
39
|
+
return `waiting for ${lease.holder} to release ${describeDevice(lease)}; its lease runs until ${leaseExpiryText(lease.expiresAt, now)} -- stim guide lifecycle lease`;
|
|
40
|
+
}
|
|
41
|
+
function appIdMatch(ours, theirs) {
|
|
42
|
+
if (!ours || !theirs) return "unknown";
|
|
43
|
+
return ours === theirs ? "same" : "different";
|
|
44
|
+
}
|
|
45
|
+
function bypassLines(lease, now, match) {
|
|
46
|
+
const consequence = {
|
|
47
|
+
same: "Both workspaces build the same app id, so this install terminates the app that workspace is running.",
|
|
48
|
+
different: "The other workspace's app id differs, so the launch below backgrounds it rather than ending it.",
|
|
49
|
+
unknown: "Stim cannot tell whether the two workspaces build the same app id; if they do, this install terminates the holder's running app."
|
|
50
|
+
}[match];
|
|
51
|
+
return [`--no-wait: ${lease.holder} holds ${describeDevice(lease)} until ${leaseExpiryText(lease.expiresAt, now)}, and this run is proceeding without a lease.`, consequence];
|
|
52
|
+
}
|
|
53
|
+
function busyRefusal({ lease, now, idLabel, waitSeconds }) {
|
|
54
|
+
return {
|
|
55
|
+
code: DEVICE_BUSY,
|
|
56
|
+
message: `${lease.holder} holds ${describeDevice(lease)} until ${leaseExpiryText(lease.expiresAt, now)}` + (waitSeconds > 0 ? `, and this command waited ${waitSeconds}s for it.` : "."),
|
|
57
|
+
remedy: `Wait longer with \`--wait <seconds>\`, pick another device with \`--device <${idLabel}>\`, or pass \`--no-wait\` to install anyway -- which takes no lease and, when both workspaces build the same app id, terminates the holder's running app.`,
|
|
58
|
+
lease: facts(lease)
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function unreadableRefusal(path) {
|
|
62
|
+
return {
|
|
63
|
+
code: DEVICE_BUSY,
|
|
64
|
+
message: `The lease file at ${path} does not parse, so Stim cannot tell who holds that device.`,
|
|
65
|
+
remedy: `Read ${path} and, once you know no run depends on it, delete it. \`stim gc\` reports it on every run.`,
|
|
66
|
+
lease: {
|
|
67
|
+
platform: null,
|
|
68
|
+
id: null,
|
|
69
|
+
deviceName: null,
|
|
70
|
+
holder: null,
|
|
71
|
+
expiresAt: null
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function untokenedRefusal(lease, now) {
|
|
76
|
+
return {
|
|
77
|
+
code: DEVICE_BUSY,
|
|
78
|
+
message: `This workspace holds ${describeDevice(lease)} until ${leaseExpiryText(lease.expiresAt, now)}, but its own record of that lease is gone, so this run cannot prove the lease is still its own.`,
|
|
79
|
+
remedy: "Run `stim device unlock`, which releases by holder, then run this command again.",
|
|
80
|
+
lease: facts(lease)
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function otherDeviceRefusal(leasedId, requestedId) {
|
|
84
|
+
return {
|
|
85
|
+
code: "STIM_NO_DEVICE",
|
|
86
|
+
message: `This workspace already leases ${leasedId}, and this run asked for ${requestedId}.`,
|
|
87
|
+
remedy: `Run \`stim device unlock\` to give up ${leasedId}, or use it with \`--device ${leasedId}\`.`,
|
|
88
|
+
lease: null
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function lostLine(holder, expiresAt, now) {
|
|
92
|
+
return `${holder ?? "another workspace"} took this device's lease${expiresAt ? ` until ${leaseExpiryText(expiresAt, now)}` : ""}. The app is already installed, so this run continues without one.`;
|
|
93
|
+
}
|
|
94
|
+
function lostRefusal(holder, expiresAt, now) {
|
|
95
|
+
const who = holder ?? "another workspace";
|
|
96
|
+
const until = expiresAt ? ` until ${leaseExpiryText(expiresAt, now)}` : "";
|
|
97
|
+
return {
|
|
98
|
+
code: DEVICE_LOST,
|
|
99
|
+
message: `${who} took this device's lease${until} before this run could install.`,
|
|
100
|
+
remedy: "Run this command again; it waits for that lease with `--wait <seconds>`.",
|
|
101
|
+
lease: null
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function runLease({ root, platform, kind, expiresAt, io = fileLeaseIo }) {
|
|
105
|
+
const handle = {
|
|
106
|
+
kind,
|
|
107
|
+
expiresAt,
|
|
108
|
+
lost: false,
|
|
109
|
+
raise(boundMs) {
|
|
110
|
+
if (handle.kind === null || handle.lost) return {
|
|
111
|
+
ok: !handle.lost,
|
|
112
|
+
holder: null,
|
|
113
|
+
expiresAt: handle.expiresAt
|
|
114
|
+
};
|
|
115
|
+
const result = raiseLease({
|
|
116
|
+
root,
|
|
117
|
+
platform,
|
|
118
|
+
minMs: leaseStepMs(boundMs)
|
|
119
|
+
}, io);
|
|
120
|
+
if (result.status === "raised") {
|
|
121
|
+
handle.expiresAt = result.lease.expiresAt;
|
|
122
|
+
return {
|
|
123
|
+
ok: true,
|
|
124
|
+
holder: result.lease.holder,
|
|
125
|
+
expiresAt: result.lease.expiresAt
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
handle.lost = true;
|
|
129
|
+
const taken = result.status === "lost" ? result.lease : null;
|
|
130
|
+
return {
|
|
131
|
+
ok: false,
|
|
132
|
+
holder: taken?.holder ?? null,
|
|
133
|
+
expiresAt: taken?.expiresAt ?? null
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
release() {
|
|
137
|
+
if (handle.kind === null) return;
|
|
138
|
+
releaseRunLease({
|
|
139
|
+
root,
|
|
140
|
+
platform
|
|
141
|
+
}, io);
|
|
142
|
+
},
|
|
143
|
+
facts() {
|
|
144
|
+
if (handle.kind === null || handle.lost || handle.expiresAt === null) return null;
|
|
145
|
+
return {
|
|
146
|
+
kind: handle.kind,
|
|
147
|
+
expiresAt: handle.expiresAt
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
return handle;
|
|
152
|
+
}
|
|
153
|
+
const SIGNAL_EXIT_CODES = {
|
|
154
|
+
SIGINT: 130,
|
|
155
|
+
SIGTERM: 143
|
|
156
|
+
};
|
|
157
|
+
function releaseLeaseOnSignal(release, { on = (signal, handler) => {
|
|
158
|
+
process.once(signal, handler);
|
|
159
|
+
}, off = (signal, handler) => {
|
|
160
|
+
process.off(signal, handler);
|
|
161
|
+
}, exit = (code) => process.exit(code) } = {}) {
|
|
162
|
+
const installed = [];
|
|
163
|
+
for (const signal of Object.keys(SIGNAL_EXIT_CODES)) {
|
|
164
|
+
const handler = () => {
|
|
165
|
+
release();
|
|
166
|
+
exit(SIGNAL_EXIT_CODES[signal]);
|
|
167
|
+
};
|
|
168
|
+
installed.push([signal, handler]);
|
|
169
|
+
on(signal, handler);
|
|
170
|
+
}
|
|
171
|
+
return () => {
|
|
172
|
+
for (const [signal, handler] of installed) off(signal, handler);
|
|
173
|
+
installed.length = 0;
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
async function waitForDevice({ root, platform, id, idLabel, waitSeconds, noWait = false, appId = null, holderAppId = () => null, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), warn = () => {}, io = fileLeaseIo, deadline, lastLine }) {
|
|
177
|
+
const path = deviceLeasePath(platform, id);
|
|
178
|
+
for (;;) {
|
|
179
|
+
const raw = io.readLease(path);
|
|
180
|
+
const current = parseLease(raw);
|
|
181
|
+
if (raw !== null && !current) return {
|
|
182
|
+
status: "refused",
|
|
183
|
+
refusal: unreadableRefusal(path)
|
|
184
|
+
};
|
|
185
|
+
if (!current || leaseIsExpired(current, now())) return { status: "free" };
|
|
186
|
+
if (current.holder === root) return {
|
|
187
|
+
status: "ours",
|
|
188
|
+
lease: current
|
|
189
|
+
};
|
|
190
|
+
if (noWait) {
|
|
191
|
+
for (const line of bypassLines(current, now(), appIdMatch(appId, holderAppId(current.holder)))) warn(line);
|
|
192
|
+
return {
|
|
193
|
+
status: "bypassed",
|
|
194
|
+
lease: current
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (now() >= deadline) return {
|
|
198
|
+
status: "refused",
|
|
199
|
+
refusal: busyRefusal({
|
|
200
|
+
lease: current,
|
|
201
|
+
now: now(),
|
|
202
|
+
idLabel,
|
|
203
|
+
waitSeconds
|
|
204
|
+
})
|
|
205
|
+
};
|
|
206
|
+
if (lastLine.at === null || now() - lastLine.at >= 3e4) {
|
|
207
|
+
lastLine.at = now();
|
|
208
|
+
warn(waitingLine(current, now()));
|
|
209
|
+
}
|
|
210
|
+
await sleep(DEVICE_WAIT_POLL_MS);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async function acquireRunLease({ root, platform, id, deviceName = null, idLabel, waitSeconds, noWait, installBoundMs, appId = null, holderAppId = () => null, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), warn = () => {}, io = fileLeaseIo }) {
|
|
214
|
+
const recorded = io.readHolder(root)[platform];
|
|
215
|
+
let held = recorded;
|
|
216
|
+
if (recorded && recorded.id !== id) {
|
|
217
|
+
const other = parseLease(io.readLease(deviceLeasePath(platform, recorded.id)));
|
|
218
|
+
if (other && other.token === recorded.token && !leaseIsExpired(other, now())) return {
|
|
219
|
+
status: "refused",
|
|
220
|
+
refusal: otherDeviceRefusal(recorded.id, id)
|
|
221
|
+
};
|
|
222
|
+
held = void 0;
|
|
223
|
+
}
|
|
224
|
+
const path = deviceLeasePath(platform, id);
|
|
225
|
+
const deadline = now() + waitSeconds * 1e3;
|
|
226
|
+
const lastLine = { at: null };
|
|
227
|
+
for (;;) {
|
|
228
|
+
const current = parseLease(io.readLease(path));
|
|
229
|
+
const mine = current && held && current.token === held.token ? {
|
|
230
|
+
lease: current,
|
|
231
|
+
record: held
|
|
232
|
+
} : null;
|
|
233
|
+
if (mine && (mine.record.kind === "run" || !leaseIsExpired(mine.lease, now()))) {
|
|
234
|
+
const raised = raiseLease({
|
|
235
|
+
root,
|
|
236
|
+
platform,
|
|
237
|
+
minMs: leaseStepMs(installBoundMs)
|
|
238
|
+
}, io);
|
|
239
|
+
if (raised.status === "raised") return {
|
|
240
|
+
status: "leased",
|
|
241
|
+
kind: mine.record.kind,
|
|
242
|
+
expiresAt: raised.lease.expiresAt
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
const outcome = await waitForDevice({
|
|
246
|
+
root,
|
|
247
|
+
platform,
|
|
248
|
+
id,
|
|
249
|
+
idLabel,
|
|
250
|
+
waitSeconds,
|
|
251
|
+
noWait,
|
|
252
|
+
appId,
|
|
253
|
+
holderAppId,
|
|
254
|
+
now,
|
|
255
|
+
sleep,
|
|
256
|
+
warn,
|
|
257
|
+
io,
|
|
258
|
+
deadline,
|
|
259
|
+
lastLine
|
|
260
|
+
});
|
|
261
|
+
if (outcome.status === "refused") return outcome;
|
|
262
|
+
if (outcome.status === "bypassed") return { status: "unleased" };
|
|
263
|
+
if (outcome.status === "ours") return {
|
|
264
|
+
status: "refused",
|
|
265
|
+
refusal: untokenedRefusal(outcome.lease, now())
|
|
266
|
+
};
|
|
267
|
+
const taken = takeLease({
|
|
268
|
+
root,
|
|
269
|
+
platform,
|
|
270
|
+
id,
|
|
271
|
+
deviceName,
|
|
272
|
+
kind: "run",
|
|
273
|
+
durationMs: leaseStepMs(installBoundMs)
|
|
274
|
+
}, io);
|
|
275
|
+
if (taken.status === "unreadable") return {
|
|
276
|
+
status: "refused",
|
|
277
|
+
refusal: unreadableRefusal(taken.path)
|
|
278
|
+
};
|
|
279
|
+
if (taken.status === "taken" || taken.status === "set") return {
|
|
280
|
+
status: "leased",
|
|
281
|
+
kind: taken.status === "set" ? held?.kind ?? "run" : "run",
|
|
282
|
+
expiresAt: taken.lease.expiresAt
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
//#endregion
|
|
287
|
+
//#region src/engine/device-pool.ts
|
|
288
|
+
function heldPoolId(root, platform, now, io = fileLeaseIo) {
|
|
289
|
+
const record = io.readHolder(root)[platform];
|
|
290
|
+
if (!record) return null;
|
|
291
|
+
const lease = parseLease(io.readLease(deviceLeasePath(platform, record.id)));
|
|
292
|
+
if (!lease || lease.token !== record.token || leaseIsExpired(lease, now)) return null;
|
|
293
|
+
return record.id;
|
|
294
|
+
}
|
|
295
|
+
function readPool(platform, candidates, io) {
|
|
296
|
+
const reading = {
|
|
297
|
+
usable: [],
|
|
298
|
+
leases: [],
|
|
299
|
+
unreadable: []
|
|
300
|
+
};
|
|
301
|
+
for (const candidate of candidates) {
|
|
302
|
+
const path = deviceLeasePath(platform, candidate.id);
|
|
303
|
+
const raw = io.readLease(path);
|
|
304
|
+
const lease = parseLease(raw);
|
|
305
|
+
if (raw !== null && !lease) {
|
|
306
|
+
reading.unreadable.push(path);
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
reading.usable.push(candidate);
|
|
310
|
+
if (lease) reading.leases.push(lease);
|
|
311
|
+
}
|
|
312
|
+
return reading;
|
|
313
|
+
}
|
|
314
|
+
function disconnectedHeldRefusal(id, idLabel) {
|
|
315
|
+
return {
|
|
316
|
+
code: "STIM_NO_DEVICE",
|
|
317
|
+
message: `This workspace leases ${id}, and it is not connected.`,
|
|
318
|
+
remedy: `Run \`stim device unlock\` to give it up, then run this command again. Naming another \`--device <${idLabel}>\` refuses the same way while this lease is held.`,
|
|
319
|
+
lease: null
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
const NO_LEASE_FACTS = {
|
|
323
|
+
platform: null,
|
|
324
|
+
id: null,
|
|
325
|
+
deviceName: null,
|
|
326
|
+
holder: null,
|
|
327
|
+
expiresAt: null
|
|
328
|
+
};
|
|
329
|
+
function unreadableSentence(unreadable) {
|
|
330
|
+
return unreadable.length ? ` Unreadable lease file, so no run may take that device around it: ${unreadable.join("; ")}.` : "";
|
|
331
|
+
}
|
|
332
|
+
function poolBusyRefusal(holders, leases, unreadable, now, waitSeconds) {
|
|
333
|
+
const named = holders.map((holder) => `${holder.id} (${holder.holder}, until ${leaseExpiryText(holder.expiresAt, now)})`);
|
|
334
|
+
const first = holders[0];
|
|
335
|
+
const lease = first ? leases.find((entry) => entry.id === first.id) : void 0;
|
|
336
|
+
if (!first) return {
|
|
337
|
+
code: DEVICE_BUSY,
|
|
338
|
+
message: `Every connected device has an unreadable lease file, so Stim cannot tell who holds them: ${unreadable.join("; ")}.`,
|
|
339
|
+
remedy: "Read each file and, once you know no run depends on it, delete it. `stim gc` reports them on every run.",
|
|
340
|
+
lease: NO_LEASE_FACTS
|
|
341
|
+
};
|
|
342
|
+
return {
|
|
343
|
+
code: DEVICE_BUSY,
|
|
344
|
+
message: `Every connected device is leased by another workspace${waitSeconds > 0 ? `, and this run waited ${waitSeconds}s for one` : ""}: ${named.join("; ")}.${unreadableSentence(unreadable)}`,
|
|
345
|
+
remedy: "Wait longer with `--wait <seconds>`, connect another device, or pass `--no-wait` to install anyway -- which takes no lease and, when both workspaces build the same app id, terminates the holder's running app.",
|
|
346
|
+
lease: {
|
|
347
|
+
platform: lease?.platform ?? null,
|
|
348
|
+
id: first.id,
|
|
349
|
+
deviceName: lease?.deviceName ?? null,
|
|
350
|
+
holder: first.holder,
|
|
351
|
+
expiresAt: first.expiresAt
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
async function selectFromPool({ root, platform, idLabel, list, noCandidates, waitSeconds, deadline, noWait = false, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), warn = () => {}, io = fileLeaseIo }) {
|
|
356
|
+
const held = heldPoolId(root, platform, now(), io);
|
|
357
|
+
const until = deadline ?? now() + waitSeconds * 1e3;
|
|
358
|
+
let lastLine = null;
|
|
359
|
+
for (;;) {
|
|
360
|
+
const { usable, leases, unreadable } = readPool(platform, list(), io);
|
|
361
|
+
const selection = selectPoolDevice({
|
|
362
|
+
candidates: usable,
|
|
363
|
+
leases,
|
|
364
|
+
held,
|
|
365
|
+
now: now()
|
|
366
|
+
});
|
|
367
|
+
if (selection.status === "selected") return selection;
|
|
368
|
+
if (selection.status === "held-disconnected") return {
|
|
369
|
+
status: "refused",
|
|
370
|
+
refusal: disconnectedHeldRefusal(selection.id, idLabel)
|
|
371
|
+
};
|
|
372
|
+
if (selection.status === "none") {
|
|
373
|
+
if (unreadable.length) return {
|
|
374
|
+
status: "refused",
|
|
375
|
+
refusal: poolBusyRefusal([], leases, unreadable, now(), waitSeconds)
|
|
376
|
+
};
|
|
377
|
+
const { message, remedy } = noCandidates();
|
|
378
|
+
return {
|
|
379
|
+
status: "refused",
|
|
380
|
+
refusal: {
|
|
381
|
+
code: "STIM_NO_DEVICE",
|
|
382
|
+
message,
|
|
383
|
+
remedy,
|
|
384
|
+
lease: null
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
const first = selection.holders[0];
|
|
389
|
+
if (noWait && first) {
|
|
390
|
+
const candidate = usable.find((entry) => entry.id === first.id);
|
|
391
|
+
if (candidate) return {
|
|
392
|
+
status: "selected",
|
|
393
|
+
candidate
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
if (now() >= until) return {
|
|
397
|
+
status: "refused",
|
|
398
|
+
refusal: poolBusyRefusal(selection.holders, leases, unreadable, now(), waitSeconds)
|
|
399
|
+
};
|
|
400
|
+
if (lastLine === null || now() - lastLine >= 3e4) {
|
|
401
|
+
lastLine = now();
|
|
402
|
+
warn(`waiting for a free device; every connected one is leased: ${selection.holders.map((holder) => `${holder.id} (${holder.holder}, until ${leaseExpiryText(holder.expiresAt, now())})`).join("; ")}`);
|
|
403
|
+
}
|
|
404
|
+
await sleep(DEVICE_WAIT_POLL_MS);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
//#endregion
|
|
408
|
+
export { lostLine as a, releaseLeaseOnSignal as c, waitForDevice as d, leaseExpiryText as i, runLease as l, DEBUG_VERIFY_STEP_MS as n, lostRefusal as o, acquireRunLease as r, parseDeviceWait as s, selectFromPool as t, waitFlagConflict as u };
|