stim 1.1.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.
Files changed (64) hide show
  1. package/README.md +37 -0
  2. package/dist/{android-CC5bUer7.mjs → android-BW8i_YxU.mjs} +1 -1
  3. package/dist/android-BqVIJoG7.mjs +1368 -0
  4. package/dist/{android-DnZlT3aU.mjs → android-CqcW1hrP.mjs} +205 -58
  5. package/dist/{android-cas-DGlTStWu.mjs → android-cas-BR5YJBYv.mjs} +6 -6
  6. package/dist/android-cas-compiler.mjs +1 -1
  7. package/dist/{app-install-oZIQRbdD.mjs → app-install-DIp9riNx.mjs} +29 -21
  8. package/dist/{build-slots-CgacA9zQ.mjs → build-slots-C3-R4XQr.mjs} +3 -3
  9. package/dist/{cache-manifest-oeH18ERr.mjs → cache-manifest-CXR-Y8pH.mjs} +1 -1
  10. package/dist/cache-manifest.mjs +1 -1
  11. package/dist/cli.mjs +13 -13
  12. package/dist/collector-run.d.mts +4 -1
  13. package/dist/collector-run.mjs +36 -11
  14. package/dist/{config-D7uu8Gbq.mjs → config-CcefMY2Q.mjs} +94 -13
  15. package/dist/{deps-64wVnpZc.mjs → deps-Spz8ozgo.mjs} +2 -2
  16. package/dist/{dev-client-Bvky1hya.mjs → dev-client-BjisHDB5.mjs} +250 -10
  17. package/dist/{device-DJKv5Qej.mjs → device-BkiW-gkk.mjs} +19 -11
  18. package/dist/{device-lease-D-G3SgVe.mjs → device-lease-BJYybF-2.mjs} +107 -83
  19. package/dist/{device-pool-BlHXnYYs.mjs → device-pool-CPbLGfrB.mjs} +19 -13
  20. package/dist/{device-remote-CCgkG82J.mjs → device-remote-AkYCKS8U.mjs} +8 -8
  21. package/dist/{doctor-DlVxTtMq.mjs → doctor-Bmv86UUW.mjs} +18 -26
  22. package/dist/{doctor-BRF9aAJ3.mjs → doctor-fAMXC4vl.mjs} +10 -10
  23. package/dist/{error-diagnostics-B5IJwv3-.mjs → error-diagnostics-DHC31rXA.mjs} +65 -55
  24. package/dist/{exec-bsN9MJXb.mjs → exec-CyylIdq9.mjs} +4 -2
  25. package/dist/{gc-DhXdgSdt.mjs → gc-GGu-8ULN.mjs} +90 -67
  26. package/dist/{guide-DrhrDBo5.mjs → guide-92CPA21s.mjs} +414 -47
  27. package/dist/{ios-CK1Vrk6Q.mjs → ios-BSXFeO5H.mjs} +200 -59
  28. package/dist/{ios-BZxKPuqP.mjs → ios-Deh5js5V.mjs} +147 -40
  29. package/dist/{ios-device-CdSm0GDw.mjs → ios-device-CWOM_S44.mjs} +2 -2
  30. package/dist/{ios-device-CYbuRNXR.mjs → ios-device-D3pzTKQE.mjs} +1 -1
  31. package/dist/{logs-Dxoy16Qv.mjs → logs-DPNF4C9s.mjs} +9 -6
  32. package/dist/{logs-query-oqn47Ffx.mjs → logs-query-R-Cqc6Qv.mjs} +22 -5
  33. package/dist/{metro-B01H3EuD.mjs → metro-CORqXyTL.mjs} +3 -3
  34. package/dist/{ndjson-DcAtEx_K.mjs → ndjson-BZwI1NBv.mjs} +5 -2
  35. package/dist/{ownership-D8HmSv2E.mjs → ownership-8O073GIC.mjs} +3 -3
  36. package/dist/{ownership-B9QIdQOw.mjs → ownership-BpCKiRlQ.mjs} +4 -4
  37. package/dist/{ownership-claim-CeJLEpjD.mjs → ownership-claim-Bf2CAfw3.mjs} +4 -3
  38. package/dist/{project-COj6nzzh.mjs → project-fKmuIaMI.mjs} +2 -2
  39. package/dist/{reclaim-BPvN2HlP.mjs → reclaim-iW9Cad7k.mjs} +80 -73
  40. package/dist/{reload-DYZEBbZy.mjs → reload-DazHDSFA.mjs} +32 -24
  41. package/dist/{server-bare-BYHqEeyw.mjs → server-bare-DEzPZLvf.mjs} +7 -2
  42. package/dist/server-expo-44em5Pmg.mjs +2 -0
  43. package/dist/{server-expo-BEMr_m6p.mjs → server-expo-DizT4OMk.mjs} +5 -5
  44. package/dist/{settings-BSwrY4FZ.mjs → settings-DEJf6_kJ.mjs} +26 -5
  45. package/dist/slot-launch-cci0xvmC.mjs +19 -0
  46. package/dist/{start-ByCylYtB.mjs → start-mObjQH2W.mjs} +12 -12
  47. package/dist/{state-IYEY-zi8.mjs → state-B3XA2wp2.mjs} +9 -9
  48. package/dist/{stats-wqxPm7Tv.mjs → stats-BR-kwj0B.mjs} +1 -1
  49. package/dist/{stats-DZ4Q5J-7.mjs → stats-CaaQr4yN.mjs} +3 -3
  50. package/dist/{status-Dia0XTqG.mjs → status-BxQNqu9t.mjs} +86 -25
  51. package/dist/{stop-D6o5zAdm.mjs → stop-CrHGcjrV.mjs} +2 -2
  52. package/dist/{stop-BApyaF21.mjs → stop-DimGXEKY.mjs} +93 -42
  53. package/dist/supervisor-run.d.mts +1 -2
  54. package/dist/supervisor-run.mjs +5 -5
  55. package/dist/{remote-cache-DOi5-zLj.mjs → teardown-ClFp1Aax.mjs} +889 -830
  56. package/dist/{workspace-process-lock-CxOO9-pf.mjs → workspace-process-lock-3NcQnZ0X.mjs} +1 -1
  57. package/dist/{worktree-DxEmUI_n.mjs → worktree-DxTgN1CJ.mjs} +16 -16
  58. package/dist/{worktree-Dx3ZC5lS.mjs → worktree-Eq6TLH8N.mjs} +7 -3
  59. package/dist/{xcode-CDB6fytw.mjs → xcode-D7sAp-Zy.mjs} +256 -103
  60. package/package.json +5 -5
  61. package/shim/bundle-response.cjs +5 -0
  62. package/dist/android-xmyGBNy5.mjs +0 -665
  63. package/dist/build-lock-vE45ZzJZ.mjs +0 -645
  64. package/dist/server-expo-rb0k-0w7.mjs +0 -2
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as getExecutor } from "./exec-bsN9MJXb.mjs";
2
+ import { t as getExecutor } from "./exec-CyylIdq9.mjs";
3
3
  import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
