stim 1.2.0 → 1.3.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 +30 -0
- package/dist/android-BqVIJoG7.mjs +1368 -0
- package/dist/{android-Bcmev2s2.mjs → android-CqcW1hrP.mjs} +148 -56
- package/dist/{android-cas-D6HPZ6UQ.mjs → android-cas-BR5YJBYv.mjs} +5 -5
- package/dist/{app-install-NFxUahnH.mjs → app-install-DIp9riNx.mjs} +6 -6
- package/dist/{build-slots-D0b6sJyo.mjs → build-slots-C3-R4XQr.mjs} +2 -2
- package/dist/{cache-manifest-Cu4_Znw9.mjs → cache-manifest-CXR-Y8pH.mjs} +1 -1
- package/dist/cache-manifest.mjs +1 -1
- package/dist/cli.mjs +13 -13
- package/dist/collector-run.d.mts +3 -1
- package/dist/collector-run.mjs +33 -8
- package/dist/{config-CMtIaTk9.mjs → config-CcefMY2Q.mjs} +93 -12
- package/dist/{deps-BLBsTNDS.mjs → deps-Spz8ozgo.mjs} +1 -1
- package/dist/{dev-client-RpvXxCXV.mjs → dev-client-BjisHDB5.mjs} +186 -9
- package/dist/{device-CoCPmazS.mjs → device-BkiW-gkk.mjs} +19 -11
- package/dist/{device-lease-Dpik41RY.mjs → device-lease-BJYybF-2.mjs} +107 -83
- package/dist/{device-pool-B5pEVa3t.mjs → device-pool-CPbLGfrB.mjs} +18 -12
- package/dist/{device-remote-E81KAdXf.mjs → device-remote-AkYCKS8U.mjs} +7 -7
- package/dist/{doctor-CqPeMsSm.mjs → doctor-Bmv86UUW.mjs} +11 -12
- package/dist/{doctor-D_CX7ib5.mjs → doctor-fAMXC4vl.mjs} +4 -4
- package/dist/{error-diagnostics-Dw8Gn3yw.mjs → error-diagnostics-DHC31rXA.mjs} +62 -52
- package/dist/{gc-B7KkDinF.mjs → gc-GGu-8ULN.mjs} +66 -57
- package/dist/{guide-BS2Xc24o.mjs → guide-92CPA21s.mjs} +196 -13
- package/dist/{ios-B7KLgIp5.mjs → ios-BSXFeO5H.mjs} +183 -54
- package/dist/{ios-D1h1Di39.mjs → ios-Deh5js5V.mjs} +78 -40
- package/dist/{ios-device-xNt0Lg6v.mjs → ios-device-CWOM_S44.mjs} +1 -1
- package/dist/{logs-DcX9AvRF.mjs → logs-DPNF4C9s.mjs} +9 -6
- package/dist/{logs-query-oqn47Ffx.mjs → logs-query-R-Cqc6Qv.mjs} +22 -5
- package/dist/{metro-COslRK_F.mjs → metro-CORqXyTL.mjs} +2 -2
- package/dist/{ndjson-DcAtEx_K.mjs → ndjson-BZwI1NBv.mjs} +5 -2
- package/dist/{ownership-CGPart6T.mjs → ownership-8O073GIC.mjs} +3 -3
- package/dist/{ownership-CYOyqxUo.mjs → ownership-BpCKiRlQ.mjs} +3 -3
- package/dist/{ownership-claim-7zRxxTfZ.mjs → ownership-claim-Bf2CAfw3.mjs} +4 -3
- package/dist/{project-BBTVABUz.mjs → project-fKmuIaMI.mjs} +2 -2
- package/dist/{reclaim-C5Jmuahx.mjs → reclaim-iW9Cad7k.mjs} +80 -74
- package/dist/{reload-Z8nKifAk.mjs → reload-DazHDSFA.mjs} +32 -24
- package/dist/{server-bare-DEk37Mz2.mjs → server-bare-DEzPZLvf.mjs} +2 -2
- package/dist/server-expo-44em5Pmg.mjs +2 -0
- package/dist/{server-expo-C3ktAwUG.mjs → server-expo-DizT4OMk.mjs} +4 -4
- package/dist/{settings-DwU1_U_q.mjs → settings-DEJf6_kJ.mjs} +2 -2
- package/dist/slot-launch-cci0xvmC.mjs +19 -0
- package/dist/{start-BrY4EuIg.mjs → start-mObjQH2W.mjs} +11 -11
- package/dist/{state-CZ-ER-8d.mjs → state-B3XA2wp2.mjs} +9 -9
- package/dist/{stats-BCWEVLrM.mjs → stats-BR-kwj0B.mjs} +1 -1
- package/dist/{stats-BsF15Bti.mjs → stats-CaaQr4yN.mjs} +3 -3
- package/dist/{status-DFI1Hfpg.mjs → status-BxQNqu9t.mjs} +55 -24
- package/dist/{stop-B7DR4awK.mjs → stop-CrHGcjrV.mjs} +2 -2
- package/dist/{stop-Btg770vV.mjs → stop-DimGXEKY.mjs} +93 -42
- package/dist/supervisor-run.d.mts +1 -2
- package/dist/supervisor-run.mjs +5 -5
- package/dist/{remote-cache-DDmJ1Roq.mjs → teardown-ClFp1Aax.mjs} +887 -888
- package/dist/{workspace-process-lock-BKfdys1q.mjs → workspace-process-lock-3NcQnZ0X.mjs} +1 -1
- package/dist/{worktree-BPTbI-LN.mjs → worktree-DxTgN1CJ.mjs} +12 -12
- package/dist/{worktree-C79xx_kM.mjs → worktree-Eq6TLH8N.mjs} +1 -1
- package/dist/{xcode-DeKCribl.mjs → xcode-D7sAp-Zy.mjs} +82 -56
- package/package.json +5 -5
- package/dist/android-K63_0UfF.mjs +0 -727
- package/dist/build-lock-BsVoLDaq.mjs +0 -645
- package/dist/server-expo-Drd7ksHu.mjs +0 -2
package/dist/cli.mjs
CHANGED
|
@@ -3,19 +3,19 @@ import { readFileSync } from "node:fs";
|
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
//#region bin/cli.ts
|
|
5
5
|
const commands = /* @__PURE__ */ new Map([
|
|
6
|
-
["doctor", () => import("./doctor-
|
|
7
|
-
["worktree", () => import("./worktree-
|
|
8
|
-
["start", () => import("./start-
|
|
9
|
-
["stop", () => import("./stop-
|
|
10
|
-
["ios", () => import("./ios-
|
|
11
|
-
["android", () => import("./android-
|
|
12
|
-
["reload", () => import("./reload-
|
|
13
|
-
["device", () => import("./device-
|
|
14
|
-
["logs", () => import("./logs-
|
|
15
|
-
["status", () => import("./status-
|
|
16
|
-
["stats", () => import("./stats-
|
|
17
|
-
["gc", () => import("./gc-
|
|
18
|
-
["guide", () => import("./guide-
|
|
6
|
+
["doctor", () => import("./doctor-fAMXC4vl.mjs")],
|
|
7
|
+
["worktree", () => import("./worktree-DxTgN1CJ.mjs")],
|
|
8
|
+
["start", () => import("./start-mObjQH2W.mjs")],
|
|
9
|
+
["stop", () => import("./stop-CrHGcjrV.mjs")],
|
|
10
|
+
["ios", () => import("./ios-BSXFeO5H.mjs")],
|
|
11
|
+
["android", () => import("./android-CqcW1hrP.mjs")],
|
|
12
|
+
["reload", () => import("./reload-DazHDSFA.mjs")],
|
|
13
|
+
["device", () => import("./device-BkiW-gkk.mjs")],
|
|
14
|
+
["logs", () => import("./logs-DPNF4C9s.mjs")],
|
|
15
|
+
["status", () => import("./status-BxQNqu9t.mjs")],
|
|
16
|
+
["stats", () => import("./stats-CaaQr4yN.mjs")],
|
|
17
|
+
["gc", () => import("./gc-GGu-8ULN.mjs")],
|
|
18
|
+
["guide", () => import("./guide-92CPA21s.mjs")]
|
|
19
19
|
]);
|
|
20
20
|
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
|
|
21
21
|
const program = new Command();
|
package/dist/collector-run.d.mts
CHANGED
|
@@ -55,6 +55,7 @@ declare function readCollectors(root: string): Record<string, unknown>;
|
|
|
55
55
|
//#region src/collector/run.d.ts
|
|
56
56
|
declare const PLATFORMS: string[];
|
|
57
57
|
interface ParsedCollectorArgs {
|
|
58
|
+
slot?: string;
|
|
58
59
|
platform?: string;
|
|
59
60
|
root?: string;
|
|
60
61
|
udid?: string;
|
|
@@ -69,6 +70,7 @@ interface ParsedCollectorArgs {
|
|
|
69
70
|
}
|
|
70
71
|
declare function parseArgs(argv: string[]): ParsedCollectorArgs;
|
|
71
72
|
interface RunCollectorOptions {
|
|
73
|
+
slot?: string;
|
|
72
74
|
platform: string;
|
|
73
75
|
root: string;
|
|
74
76
|
udid?: string | null;
|
|
@@ -114,7 +116,7 @@ interface RunCollectorHandle {
|
|
|
114
116
|
startedAt: string;
|
|
115
117
|
pid: number | null;
|
|
116
118
|
}
|
|
117
|
-
declare function runCollector({ platform, root, udid, appName, appExecutable, bundleId, serial, packageName, physical, payloadUrl, startStream, resolvePid, pidTimeoutMs, pidOf, pidWatchMs, watchPid, resolveClockOffset, now, onExit, attachSignals, stderr }: RunCollectorOptions): Promise<RunCollectorHandle | null>;
|
|
119
|
+
declare function runCollector({ slot, platform, root, udid, appName, appExecutable, bundleId, serial, packageName, physical, payloadUrl, startStream, resolvePid, pidTimeoutMs, pidOf, pidWatchMs, watchPid, resolveClockOffset, now, onExit, attachSignals, stderr }: RunCollectorOptions): Promise<RunCollectorHandle | null>;
|
|
118
120
|
declare function main(argv?: string[]): Promise<void>;
|
|
119
121
|
//#endregion
|
|
120
122
|
export { PLATFORMS, ParsedCollectorArgs, RunCollectorHandle, RunCollectorOptions, main, parseArgs, readCollectors, registerCollector, runCollector, unregisterCollector };
|
package/dist/collector-run.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { t as getExecutor } from "./exec-CyylIdq9.mjs";
|
|
2
|
-
import { T as captureProcessToken } from "./config-
|
|
3
|
-
import { r as createNdjsonWriter } from "./ndjson-
|
|
4
|
-
import { D as workspaceLogsDir } from "./state-
|
|
2
|
+
import { I as deviceSlotKey, T as captureProcessToken, V as validateDeviceSlot } from "./config-CcefMY2Q.mjs";
|
|
3
|
+
import { r as createNdjsonWriter } from "./ndjson-BZwI1NBv.mjs";
|
|
4
|
+
import { D as workspaceLogsDir } from "./state-B3XA2wp2.mjs";
|
|
5
5
|
import { t as createLineReader } from "./process-output-C7IlIzd7.mjs";
|
|
6
6
|
import { i as startIosDeviceConsole, r as parseDeviceConsoleLine } from "./ios-device-D3pzTKQE.mjs";
|
|
7
|
-
import { a as unregisterCollector, i as registerCollector, r as readCollectors, t as collectorProcessTitle } from "./ownership-
|
|
7
|
+
import { a as unregisterCollector, i as registerCollector, r as readCollectors, t as collectorProcessTitle } from "./ownership-8O073GIC.mjs";
|
|
8
8
|
import { a as watchAppPid, i as waitForAppPid, n as parseLogcatLine, r as startAndroidLogcat, t as androidClockOffset } from "./android-BW8i_YxU.mjs";
|
|
9
9
|
import { existsSync } from "node:fs";
|
|
10
10
|
import { isAbsolute, join, resolve } from "node:path";
|
|
@@ -193,6 +193,7 @@ function startIosLogStream({ udid, appName, executableName = null, spawnFn = nul
|
|
|
193
193
|
//#region src/collector/run.ts
|
|
194
194
|
const PLATFORMS = ["ios", "android"];
|
|
195
195
|
function parseArgs(argv) {
|
|
196
|
+
let slot;
|
|
196
197
|
let platform;
|
|
197
198
|
let root;
|
|
198
199
|
let udid;
|
|
@@ -205,6 +206,16 @@ function parseArgs(argv) {
|
|
|
205
206
|
let payloadUrl = null;
|
|
206
207
|
for (let i = 0; i < argv.length; i++) {
|
|
207
208
|
const arg = argv[i];
|
|
209
|
+
if (arg === "--slot") {
|
|
210
|
+
slot = argv[++i];
|
|
211
|
+
if (!slot) return { error: "--slot needs a name." };
|
|
212
|
+
try {
|
|
213
|
+
validateDeviceSlot(slot);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
return { error: String(error.message) };
|
|
216
|
+
}
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
208
219
|
if (arg === "--platform") {
|
|
209
220
|
platform = argv[++i];
|
|
210
221
|
continue;
|
|
@@ -272,7 +283,8 @@ function parseArgs(argv) {
|
|
|
272
283
|
serial,
|
|
273
284
|
packageName,
|
|
274
285
|
physical,
|
|
275
|
-
payloadUrl
|
|
286
|
+
payloadUrl,
|
|
287
|
+
...slot ? { slot } : {}
|
|
276
288
|
};
|
|
277
289
|
}
|
|
278
290
|
const noopFlush = () => {};
|
|
@@ -281,7 +293,12 @@ function startMessage({ platform, physical, pid, appName, bundleId, udid, packag
|
|
|
281
293
|
if (!physical) return `device log collector pid ${pid} streaming ${appName} on ${udid}`;
|
|
282
294
|
return `device log collector pid ${pid} launching ${bundleId} on device ${udid} and streaming its console; subsystem, category and severity are not carried on hardware -- see \`stim guide logs\``;
|
|
283
295
|
}
|
|
284
|
-
async function runCollector({ platform, root, udid = null, appName = null, appExecutable = null, bundleId = null, serial = null, packageName = null, physical = false, payloadUrl = null, startStream = null, resolvePid = null, pidTimeoutMs = 3e4, pidOf = null, pidWatchMs = null, watchPid = watchAppPid, resolveClockOffset = androidClockOffset, now = Date.now, onExit = (code) => process.exit(code), attachSignals = true, stderr = (line) => console.error(line) }) {
|
|
296
|
+
async function runCollector({ slot = "default", platform, root, udid = null, appName = null, appExecutable = null, bundleId = null, serial = null, packageName = null, physical = false, payloadUrl = null, startStream = null, resolvePid = null, pidTimeoutMs = 3e4, pidOf = null, pidWatchMs = null, watchPid = watchAppPid, resolveClockOffset = androidClockOffset, now = Date.now, onExit = (code) => process.exit(code), attachSignals = true, stderr = (line) => console.error(line) }) {
|
|
297
|
+
const key = deviceSlotKey(platform, slot);
|
|
298
|
+
const attribution = slot === "default" ? {} : {
|
|
299
|
+
slot,
|
|
300
|
+
deviceId: udid ?? serial
|
|
301
|
+
};
|
|
285
302
|
const writer = createNdjsonWriter(join(workspaceLogsDir(root), "device.ndjson"));
|
|
286
303
|
const startedAt = new Date(now()).toISOString();
|
|
287
304
|
const processToken = captureProcessToken(process.pid);
|
|
@@ -299,6 +316,7 @@ async function runCollector({ platform, root, udid = null, appName = null, appEx
|
|
|
299
316
|
finished = true;
|
|
300
317
|
watcher?.stop();
|
|
301
318
|
if (physical && captured === 0 && !signalled) writer.write({
|
|
319
|
+
...attribution,
|
|
302
320
|
src: "device",
|
|
303
321
|
platform,
|
|
304
322
|
level: "warn",
|
|
@@ -306,6 +324,7 @@ async function runCollector({ platform, root, udid = null, appName = null, appEx
|
|
|
306
324
|
msg: `the device console produced no output for ${bundleId}; devicectl only connects the app's streams when it starts the app, and os_log below the info level never reaches them`
|
|
307
325
|
});
|
|
308
326
|
writer.write({
|
|
327
|
+
...attribution,
|
|
309
328
|
src: "device",
|
|
310
329
|
platform,
|
|
311
330
|
level,
|
|
@@ -313,7 +332,7 @@ async function runCollector({ platform, root, udid = null, appName = null, appEx
|
|
|
313
332
|
msg
|
|
314
333
|
});
|
|
315
334
|
try {
|
|
316
|
-
unregisterCollector(root,
|
|
335
|
+
unregisterCollector(root, key, process.pid, processToken);
|
|
317
336
|
} catch {}
|
|
318
337
|
const closed = writer.close();
|
|
319
338
|
if (closed.dropped > 0) stderr(`Stim collector: dropped ${closed.dropped} record(s); last error: ${describe(closed.lastError)}`);
|
|
@@ -327,7 +346,8 @@ async function runCollector({ platform, root, udid = null, appName = null, appEx
|
|
|
327
346
|
finish(0, "info", `device log collector received ${signal}; detaching`, "collector_stopped", { signalled: true });
|
|
328
347
|
});
|
|
329
348
|
try {
|
|
330
|
-
registerCollector(root,
|
|
349
|
+
registerCollector(root, key, {
|
|
350
|
+
...attribution,
|
|
331
351
|
pid: process.pid,
|
|
332
352
|
processToken,
|
|
333
353
|
startedAt
|
|
@@ -355,6 +375,7 @@ async function runCollector({ platform, root, udid = null, appName = null, appEx
|
|
|
355
375
|
pid = resolved.pid ?? null;
|
|
356
376
|
}
|
|
357
377
|
writer.write({
|
|
378
|
+
...attribution,
|
|
358
379
|
src: "device",
|
|
359
380
|
platform,
|
|
360
381
|
level: "info",
|
|
@@ -375,6 +396,7 @@ async function runCollector({ platform, root, udid = null, appName = null, appEx
|
|
|
375
396
|
const attach = (streamPid) => {
|
|
376
397
|
const clockOffsetMs = platform === "android" ? resolveClockOffset(serial, { now }) : null;
|
|
377
398
|
if (platform === "android") writer.write({
|
|
399
|
+
...attribution,
|
|
378
400
|
src: "device",
|
|
379
401
|
platform,
|
|
380
402
|
level: clockOffsetMs === null ? "warn" : "debug",
|
|
@@ -390,6 +412,7 @@ async function runCollector({ platform, root, udid = null, appName = null, appEx
|
|
|
390
412
|
if (!record) return;
|
|
391
413
|
captured++;
|
|
392
414
|
writer.write({
|
|
415
|
+
...attribution,
|
|
393
416
|
...record,
|
|
394
417
|
platform
|
|
395
418
|
});
|
|
@@ -420,6 +443,7 @@ async function runCollector({ platform, root, udid = null, appName = null, appEx
|
|
|
420
443
|
const errReader = createLineReader(physical ? onLine : (line) => {
|
|
421
444
|
const text = String(line).trimEnd();
|
|
422
445
|
if (text.trim()) writer.write({
|
|
446
|
+
...attribution,
|
|
423
447
|
src: "device",
|
|
424
448
|
platform,
|
|
425
449
|
level: "debug",
|
|
@@ -464,6 +488,7 @@ async function runCollector({ platform, root, udid = null, appName = null, appEx
|
|
|
464
488
|
killChild(child);
|
|
465
489
|
child = null;
|
|
466
490
|
writer.write({
|
|
491
|
+
...attribution,
|
|
467
492
|
src: "device",
|
|
468
493
|
platform,
|
|
469
494
|
level: "info",
|
|
@@ -4,6 +4,85 @@ import { homedir } from "os";
|
|
|
4
4
|
import { dirname, isAbsolute, join, normalize } from "path";
|
|
5
5
|
import { withDirLock } from "@stim-cli/core";
|
|
6
6
|
import { capture, check, decode } from "unique-pid";
|
|
7
|
+
//#region src/device-slots.ts
|
|
8
|
+
const DEFAULT_DEVICE_SLOT = "default";
|
|
9
|
+
function validateDeviceSlot(slot = "default") {
|
|
10
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(slot) || [
|
|
11
|
+
"constructor",
|
|
12
|
+
"prototype",
|
|
13
|
+
"__proto__"
|
|
14
|
+
].includes(slot)) {
|
|
15
|
+
const error = /* @__PURE__ */ new Error("A device slot must be 1-64 letters, digits, underscores or hyphens, starting with a letter or digit.");
|
|
16
|
+
Object.assign(error, { code: "STIM_BAD_ARG" });
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
return slot;
|
|
20
|
+
}
|
|
21
|
+
function deviceSlotPlatforms(project, slot = DEFAULT_DEVICE_SLOT) {
|
|
22
|
+
validateDeviceSlot(slot);
|
|
23
|
+
if (slot === DEFAULT_DEVICE_SLOT) return project?.platforms;
|
|
24
|
+
return project?.deviceSlots && Object.hasOwn(project.deviceSlots, slot) ? project.deviceSlots[slot] : void 0;
|
|
25
|
+
}
|
|
26
|
+
function projectDeviceSlots(project) {
|
|
27
|
+
const slots = [{
|
|
28
|
+
slot: DEFAULT_DEVICE_SLOT,
|
|
29
|
+
platforms: project?.platforms ?? {}
|
|
30
|
+
}];
|
|
31
|
+
for (const [slot, platforms] of Object.entries(project?.deviceSlots ?? {})) {
|
|
32
|
+
validateDeviceSlot(slot);
|
|
33
|
+
if (slot === DEFAULT_DEVICE_SLOT) throw new Error("The default device slot must use the existing platforms record.");
|
|
34
|
+
if (!platforms || typeof platforms !== "object" || Array.isArray(platforms)) throw new Error(`Invalid device slot record: ${slot}`);
|
|
35
|
+
slots.push({
|
|
36
|
+
slot,
|
|
37
|
+
platforms
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return slots;
|
|
41
|
+
}
|
|
42
|
+
function assignSlotDevice(project, platform, device, slot = DEFAULT_DEVICE_SLOT) {
|
|
43
|
+
validateDeviceSlot(slot);
|
|
44
|
+
if (platform !== "ios" && platform !== "android") throw new Error(`Unknown device platform: ${platform}`);
|
|
45
|
+
if (slot === DEFAULT_DEVICE_SLOT) project.platforms = {
|
|
46
|
+
...project.platforms,
|
|
47
|
+
[platform]: device
|
|
48
|
+
};
|
|
49
|
+
else project.deviceSlots = {
|
|
50
|
+
...project.deviceSlots,
|
|
51
|
+
[slot]: {
|
|
52
|
+
...deviceSlotPlatforms(project, slot),
|
|
53
|
+
[platform]: device
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function removeSlotDevice(project, platform, slot = DEFAULT_DEVICE_SLOT) {
|
|
58
|
+
const platforms = deviceSlotPlatforms(project, slot);
|
|
59
|
+
if (!platforms) return;
|
|
60
|
+
delete platforms[platform];
|
|
61
|
+
if (slot !== DEFAULT_DEVICE_SLOT && Object.keys(platforms).length === 0) {
|
|
62
|
+
delete project.deviceSlots[slot];
|
|
63
|
+
if (Object.keys(project.deviceSlots).length === 0) delete project.deviceSlots;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function deviceSlotKey(platform, slot = "default") {
|
|
67
|
+
validateDeviceSlot(slot);
|
|
68
|
+
if (platform !== "ios" && platform !== "android") throw new Error(`Unknown device platform: ${platform}`);
|
|
69
|
+
return slot === "default" ? platform : `${platform}:${slot}`;
|
|
70
|
+
}
|
|
71
|
+
function parseDeviceSlotKey(key) {
|
|
72
|
+
const [platform, slot = "default", extra] = key.split(":");
|
|
73
|
+
if (platform !== "ios" && platform !== "android" || extra !== void 0) return null;
|
|
74
|
+
try {
|
|
75
|
+
validateDeviceSlot(slot);
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
if (key !== deviceSlotKey(platform, slot)) return null;
|
|
80
|
+
return {
|
|
81
|
+
platform,
|
|
82
|
+
slot
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
7
86
|
//#region src/fs-util.ts
|
|
8
87
|
function volumeRootFor(path) {
|
|
9
88
|
const m = normalize(String(path)).match(/^\/volumes\/([^/]+)/i);
|
|
@@ -266,31 +345,31 @@ function claimMetroPort(projectPath, port) {
|
|
|
266
345
|
return port;
|
|
267
346
|
});
|
|
268
347
|
}
|
|
269
|
-
function setDevice(projectPath, platform, deviceFields) {
|
|
348
|
+
function setDevice(projectPath, platform, deviceFields, slot = "default") {
|
|
270
349
|
requireAbsoluteProjectPath(projectPath);
|
|
271
350
|
withConfigLock(() => {
|
|
272
351
|
const cfg = ensureConfig();
|
|
273
352
|
if (!cfg.projects[projectPath]) throw new Error(`Project not registered: ${projectPath}`);
|
|
274
|
-
cfg.projects[projectPath]
|
|
275
|
-
cfg.projects[projectPath].platforms[platform] = deviceFields;
|
|
353
|
+
assignSlotDevice(cfg.projects[projectPath], platform, deviceFields, slot);
|
|
276
354
|
saveConfig(cfg);
|
|
277
355
|
});
|
|
278
356
|
}
|
|
279
|
-
function releaseAndroidConsolePort(projectPath, consolePort) {
|
|
357
|
+
function releaseAndroidConsolePort(projectPath, consolePort, slot = "default") {
|
|
280
358
|
return withConfigLock(() => {
|
|
281
359
|
const cfg = loadConfig();
|
|
282
|
-
const android = cfg?.projects?.[projectPath]?.
|
|
360
|
+
const android = deviceSlotPlatforms(cfg?.projects?.[projectPath], slot)?.android;
|
|
283
361
|
if (!cfg || !android || android.consolePort !== consolePort) return false;
|
|
284
362
|
delete android.consolePort;
|
|
285
363
|
saveConfig(cfg);
|
|
286
364
|
return true;
|
|
287
365
|
});
|
|
288
366
|
}
|
|
289
|
-
function clearDevice(projectPath, platform) {
|
|
367
|
+
function clearDevice(projectPath, platform, slot = "default") {
|
|
290
368
|
withConfigLock(() => {
|
|
291
369
|
const cfg = loadConfig();
|
|
292
|
-
|
|
293
|
-
|
|
370
|
+
const project = cfg?.projects?.[projectPath];
|
|
371
|
+
if (!cfg || !project) return;
|
|
372
|
+
removeSlotDevice(project, platform, slot);
|
|
294
373
|
saveConfig(cfg);
|
|
295
374
|
});
|
|
296
375
|
}
|
|
@@ -371,11 +450,13 @@ function allConsolePortsAndSerials({ isMounted = isOnMountedVolume } = {}) {
|
|
|
371
450
|
if (!cfg) return result;
|
|
372
451
|
for (const [path, proj] of Object.entries(cfg.projects || {})) {
|
|
373
452
|
if (!existsSync(path) && isMounted(path)) continue;
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
453
|
+
for (const { platforms } of projectDeviceSlots(proj)) {
|
|
454
|
+
const android = platforms.android;
|
|
455
|
+
if (typeof android?.consolePort === "number") result.androidConsolePorts.push(android.consolePort);
|
|
456
|
+
if (android?.serial && !android.avdName) result.androidPhysicalSerials.push(android.serial);
|
|
457
|
+
}
|
|
377
458
|
}
|
|
378
459
|
return result;
|
|
379
460
|
}
|
|
380
461
|
//#endregion
|
|
381
|
-
export { directorySize as A, withConfigLock as C, sameProcessRecord as D, inspectProcessIdentity as E, isOnMountedVolume as M, listMountedVolumes as N, waitForProcessExit as O, volumeRootFor as P, upsertProject as S, captureProcessToken as T, releaseAndroidConsolePort as _, clearSupervisor as a, setDevice as b, findEnclosingWorktreeRoot as c, getConfigPath as d, getProject as f, loadConfig as g, isPathPrefix as h, clearDevice as i, formatBytes as j, withDirLock as k, getConcurrencyLimits as l, getRepoSettings as m, allMetroPorts as n, configCorruptRepair as o, getProjectSettings as p, claimMetroPort as r, ensureConfig as s, allConsolePortsAndSerials as t, getConfigDir as u, removeProject as v, captureProcessIdentity as w, setSupervisor as x, saveConfig as y };
|
|
462
|
+
export { directorySize as A, removeSlotDevice as B, withConfigLock as C, sameProcessRecord as D, inspectProcessIdentity as E, assignSlotDevice as F, deviceSlotKey as I, deviceSlotPlatforms as L, isOnMountedVolume as M, listMountedVolumes as N, waitForProcessExit as O, volumeRootFor as P, parseDeviceSlotKey as R, upsertProject as S, captureProcessToken as T, validateDeviceSlot as V, releaseAndroidConsolePort as _, clearSupervisor as a, setDevice as b, findEnclosingWorktreeRoot as c, getConfigPath as d, getProject as f, loadConfig as g, isPathPrefix as h, clearDevice as i, formatBytes as j, withDirLock as k, getConcurrencyLimits as l, getRepoSettings as m, allMetroPorts as n, configCorruptRepair as o, getProjectSettings as p, claimMetroPort as r, ensureConfig as s, allConsolePortsAndSerials as t, getConfigDir as u, removeProject as v, captureProcessIdentity as w, setSupervisor as x, saveConfig as y, projectDeviceSlots as z };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as getExecutor } from "./exec-CyylIdq9.mjs";
|
|
2
2
|
import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-C7IlIzd7.mjs";
|
|
3
|
-
import { f as startBuildHeartbeat, h as bundlerPin, r as HEARTBEAT_INTERVAL_MS } from "./xcode-
|
|
3
|
+
import { f as startBuildHeartbeat, h as bundlerPin, r as HEARTBEAT_INTERVAL_MS } from "./xcode-D7sAp-Zy.mjs";
|
|
4
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
5
5
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
6
6
|
import { homedir } from "node:os";
|
|
@@ -1,16 +1,190 @@
|
|
|
1
1
|
import { t as getExecutor } from "./exec-CyylIdq9.mjs";
|
|
2
|
-
import { y as ensureWorkspaceStorage } from "./state-
|
|
3
|
-
import { C as removeTemporaryEntry, S as makeTemporaryDirectory } from "./worktree-
|
|
4
|
-
import { i as detectIsExpo, o as isPackageResolvable } from "./project-
|
|
2
|
+
import { y as ensureWorkspaceStorage } from "./state-B3XA2wp2.mjs";
|
|
3
|
+
import { C as removeTemporaryEntry, S as makeTemporaryDirectory } from "./worktree-Eq6TLH8N.mjs";
|
|
4
|
+
import { i as detectIsExpo, o as isPackageResolvable } from "./project-fKmuIaMI.mjs";
|
|
5
5
|
import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-C7IlIzd7.mjs";
|
|
6
|
-
import { t as NOT_OURS_FOREIGN_CWD } from "./metro-
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { i as expoBinRefusal, r as expoBinPath, t as cleanLine } from "./server-expo-
|
|
10
|
-
import {
|
|
6
|
+
import { t as NOT_OURS_FOREIGN_CWD } from "./metro-CORqXyTL.mjs";
|
|
7
|
+
import { r as claimFailure } from "./ownership-claim-Bf2CAfw3.mjs";
|
|
8
|
+
import { V as releaseBuildLock, z as acquireBuildLock } from "./android-BqVIJoG7.mjs";
|
|
9
|
+
import { i as expoBinRefusal, r as expoBinPath, t as cleanLine } from "./server-expo-DizT4OMk.mjs";
|
|
10
|
+
import { x as resolveEasCliBin } from "./teardown-ClFp1Aax.mjs";
|
|
11
|
+
import { i as LAUNCH_BUNDLING, s as LAUNCH_UNVERIFIED } from "./app-install-DIp9riNx.mjs";
|
|
12
|
+
import { f as startBuildHeartbeat, p as tailLines, r as HEARTBEAT_INTERVAL_MS } from "./xcode-D7sAp-Zy.mjs";
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
14
|
+
import { createHash } from "node:crypto";
|
|
11
15
|
import { basename, join } from "node:path";
|
|
12
16
|
import chalk from "chalk";
|
|
13
17
|
import { request } from "node:http";
|
|
18
|
+
//#region src/engine/eas-build.ts
|
|
19
|
+
function isEasBuildFailure(result) {
|
|
20
|
+
return result?.ok === false;
|
|
21
|
+
}
|
|
22
|
+
function easOptionRefusal({ profile, isExpo, buildSelector, buildCache }) {
|
|
23
|
+
if (profile === void 0) return null;
|
|
24
|
+
if (profile.trim() && isExpo && buildSelector === void 0 && buildCache !== false) return null;
|
|
25
|
+
return {
|
|
26
|
+
code: "STIM_BAD_ARG",
|
|
27
|
+
message: "--eas-profile requires an Expo project and a non-empty development profile.",
|
|
28
|
+
remedy: "Use --eas-profile <name> without --scheme, --configuration, --variant, or --no-build-cache."
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function object(value) {
|
|
32
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
33
|
+
}
|
|
34
|
+
function shellArg(value) {
|
|
35
|
+
return /^[a-zA-Z0-9_.-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
|
|
36
|
+
}
|
|
37
|
+
function easDeviceBuildRemedy(profile) {
|
|
38
|
+
return `If the device is not registered, run npx eas-cli device:create. With approval for cloud build costs and signing changes, run npx eas-cli build --platform ios --profile ${shellArg(profile)} to produce a build with a valid profile for this device, then retry the same Stim command.`;
|
|
39
|
+
}
|
|
40
|
+
var EasFailure = class extends Error {
|
|
41
|
+
code;
|
|
42
|
+
remedy;
|
|
43
|
+
constructor(code, message, remedy) {
|
|
44
|
+
super(message);
|
|
45
|
+
this.code = code;
|
|
46
|
+
this.remedy = remedy;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
function selectEasBuild(builds, { platform, profile, fingerprint, projectId, physical = false }) {
|
|
50
|
+
if (!Array.isArray(builds)) throw new Error("EAS build:list did not return an array.");
|
|
51
|
+
for (const value of builds) {
|
|
52
|
+
const build = object(value);
|
|
53
|
+
if (typeof build.id === "string" && build.id.length > 0 && build.status === "FINISHED" && build.platform === platform.toUpperCase() && build.buildProfile === profile && object(build.fingerprint).hash === fingerprint && object(build.project).id === projectId && (platform !== "ios" || build.isForIosSimulator === !physical) && build.distribution === "INTERNAL" && typeof object(build.artifacts).applicationArchiveUrl === "string") return build.id;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
async function resolveEasDevelopmentBuild({ root, platform, profile, note, isExpo = true, physical = false, selectors = [], buildCache }) {
|
|
58
|
+
if (profile === void 0) return null;
|
|
59
|
+
const refusal = easOptionRefusal({
|
|
60
|
+
profile,
|
|
61
|
+
isExpo,
|
|
62
|
+
buildSelector: selectors.find((value) => value != null) ?? void 0,
|
|
63
|
+
buildCache
|
|
64
|
+
});
|
|
65
|
+
if (refusal) return {
|
|
66
|
+
ok: false,
|
|
67
|
+
...refusal
|
|
68
|
+
};
|
|
69
|
+
const retry = "the same Stim command";
|
|
70
|
+
const buildCommand = `npx eas-cli build --platform ${platform} --profile ${shellArg(profile)}`;
|
|
71
|
+
let lock = null;
|
|
72
|
+
try {
|
|
73
|
+
const cli = resolveEasCliBin(root);
|
|
74
|
+
if (!cli) throw new EasFailure("STIM_EAS_UNAVAILABLE", "EAS CLI is not installed.", "Install eas-cli, run eas login, then retry.");
|
|
75
|
+
let profileEnv = {};
|
|
76
|
+
const run = (args, timeoutMs = 12e4) => {
|
|
77
|
+
try {
|
|
78
|
+
return JSON.parse(getExecutor().runFile(cli.file, [
|
|
79
|
+
...args,
|
|
80
|
+
"--json",
|
|
81
|
+
"--non-interactive"
|
|
82
|
+
], {
|
|
83
|
+
cwd: root,
|
|
84
|
+
timeoutMs,
|
|
85
|
+
env: profileEnv
|
|
86
|
+
}));
|
|
87
|
+
} catch {
|
|
88
|
+
throw new EasFailure("STIM_EAS_UNAVAILABLE", `EAS ${args[0]} failed or returned invalid JSON.`, `Run npx eas-cli ${args.map(shellArg).join(" ")} to inspect the error, then retry ${retry}.`);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
note(`eas resolving profile ${profile}`);
|
|
92
|
+
const config = object(run([
|
|
93
|
+
"config",
|
|
94
|
+
"--platform",
|
|
95
|
+
platform,
|
|
96
|
+
"--profile",
|
|
97
|
+
profile
|
|
98
|
+
]));
|
|
99
|
+
const buildProfile = object(config.buildProfile);
|
|
100
|
+
profileEnv = object(buildProfile.env);
|
|
101
|
+
const projectId = object(object(object(config.appConfig).extra).eas).projectId;
|
|
102
|
+
if (buildProfile.developmentClient !== true || buildProfile.distribution !== "internal" || platform === "ios" && buildProfile.simulator === true !== !physical) throw new EasFailure("STIM_BAD_ARG", `EAS profile ${profile} is not an internal development build for this ${platform} target.`, `Set developmentClient: true and distribution: internal${platform === "ios" ? `, with ios.simulator: ${!physical}` : ""} in that eas.json profile.`);
|
|
103
|
+
if (typeof projectId !== "string" || !projectId) throw new EasFailure("STIM_BAD_ARG", "The Expo app is not linked to an EAS project.", "Link the intended EAS project with eas init, then retry.");
|
|
104
|
+
if (platform === "ios" ? buildProfile.buildConfiguration !== void 0 && buildProfile.buildConfiguration !== "Debug" : buildProfile.gradleCommand !== void 0 && (typeof buildProfile.gradleCommand !== "string" || !/^:app:assemble\w*Debug$/.test(buildProfile.gradleCommand))) throw new EasFailure("STIM_BAD_ARG", `EAS profile ${profile} overrides the development build configuration.`, "Use ios.buildConfiguration: Debug or an android.gradleCommand that assembles a single Debug APK, or remove the override.");
|
|
105
|
+
note(`eas fingerprinting ${platform} with profile ${profile}`);
|
|
106
|
+
const fingerprint = object(run([
|
|
107
|
+
"fingerprint:generate",
|
|
108
|
+
"--platform",
|
|
109
|
+
platform,
|
|
110
|
+
"--build-profile",
|
|
111
|
+
profile
|
|
112
|
+
])).hash;
|
|
113
|
+
if (typeof fingerprint !== "string" || !/^[a-f0-9]{40,64}$/.test(fingerprint)) throw new EasFailure("STIM_EAS_UNAVAILABLE", "EAS returned no valid native fingerprint.", `Run npx eas-cli fingerprint:generate --platform ${platform} --build-profile ${shellArg(profile)} to inspect the result.`);
|
|
114
|
+
note(`eas looking for ${fingerprint.slice(0, 8)}`);
|
|
115
|
+
const buildId = selectEasBuild(run([
|
|
116
|
+
"build:list",
|
|
117
|
+
"--platform",
|
|
118
|
+
platform,
|
|
119
|
+
"--build-profile",
|
|
120
|
+
profile,
|
|
121
|
+
"--fingerprint-hash",
|
|
122
|
+
fingerprint,
|
|
123
|
+
"--status",
|
|
124
|
+
"finished",
|
|
125
|
+
...platform === "ios" && !physical ? ["--simulator"] : ["--distribution", "internal"],
|
|
126
|
+
"--limit",
|
|
127
|
+
"1"
|
|
128
|
+
]), {
|
|
129
|
+
platform,
|
|
130
|
+
profile,
|
|
131
|
+
fingerprint,
|
|
132
|
+
projectId,
|
|
133
|
+
physical
|
|
134
|
+
});
|
|
135
|
+
if (!buildId) throw new EasFailure("STIM_EAS_BUILD_MISSING", `No compatible EAS ${platform} build exists for profile ${profile} and fingerprint ${fingerprint}.`, `A cloud build may incur charges. With approval to run it, use ${buildCommand}, then retry ${retry}.`);
|
|
136
|
+
const cacheKey = `eas-${createHash("sha256").update(JSON.stringify({
|
|
137
|
+
projectId,
|
|
138
|
+
buildId
|
|
139
|
+
})).digest("hex")}-debug-${physical && platform === "ios" ? "device" : "sim"}`;
|
|
140
|
+
lock = acquireBuildLock({
|
|
141
|
+
platform,
|
|
142
|
+
key: cacheKey,
|
|
143
|
+
root
|
|
144
|
+
});
|
|
145
|
+
if (!lock.acquired) throw new EasFailure("STIM_EAS_UNAVAILABLE", `Another run holds the EAS artifact claim at ${lock.path}.`, `Wait for that run to finish, then retry ${retry}.`);
|
|
146
|
+
note(`eas resolving artifact for build ${buildId}`);
|
|
147
|
+
const result = object(run([
|
|
148
|
+
"build:download",
|
|
149
|
+
"--build-id",
|
|
150
|
+
buildId
|
|
151
|
+
], 6e5));
|
|
152
|
+
if (typeof result.path !== "string" || !existsSync(result.path)) throw new Error("EAS returned no downloaded artifact.");
|
|
153
|
+
const path = realpathSync(result.path);
|
|
154
|
+
if (platform === "ios" ? !path.endsWith(".app") || !statSync(path).isDirectory() : !path.endsWith(".apk") || !statSync(path).isFile()) throw new Error("EAS returned an unexpected artifact path or type.");
|
|
155
|
+
note(`eas artifact ready ${fingerprint.slice(0, 8)}`);
|
|
156
|
+
return {
|
|
157
|
+
ok: true,
|
|
158
|
+
path,
|
|
159
|
+
fingerprint,
|
|
160
|
+
cacheKey,
|
|
161
|
+
cacheHit: "remote"
|
|
162
|
+
};
|
|
163
|
+
} catch (error) {
|
|
164
|
+
const claim = claimFailure(error, null);
|
|
165
|
+
if (claim) return {
|
|
166
|
+
ok: false,
|
|
167
|
+
code: claim.code,
|
|
168
|
+
message: claim.message,
|
|
169
|
+
remedy: claim.remedy
|
|
170
|
+
};
|
|
171
|
+
if (error instanceof EasFailure) return {
|
|
172
|
+
ok: false,
|
|
173
|
+
code: error.code,
|
|
174
|
+
message: error.message,
|
|
175
|
+
remedy: error.remedy
|
|
176
|
+
};
|
|
177
|
+
return {
|
|
178
|
+
ok: false,
|
|
179
|
+
code: "STIM_EAS_UNAVAILABLE",
|
|
180
|
+
message: `Could not resolve the EAS development build: ${error.message}`,
|
|
181
|
+
remedy: `Retry ${retry}. No cloud build was started.`
|
|
182
|
+
};
|
|
183
|
+
} finally {
|
|
184
|
+
releaseBuildLock(lock);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
//#endregion
|
|
14
188
|
//#region src/engine/metro-warmup.ts
|
|
15
189
|
const WARMUP_TIMEOUT_MS = 6e4;
|
|
16
190
|
async function requestLocal(url, platform, prefetch) {
|
|
@@ -135,6 +309,9 @@ function launchOutcomeRecord({ launchState, release, bundleId, configuration, me
|
|
|
135
309
|
msg
|
|
136
310
|
};
|
|
137
311
|
}
|
|
312
|
+
function isPhysicalDeviceRequest(flag) {
|
|
313
|
+
return flag !== null && flag !== void 0 && flag !== false;
|
|
314
|
+
}
|
|
138
315
|
//#endregion
|
|
139
316
|
//#region src/engine/prebuild.ts
|
|
140
317
|
const PREBUILD_ERROR = "STIM_PREBUILD_FAILED";
|
|
@@ -617,4 +794,4 @@ function readJson(file) {
|
|
|
617
794
|
}
|
|
618
795
|
}
|
|
619
796
|
//#endregion
|
|
620
|
-
export { swapJsBundle as a, ensureWorkspaceStorageSafely as c,
|
|
797
|
+
export { easDeviceBuildRemedy as _, swapJsBundle as a, ensureWorkspaceStorageSafely as c, launchOutcomeRecord as d, noMetroMessage as f, warmMetro as g, sleep as h, detectEntryFile as i, gateShouldRetry as l, resolveMetroWithRetry as m, pickDevClientScheme as n, needsPrebuild as o, noMetroRemedy as p, schemesFromInfoPlist as r, runPrebuild as s, devClientScheme as t, isPhysicalDeviceRequest as u, isEasBuildFailure as v, resolveEasDevelopmentBuild as y };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { V as validateDeviceSlot } from "./config-CcefMY2Q.mjs";
|
|
2
|
+
import { a as findProjectRoot } from "./project-fKmuIaMI.mjs";
|
|
2
3
|
import { r as clockTime } from "./command-output-8wHMEde0.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { c as listIosDevices, o as iosPoolCandidates, s as iosPoolNoCandidatesRefusal, u as resolveIosPhysicalDevice } from "./ios-device-
|
|
4
|
+
import { C as memoizeEmulatorProbe, P as resolvePhysicalDevice, i as androidPoolNoCandidatesRefusal, j as probeEmulatorSerial, k as physicalDeviceModel, r as androidPoolCandidates, y as listAdbDevices } from "./android-BqVIJoG7.mjs";
|
|
5
|
+
import { f as takeLease, l as releaseWorkspaceLeases, n as fileLeaseIo, o as parseLeaseDuration } from "./device-lease-BJYybF-2.mjs";
|
|
6
|
+
import { d as waitForDevice, s as parseDeviceWait, t as selectFromPool } from "./device-pool-CPbLGfrB.mjs";
|
|
7
|
+
import { c as listIosDevices, o as iosPoolCandidates, s as iosPoolNoCandidatesRefusal, u as resolveIosPhysicalDevice } from "./ios-device-CWOM_S44.mjs";
|
|
7
8
|
import chalk from "chalk";
|
|
8
9
|
//#region src/commands/device.ts
|
|
9
10
|
const DEFAULT_FOR = "5m";
|
|
@@ -24,7 +25,7 @@ const DEFAULT_DEPS = {
|
|
|
24
25
|
note: (line) => console.error(line)
|
|
25
26
|
};
|
|
26
27
|
function grantLine(facts, durationText) {
|
|
27
|
-
return `locked ${facts.deviceName ? `${facts.deviceName} (${facts.id})` : facts.id} for ${facts.holder} until ${clockTime(facts.expiresAt)} (${durationText}). Renew: stim device lock ${facts.platform} --for ${durationText}. Release: stim device unlock.`;
|
|
28
|
+
return `locked ${facts.deviceName ? `${facts.deviceName} (${facts.id})` : facts.id} for ${facts.holder} until ${clockTime(facts.expiresAt)} (${durationText}). Renew: stim device lock ${facts.platform}${facts.slot ? ` --slot ${facts.slot}` : ""} --for ${durationText}. Release: stim device unlock${facts.slot ? ` --slot ${facts.slot}` : ""}.`;
|
|
28
29
|
}
|
|
29
30
|
function releasedLine(lease) {
|
|
30
31
|
return `unlocked ${lease.deviceName ? `${lease.deviceName} (${lease.id})` : lease.id} -- the ${lease.platform} lease ran until ${clockTime(lease.expiresAt)}.`;
|
|
@@ -59,9 +60,10 @@ function resolveDevice(platform, requested, d) {
|
|
|
59
60
|
function isFailure(value) {
|
|
60
61
|
return "code" in value;
|
|
61
62
|
}
|
|
62
|
-
async function poolDevice(platform, idLabel, waitSeconds, deadline, root, d) {
|
|
63
|
+
async function poolDevice(platform, idLabel, waitSeconds, deadline, root, d, slot) {
|
|
63
64
|
const isEmulator = memoizeEmulatorProbe(d.probeEmulatorSerial);
|
|
64
65
|
const pooled = await d.selectFromPool({
|
|
66
|
+
...slot ? { slot } : {},
|
|
65
67
|
root,
|
|
66
68
|
platform,
|
|
67
69
|
idLabel,
|
|
@@ -140,10 +142,11 @@ async function runLock(platformArg, idArg, opts = {}, overrides = {}) {
|
|
|
140
142
|
const idLabel = platform === "ios" ? "udid" : "serial";
|
|
141
143
|
const deadline = d.now() + wait.seconds * 1e3;
|
|
142
144
|
const lastLine = { at: null };
|
|
143
|
-
const device = idArg ? resolveDevice(platform, idArg, d) : await poolDevice(platform, idLabel, wait.seconds, deadline, root, d);
|
|
145
|
+
const device = idArg ? resolveDevice(platform, idArg, d) : await poolDevice(platform, idLabel, wait.seconds, deadline, root, d, opts.slot);
|
|
144
146
|
if (isFailure(device)) return report(device);
|
|
145
147
|
for (;;) {
|
|
146
148
|
const outcome = await d.waitForDevice({
|
|
149
|
+
...opts.slot ? { slot: opts.slot } : {},
|
|
147
150
|
root,
|
|
148
151
|
platform,
|
|
149
152
|
id: device.id,
|
|
@@ -161,6 +164,7 @@ async function runLock(platformArg, idArg, opts = {}, overrides = {}) {
|
|
|
161
164
|
lease: outcome.refusal.lease
|
|
162
165
|
});
|
|
163
166
|
const taken = d.takeLease({
|
|
167
|
+
...opts.slot ? { slot: opts.slot } : {},
|
|
164
168
|
root,
|
|
165
169
|
platform,
|
|
166
170
|
id: device.id,
|
|
@@ -182,6 +186,7 @@ async function runLock(platformArg, idArg, opts = {}, overrides = {}) {
|
|
|
182
186
|
});
|
|
183
187
|
if (taken.status === "held") continue;
|
|
184
188
|
const facts = {
|
|
189
|
+
...taken.lease.slot ? { slot: taken.lease.slot } : {},
|
|
185
190
|
platform,
|
|
186
191
|
id: taken.lease.id,
|
|
187
192
|
deviceName: taken.lease.deviceName,
|
|
@@ -224,7 +229,10 @@ async function runUnlock(platformArg, opts = {}, overrides = {}) {
|
|
|
224
229
|
message: "Not in a React Native project (no package.json found).",
|
|
225
230
|
remedy: "Run this from the app directory -- the one holding package.json."
|
|
226
231
|
});
|
|
227
|
-
const released = d.releaseLeases(root, {
|
|
232
|
+
const released = d.releaseLeases(root, {
|
|
233
|
+
platform,
|
|
234
|
+
...opts.slot ? { slot: opts.slot } : {}
|
|
235
|
+
}, d.io);
|
|
228
236
|
if (released.length === 0) d.note(chalk.dim(`No ${platform ? `${platform} ` : ""}device lease to release for ${root}. \`stim status\` lists every lease on this machine.`));
|
|
229
237
|
if (json) d.out(JSON.stringify(released.map((lease) => ({
|
|
230
238
|
...lease,
|
|
@@ -235,10 +243,10 @@ async function runUnlock(platformArg, opts = {}, overrides = {}) {
|
|
|
235
243
|
}
|
|
236
244
|
function registerDevice(program, deps = {}) {
|
|
237
245
|
const device = program.command("device").description("Lease a connected physical device to this workspace, and give it back");
|
|
238
|
-
device.command("lock").argument("<platform>", "ios or android").argument("[id]", "the UDID or serial to lease; without one, the first free connected device is used").description("Lease a connected physical device to this workspace for a declared time, so another workspace's `--device` run waits instead of installing over it. Nothing but this command and a `--device` run moves the expiry.").option("--for <duration>", `How long to hold it: a whole number of seconds or minutes from 10s to 30m (default ${DEFAULT_FOR})`).option("--wait <seconds>", "How long to wait for a device another workspace holds, before refusing with STIM_DEVICE_BUSY (default 60, 0 refuses at once)").option("--json", "print the lease as JSON on stdout").action(async (platform, id, opts) => {
|
|
246
|
+
device.command("lock").argument("<platform>", "ios or android").argument("[id]", "the UDID or serial to lease; without one, the first free connected device is used").description("Lease a connected physical device to this workspace for a declared time, so another workspace's `--device` run waits instead of installing over it. Nothing but this command and a `--device` run moves the expiry.").option("--for <duration>", `How long to hold it: a whole number of seconds or minutes from 10s to 30m (default ${DEFAULT_FOR})`).option("--wait <seconds>", "How long to wait for a device another workspace holds, before refusing with STIM_DEVICE_BUSY (default 60, 0 refuses at once)").option("--slot <name>", "Use this workspace device slot", validateDeviceSlot).option("--json", "print the lease as JSON on stdout").action(async (platform, id, opts) => {
|
|
239
247
|
if ("code" in await runLock(platform, id, opts, deps)) process.exit(1);
|
|
240
248
|
});
|
|
241
|
-
device.command("unlock").argument("[platform]", "ios or android; without one, every lease this workspace holds is released").description("Release the device lease or leases this workspace holds. Releasing nothing is not an error.").option("--json", "print the released leases as JSON on stdout").action(async (platform, opts) => {
|
|
249
|
+
device.command("unlock").argument("[platform]", "ios or android; without one, every lease this workspace holds is released").description("Release the device lease or leases this workspace holds. Releasing nothing is not an error.").option("--slot <name>", "Use this workspace device slot", validateDeviceSlot).option("--json", "print the released leases as JSON on stdout").action(async (platform, opts) => {
|
|
242
250
|
const result = await runUnlock(platform, opts, deps);
|
|
243
251
|
if (!Array.isArray(result)) process.exit(1);
|
|
244
252
|
});
|