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.
Files changed (66) hide show
  1. package/README.md +29 -10
  2. package/dist/android-BW8i_YxU.mjs +204 -0
  3. package/dist/android-Bcmev2s2.mjs +2334 -0
  4. package/dist/android-K63_0UfF.mjs +727 -0
  5. package/dist/android-cas-D6HPZ6UQ.mjs +1078 -0
  6. package/dist/android-cas-compiler.mjs +2 -2
  7. package/dist/app-install-NFxUahnH.mjs +1149 -0
  8. package/dist/build-lock-BsVoLDaq.mjs +645 -0
  9. package/dist/build-slots-D0b6sJyo.mjs +152 -0
  10. package/dist/{cache-manifest-4tH64LQ9.mjs → cache-manifest-Cu4_Znw9.mjs} +2 -2
  11. package/dist/cache-manifest.mjs +1 -1
  12. package/dist/cli.mjs +24 -27166
  13. package/dist/collector-run.d.mts +1 -0
  14. package/dist/collector-run.mjs +7 -5
  15. package/dist/command-output-8wHMEde0.mjs +289 -0
  16. package/dist/{config-7kmtuhO2.mjs → config-CMtIaTk9.mjs} +44 -13
  17. package/dist/deps-BLBsTNDS.mjs +435 -0
  18. package/dist/dev-client-RpvXxCXV.mjs +620 -0
  19. package/dist/device-CoCPmazS.mjs +250 -0
  20. package/dist/device-lease-Dpik41RY.mjs +335 -0
  21. package/dist/device-pool-B5pEVa3t.mjs +408 -0
  22. package/dist/device-remote-E81KAdXf.mjs +1209 -0
  23. package/dist/doctor-CqPeMsSm.mjs +1130 -0
  24. package/dist/doctor-D_CX7ib5.mjs +530 -0
  25. package/dist/error-diagnostics-Dw8Gn3yw.mjs +636 -0
  26. package/dist/{exec-bsN9MJXb.mjs → exec-CyylIdq9.mjs} +4 -2
  27. package/dist/gc-B7KkDinF.mjs +1665 -0
  28. package/dist/guide-BS2Xc24o.mjs +4503 -0
  29. package/dist/ios-B7KLgIp5.mjs +2733 -0
  30. package/dist/ios-D1h1Di39.mjs +451 -0
  31. package/dist/ios-device-D3pzTKQE.mjs +81 -0
  32. package/dist/ios-device-xNt0Lg6v.mjs +413 -0
  33. package/dist/logs-DcX9AvRF.mjs +176 -0
  34. package/dist/logs-query-oqn47Ffx.mjs +273 -0
  35. package/dist/metro-COslRK_F.mjs +184 -0
  36. package/dist/metro-store-CDDZHtPl.mjs +92 -0
  37. package/dist/ownership-CGPart6T.mjs +60 -0
  38. package/dist/ownership-CYOyqxUo.mjs +475 -0
  39. package/dist/ownership-claim-7zRxxTfZ.mjs +569 -0
  40. package/dist/{project-Dit1CckW.mjs → project-BBTVABUz.mjs} +12 -3
  41. package/dist/reclaim-C5Jmuahx.mjs +348 -0
  42. package/dist/reload-Z8nKifAk.mjs +332 -0
  43. package/dist/remote-cache-DDmJ1Roq.mjs +994 -0
  44. package/dist/{server-bare-BmTVVmBB.mjs → server-bare-DEk37Mz2.mjs} +8 -2
  45. package/dist/{server-expo-CHJe1r6j.mjs → server-expo-C3ktAwUG.mjs} +6 -5
  46. package/dist/server-expo-Drd7ksHu.mjs +2 -0
  47. package/dist/settings-DwU1_U_q.mjs +708 -0
  48. package/dist/spawn-entry-nCE6m770.mjs +14 -0
  49. package/dist/start-BrY4EuIg.mjs +822 -0
  50. package/dist/{state-2nush3MK.mjs → state-CZ-ER-8d.mjs} +21 -22
  51. package/dist/stats-BCWEVLrM.mjs +307 -0
  52. package/dist/stats-BsF15Bti.mjs +62 -0
  53. package/dist/status-DFI1Hfpg.mjs +393 -0
  54. package/dist/stop-B7DR4awK.mjs +3 -0
  55. package/dist/stop-Btg770vV.mjs +591 -0
  56. package/dist/supervisor-run.d.mts +0 -1
  57. package/dist/supervisor-run.mjs +4 -4
  58. package/dist/workspace-process-lock-BKfdys1q.mjs +47 -0
  59. package/dist/worktree-BPTbI-LN.mjs +1322 -0
  60. package/dist/worktree-C79xx_kM.mjs +543 -0
  61. package/dist/xcode-DeKCribl.mjs +1832 -0
  62. package/package.json +5 -5
  63. package/shim/bundle-response.cjs +5 -0
  64. package/dist/android-jc8MnUyL.mjs +0 -514
  65. package/dist/metro-store-COLl1pOk.mjs +0 -1249
  66. 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 };