4
4
  import { createHash, randomUUID } from "node:crypto";
5
5
  import { join } from "node:path";
@@ -1,8 +1,8 @@
1
- import { t as getExecutor } from "./exec-bsN9MJXb.mjs";
2
- import { o as parseNdjsonText } from "./ndjson-DcAtEx_K.mjs";
3
- import { t as DEV_MENU_LAUNCH_ARGS } from "./ios-device-CYbuRNXR.mjs";
1
+ import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
+ import { o as parseNdjsonText } from "./ndjson-BZwI1NBv.mjs";
3
+ import { t as DEV_MENU_LAUNCH_ARGS } from "./ios-device-D3pzTKQE.mjs";
4
4
  import { s as isAppLaunchError } from "./command-output-8wHMEde0.mjs";
5
- import { C as uninstallIosApp, f as listUserApps } from "./ios-BZxKPuqP.mjs";
5
+ import { l as iosSimulatorFailureAdvice, p as listUserApps, w as uninstallIosApp } from "./ios-Deh5js5V.mjs";
6
6
  import { closeSync, lstatSync, openSync, readFileSync, readSync, readdirSync } from "node:fs";
7
7
  import { createHash } from "node:crypto";
8
8
  import { join } from "node:path";
@@ -157,6 +157,10 @@ const IOS_DEV_MENU_OFF_KEYS = ["EXDevMenuShowsAtLaunch", "EXDevMenuShowFloatingA
157
157
  const DEV_CLIENT_ONBOARDING_QUERY = "disableOnboarding=1";
158
158
  const DEV_CLIENT_DISABLE_FAB_QUERY = "disableFab=1";
159
159
  const ANDROID_DISABLE_AUTO_LAUNCH_EXTRA = "EXDevMenuDisableAutoLaunch";
160
+ function describeIosSimulatorFailure(error, exec) {
161
+ const detail = describe(error);
162
+ return error?.code === "ETIMEDOUT" ? `${detail}. ${iosSimulatorFailureAdvice(exec)}` : detail;
163
+ }
160
164
  function installIosApp({ udid, appPath, bundleId = null, devClientScheme = null, proveInstalled = true }, { exec = null, now = null } = {}) {
161
165
  const e = exec || getExecutor();
162
166
  const artifactStartedAt = now?.();
@@ -171,12 +175,15 @@ function installIosApp({ udid, appPath, bundleId = null, devClientScheme = null,
171
175
  "install",
172
176
  udid,
173
177
  appPath
174
- ]);
178
+ ], {
179
+ timeoutMs: 3e5,
180
+ killSignal: "SIGKILL"
181
+ });
175
182
  } catch (err) {
176
183
  return {
177
184
  failed: true,
178
185
  code: INSTALL_ERROR,
179
- reason: `simctl install failed for ${appPath}: ${describe(err)}`
186
+ reason: `simctl install failed for ${appPath}: ${describeIosSimulatorFailure(err, e)}`
180
187
  };
181
188
  }
182
189
  const artifactFinishedAt = now?.();
@@ -193,7 +200,7 @@ function installIosApp({ udid, appPath, bundleId = null, devClientScheme = null,
193
200
  key,
194
201
  "-bool",
195
202
  "false"
196
- ]);
203
+ ], { timeoutMs: 6e4 });
197
204
  for (const key of iosSchemeApprovalKeys(bundleId, devClientScheme)) e.runFile("xcrun", [
198
205
  "simctl",
199
206
  "spawn",
@@ -204,12 +211,12 @@ function installIosApp({ udid, appPath, bundleId = null, devClientScheme = null,
204
211
  key,
205
212
  "-string",
206
213
  bundleId
207
- ]);
214
+ ], { timeoutMs: 6e4 });
208
215
  } catch (err) {
209
216
  return {
210
217
  failed: true,
211
218
  code: INSTALL_ERROR,
212
- reason: `Installed ${bundleId}, but could not prepare the dev client: ${describe(err)}`
219
+ reason: `Installed ${bundleId}, but could not prepare the dev client: ${describeIosSimulatorFailure(err, e)}`
213
220
  };
214
221
  }
215
222
  const devClientPreparationDurationMs = now && preparationStartedAt !== void 0 ? now() - preparationStartedAt : void 0;
@@ -318,12 +325,12 @@ function launchIosApp({ udid, bundleId, metroPort, devClientScheme = null, conso
318
325
  bundleId,
319
326
  "RCT_jsLocation",
320
327
  jsLocationValue(metroPort)
321
- ]);
328
+ ], { timeoutMs: 6e4 });
322
329
  } catch (err) {
323
330
  return {
324
331
  failed: true,
325
332
  code: LAUNCH_ERROR,
326
- reason: `Could not point ${bundleId} at Metro port ${metroPort} (defaults write RCT_jsLocation): ${describe(err)}`
333
+ reason: `Could not point ${bundleId} at Metro port ${metroPort} (defaults write RCT_jsLocation): ${describeIosSimulatorFailure(err, e)}`
327
334
  };
328
335
  }
329
336
  if (devClientScheme) {
@@ -349,7 +356,7 @@ function launchIosApp({ udid, bundleId, metroPort, devClientScheme = null, conso
349
356
  "openurl",
350
357
  udid,
351
358
  url
352
- ]);
359
+ ], { timeoutMs: 6e4 });
353
360
  return {
354
361
  ok: true,
355
362
  mode: "openurl",
@@ -360,7 +367,7 @@ function launchIosApp({ udid, bundleId, metroPort, devClientScheme = null, conso
360
367
  return {
361
368
  failed: true,
362
369
  code: LAUNCH_ERROR,
363
- reason: `simctl openurl ${url} failed: ${describe(err)}`
370
+ reason: `simctl openurl ${url} failed: ${describeIosSimulatorFailure(err, e)}`
364
371
  };
365
372
  }
366
373
  }
@@ -377,7 +384,7 @@ function launchIosApp({ udid, bundleId, metroPort, devClientScheme = null, conso
377
384
  return {
378
385
  failed: true,
379
386
  code: LAUNCH_ERROR,
380
- reason: `simctl launch ${bundleId} failed: ${describe(err)}`
387
+ reason: `simctl launch ${bundleId} failed: ${describeIosSimulatorFailure(err, e)}`
381
388
  };
382
389
  }
383
390
  }
@@ -893,7 +900,7 @@ function nativeLaunchFailure({ readCrashes, since, platform, processAlive, mode,
893
900
  waitedMs
894
901
  } : null;
895
902
  }
896
- async function verifyLaunch({ logsDir, since, metroPort = null, platform = null, mode = null, timeoutMs = VERIFY_TIMEOUT_MS, stabilityMs = STABILITY_WINDOW_MS, pollMs = VERIFY_POLL_MS, readRecords = null, readDeviceRecords = null, readClientRecords = null, readNativeCrashes = null, processAlive = null, onReadinessPending = null, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)) } = {}) {
903
+ async function verifyLaunch({ slot, logsDir, since, metroPort = null, platform = null, mode = null, requireBundleResponse, timeoutMs = VERIFY_TIMEOUT_MS, stabilityMs = STABILITY_WINDOW_MS, pollMs = VERIFY_POLL_MS, readRecords = null, readDeviceRecords = null, readClientRecords = null, readNativeCrashes = null, processAlive = null, onReadinessPending = null, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)) } = {}) {
897
904
  const read = readRecords || (() => readMetroRecords(logsDir));
898
905
  const readDevice = readDeviceRecords || (() => readNdjson(logsDir, "device.ndjson"));
899
906
  const readClient = readClientRecords || (() => readNdjson(logsDir, "client.ndjson"));
@@ -908,9 +915,10 @@ async function verifyLaunch({ logsDir, since, metroPort = null, platform = null,
908
915
  let runtimeStartedAt = null;
909
916
  let nextCrashCheck = startedAt + 1e3;
910
917
  while (true) {
911
- const metroRecords = read().filter((record) => after(record, since));
912
- const deviceRecords = readDevice().filter((record) => after(record, since));
913
- const clientRecords = readClient().filter((record) => after(record, since));
918
+ const metroRecords = read().filter((record) => after(record, since) && (slot === void 0 || record.slot === slot));
919
+ const bundleRecords = metroRecords.filter((record) => !requireBundleResponse || String(record.event).startsWith("bundle_response_"));
920
+ const deviceRecords = readDevice().filter((record) => after(record, since) && (slot === void 0 || (record.slot ?? "default") === slot));
921
+ const clientRecords = readClient().filter((record) => after(record, since) && (slot === void 0 || record.slot === slot));
914
922
  if (now() >= nextCrashCheck) {
915
923
  nextCrashCheck = now() + 2e3;
916
924
  const crash = nativeLaunchFailure({
@@ -924,14 +932,14 @@ async function verifyLaunch({ logsDir, since, metroPort = null, platform = null,
924
932
  if (crash) return crash;
925
933
  }
926
934
  if (deliveryId === null) {
927
- const request = metroRecords.find((record) => record.event === "bundle_response_started" && typeof record.requestId === "string" && isBundleProof(record, since, platform));
935
+ const request = bundleRecords.find((record) => record.event === "bundle_response_started" && typeof record.requestId === "string" && isBundleProof(record, since, platform));
928
936
  if (request) {
929
937
  deliveryId = request.requestId;
930
938
  proof = null;
931
939
  stabilityDeadline = null;
932
940
  }
933
941
  }
934
- for (const record of metroRecords) {
942
+ for (const record of bundleRecords) {
935
943
  if (isBundleProof(record, since, platform)) activity = record;
936
944
  const completed = deliveryId === null ? isBundleReadyProof(record, since, platform) : record.event === "bundle_response_finished" && record.requestId === deliveryId && isBundleProof(record, since, platform);
937
945
  if (!proof && completed) {
@@ -960,7 +968,7 @@ async function verifyLaunch({ logsDir, since, metroPort = null, platform = null,
960
968
  }
961
969
  }
962
970
  const ready = pendingAt !== null && signals.some((record) => Number(record.ts) >= pendingAt && Number(record.ts) <= readinessDeadline && appReadinessSignal(record, platform) === "ready");
963
- const bundleErrors = metroRecords.filter((record) => isFatalLaunchError(record, platform) || deliveryId !== null && record.event === "bundle_response_failed" && record.requestId === deliveryId && isBundleProof(record, since, platform));
971
+ const bundleErrors = bundleRecords.filter((record) => isFatalLaunchError(record, platform) || deliveryId !== null && record.event === "bundle_response_failed" && record.requestId === deliveryId && isBundleProof(record, since, platform));
964
972
  if (bundleErrors.length) {
965
973
  const alive = processAlive ? processAlive() : null;
966
974
  const fatalRecord = bundleErrors[bundleErrors.length - 1];
@@ -1,6 +1,6 @@
1
- import { u as getConfigDir } from "./config-D7uu8Gbq.mjs";
1
+ import { u as getConfigDir } from "./config-CcefMY2Q.mjs";
2
2
  import { a as formatElapsed } from "./command-output-8wHMEde0.mjs";
3
- import { d as readClaimSet, f as releaseClaim, h as tryAcquireClaim, s as isClaimRefusal } from "./ownership-claim-CeJLEpjD.mjs";
3
+ import { d as readClaimSet, f as releaseClaim, h as tryAcquireClaim, s as isClaimRefusal } from "./ownership-claim-Bf2CAfw3.mjs";
4
4
  import { readdirSync, statSync } from "node:fs";
5
5
  import { join } from "node:path";
6
6
  //#region src/engine/build-slots.ts
@@ -77,7 +77,7 @@ function tryAcquireBuildSlot({ max, root = null, logFile = null }) {
77
77
  return null;
78
78
  }
79
79
  function slotWaitingLine({ max, elapsedMs }) {
80
- return `${"build".padEnd(11)} waiting for a build slot (all ${max} in use, ${formatElapsed(elapsedMs)} elapsed)`;
80
+ return `${"build".padEnd(11)} waiting for a build slot (all ${max} in use, ${formatElapsed(elapsedMs)} elapsed) -- stim guide lifecycle concurrency`;
81
81
  }
82
82
  async function acquireBuildSlot({ max, root = null, logFile = null, now = Date.now, out = () => {}, sleep = sleepAsync, intervalMs = SLOT_POLL_MS, progressMs = SLOT_PROGRESS_MS, ceilingMs = SLOT_CEILING_MS }) {
83
83
  if (!max || max <= 0) return {
@@ -1,4 +1,4 @@
1
- import { u as getConfigDir } from "./config-D7uu8Gbq.mjs";
1
+ import { u as getConfigDir } from "./config-CcefMY2Q.mjs";
2
2
  import { existsSync } from "fs";
3
3
  import { homedir } from "os";
4
4
  import { join, resolve } from "path";
@@ -1,2 +1,2 @@
1
- import { a as unregister, i as registeredCaches, n as readManifest, r as register, t as manifestPath } from "./cache-manifest-oeH18ERr.mjs";
1
+ import { a as unregister, i as registeredCaches, n as readManifest, r as register, t as manifestPath } from "./cache-manifest-CXR-Y8pH.mjs";
2
2
  export { manifestPath, readManifest, register, registeredCaches, unregister };
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-BRF9aAJ3.mjs")],
7
- ["worktree", () => import("./worktree-DxEmUI_n.mjs")],
8
- ["start", () => import("./start-ByCylYtB.mjs")],
9
- ["stop", () => import("./stop-D6o5zAdm.mjs")],
10
- ["ios", () => import("./ios-CK1Vrk6Q.mjs")],
11
- ["android", () => import("./android-DnZlT3aU.mjs")],
12
- ["reload", () => import("./reload-DYZEBbZy.mjs")],
13
- ["device", () => import("./device-DJKv5Qej.mjs")],
14
- ["logs", () => import("./logs-Dxoy16Qv.mjs")],
15
- ["status", () => import("./status-Dia0XTqG.mjs")],
16
- ["stats", () => import("./stats-DZ4Q5J-7.mjs")],
17
- ["gc", () => import("./gc-DhXdgSdt.mjs")],
18
- ["guide", () => import("./guide-DrhrDBo5.mjs")]
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();
@@ -4,6 +4,7 @@ import { ChildProcess as ChildProcess$1 } from "node:child_process";
4
4
  //#region src/exec.d.ts
5
5
  interface ExecOptions {
6
6
  timeoutMs?: number;
7
+ killSignal?: NodeJS.Signals;
7
8
  cwd?: string;
8
9
  env?: Record<string, string>;
9
10
  omitEnv?: readonly string[];
@@ -54,6 +55,7 @@ declare function readCollectors(root: string): Record<string, unknown>;
54
55
  //#region src/collector/run.d.ts
55
56
  declare const PLATFORMS: string[];
56
57
  interface ParsedCollectorArgs {
58
+ slot?: string;
57
59
  platform?: string;
58
60
  root?: string;
59
61
  udid?: string;
@@ -68,6 +70,7 @@ interface ParsedCollectorArgs {
68
70
  }
69
71
  declare function parseArgs(argv: string[]): ParsedCollectorArgs;
70
72
  interface RunCollectorOptions {
73
+ slot?: string;
71
74
  platform: string;
72
75
  root: string;
73
76
  udid?: string | null;
@@ -113,7 +116,7 @@ interface RunCollectorHandle {
113
116
  startedAt: string;
114
117
  pid: number | null;
115
118
  }
116
- 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>;
117
120
  declare function main(argv?: string[]): Promise<void>;
118
121
  //#endregion
119
122
  export { PLATFORMS, ParsedCollectorArgs, RunCollectorHandle, RunCollectorOptions, main, parseArgs, readCollectors, registerCollector, runCollector, unregisterCollector };
@@ -1,11 +1,11 @@
1
- import { t as getExecutor } from "./exec-bsN9MJXb.mjs";
2
- import { T as captureProcessToken } from "./config-D7uu8Gbq.mjs";
3
- import { r as createNdjsonWriter } from "./ndjson-DcAtEx_K.mjs";
4
- import { D as workspaceLogsDir } from "./state-IYEY-zi8.mjs";
1
+ import { t as getExecutor } from "./exec-CyylIdq9.mjs";
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
- import { i as startIosDeviceConsole, r as parseDeviceConsoleLine } from "./ios-device-CYbuRNXR.mjs";
7
- import { a as unregisterCollector, i as registerCollector, r as readCollectors, t as collectorProcessTitle } from "./ownership-D8HmSv2E.mjs";
8
- import { a as watchAppPid, i as waitForAppPid, n as parseLogcatLine, r as startAndroidLogcat, t as androidClockOffset } from "./android-CC5bUer7.mjs";
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-8O073GIC.mjs";
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";
11
11
  import { fileURLToPath } from "node:url";
@@ -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, platform, process.pid, processToken);
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, platform, {
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",
@@ -1,9 +1,88 @@
1
- import { t as getExecutor } from "./exec-bsN9MJXb.mjs";
1
+ import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
2
  import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
3
3
  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].platforms = cfg.projects[projectPath].platforms || {};
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]?.platforms?.android;
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
- if (!cfg?.projects?.[projectPath]?.platforms) return;
293
- delete cfg.projects[projectPath].platforms[platform];
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 android = proj.platforms?.android;
375
- if (typeof android?.consolePort === "number") result.androidConsolePorts.push(android.consolePort);
376
- if (android?.serial && !android.avdName) result.androidPhysicalSerials.push(android.serial);
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
- import { t as getExecutor } from "./exec-bsN9MJXb.mjs";
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-CDB6fytw.mjs";
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";