stim 1.10.0 → 1.11.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 (58) hide show
  1. package/dist/{build-progress-BXx-ZBe4.mjs → activity-BtG4Va9l.mjs} +8 -182
  2. package/dist/{android-DPov-fv2.mjs → android-BA2i_KAV.mjs} +1 -1
  3. package/dist/{android-DBkeHwpm.mjs → android-C1q4l523.mjs} +376 -99
  4. package/dist/{android-787HhP5w.mjs → android-C4zG2Tuc.mjs} +63 -18
  5. package/dist/{android-cas-CITmIeWY.mjs → android-cas-DfJkwCXW.mjs} +3 -3
  6. package/dist/{app-install-CrJ6t_bv.mjs → app-install-DHut6JnV.mjs} +1 -1
  7. package/dist/{budget-BP4jZZL4.mjs → budget-De3P2rq_.mjs} +9 -9
  8. package/dist/{dev-client-92U593KT.mjs → build-plan-CeAYgKAx.mjs} +415 -24
  9. package/dist/build-progress-CDwogz6R.mjs +179 -0
  10. package/dist/cli.mjs +14 -14
  11. package/dist/collector-run.mjs +1 -1
  12. package/dist/{deps-DP4eWqea.mjs → deps-OF7CD5Dx.mjs} +2 -2
  13. package/dist/{device-DhTZe7L4.mjs → device-BwH2iI3O.mjs} +47 -9
  14. package/dist/{device-lease-CbEaIk7b.mjs → device-lease-D83nqeMX.mjs} +51 -51
  15. package/dist/{device-pool-Di8Q-9Ro.mjs → device-pool-DHj7cdPy.mjs} +2 -2
  16. package/dist/{device-remote-Dy1jGj3U.mjs → device-remote-BQZSnnmE.mjs} +7 -7
  17. package/dist/{doctor-B-inAJmt.mjs → doctor-D8QdMRMf.mjs} +12 -12
  18. package/dist/{error-diagnostics-xxWKNO3-.mjs → error-diagnostics-UYFSQwRy.mjs} +3 -3
  19. package/dist/{gc-ChPFZMRC.mjs → gc-COZteyiq.mjs} +27 -253
  20. package/dist/{guide-DwmaD_Wa.mjs → guide-NDvTmMi-.mjs} +336 -48
  21. package/dist/{idle-DpRLp5lj.mjs → idle-NETbeNiQ.mjs} +4 -3
  22. package/dist/{in-use-CjUluqIX.mjs → in-use-DVhlSoyn.mjs} +4 -4
  23. package/dist/{ios-Dd-hYJBh.mjs → ios-CQLKpNfr.mjs} +294 -81
  24. package/dist/{ios-B2Fpqvlb.mjs → ios-DArbmwer.mjs} +16 -7
  25. package/dist/{ios-device-CtRdEVjx.mjs → ios-device-CwDZo0X4.mjs} +1 -1
  26. package/dist/{launch-verify-CQS_Kg-C.mjs → launch-verify-CuPDGXdr.mjs} +1 -1
  27. package/dist/logs-B44a1UeU.mjs +485 -0
  28. package/dist/merge-state-DDxS-XIZ.mjs +232 -0
  29. package/dist/{named-ports-DiTfSz6p.mjs → named-ports-Dc7neAlc.mjs} +1 -1
  30. package/dist/native-run-D7yB_AsB.mjs +143 -0
  31. package/dist/{ownership-DZjdx8Gq.mjs → ownership-BiojdtAp.mjs} +3 -3
  32. package/dist/{ports-CLSSFOuz.mjs → ports-DwBaRFJs.mjs} +1 -1
  33. package/dist/{reload-DoETJ3rl.mjs → reload-By9Dwsrt.mjs} +4 -4
  34. package/dist/{server-bare-DVbb7kXH.mjs → server-bare-D_RK1RCj.mjs} +1 -1
  35. package/dist/server-expo-B-CCUJZj.mjs +2 -0
  36. package/dist/{server-expo-BvOCeakp.mjs → server-expo-o6H5gcBc.mjs} +1 -1
  37. package/dist/{settings-DOugPlPz.mjs → settings-B5oFZEri.mjs} +12 -3
  38. package/dist/{settings-EWYNwe72.mjs → settings-C0ugALSC.mjs} +1 -1
  39. package/dist/settings.schema.json +52 -0
  40. package/dist/{simslim-Ca3487H6.mjs → simslim-CIR3IYJO.mjs} +150 -95
  41. package/dist/{slot-launch-C5cg0fmk.mjs → slot-launch-rggO1Esw.mjs} +2 -2
  42. package/dist/{spawn-claims-CUleC2cM.mjs → spawn-claims-BJwXTMPt.mjs} +24 -1
  43. package/dist/{start-F9gv5dcC.mjs → start-BSsf2tUs.mjs} +4 -4
  44. package/dist/{start-VoFsPSes.mjs → start-i8zc9VmS.mjs} +1 -1
  45. package/dist/{status-DLCD-o3s.mjs → status-CitcBRsC.mjs} +227 -22
  46. package/dist/{status-Dr4fRkym.mjs → status-Q-WDEGCB.mjs} +20 -6
  47. package/dist/{stop-Dj4pfmyv.mjs → stop-DIhrvu0B.mjs} +188 -27
  48. package/dist/stop-rst5mL14.mjs +3 -0
  49. package/dist/supervisor-run.mjs +7 -7
  50. package/dist/{workspace-process-lock-BXA96myD.mjs → workspace-process-lock-Cx74RSDZ.mjs} +9 -4
  51. package/dist/{workspaces-BTUyFfkv.mjs → workspaces-COD2xCz-.mjs} +15 -5
  52. package/dist/{worktree-4aZg2NnI.mjs → worktree-DhxDTK44.mjs} +1 -1
  53. package/dist/{worktree-BMS7GeqB.mjs → worktree-KOjMX3zA.mjs} +8 -8
  54. package/dist/{xcode-B2tBkf0m.mjs → xcode-B5SPD--o.mjs} +3 -3
  55. package/package.json +5 -5
  56. package/dist/logs-MARIy6m9.mjs +0 -209
  57. package/dist/server-expo-DBVTdult.mjs +0 -2
  58. package/dist/stop-ZvOeTmKb.mjs +0 -3
@@ -0,0 +1,179 @@
1
+ import { k as ownership_claim_exports } from "./config-BEq0Qw_a.mjs";
2
+ import { f as clearWorkspaceStateKey, g as updateWorkspaceState } from "./metro-BWLOJ_Wr.mjs";
3
+ import { a as formatElapsed, u as plural } from "./command-output-D5z2_bcF.mjs";
4
+ import { BUILD_PHASES } from "@stim-cli/core/state";
5
+ //#region src/engine/build-progress.ts
6
+ const ACTIVE_BUILD_KEY = "activeBuild";
7
+ const NO_BUILD_PROGRESS = {
8
+ step: () => {},
9
+ durations: () => ({}),
10
+ clear: () => {}
11
+ };
12
+ const COLD_PHASES = [
13
+ "prebuild",
14
+ "pods",
15
+ "compile"
16
+ ];
17
+ const DEVICE_PHASES = ["install", "launch"];
18
+ function startBuildProgress({ root, platform, slot, claim, now = Date.now, note = () => {} }) {
19
+ const startedAt = new Date(now()).toISOString();
20
+ const record = {
21
+ platform,
22
+ slot,
23
+ startedAt,
24
+ phase: "prepare",
25
+ phaseStartedAt: startedAt,
26
+ phases: [{
27
+ phase: "prepare",
28
+ startedAt
29
+ }],
30
+ claim: {
31
+ root: claim.root,
32
+ path: claim.path,
33
+ claimId: claim.claimId,
34
+ pid: claim.owner.pid
35
+ }
36
+ };
37
+ let warned = false;
38
+ const guard = (fn) => {
39
+ try {
40
+ fn();
41
+ } catch (error) {
42
+ if (warned) return;
43
+ warned = true;
44
+ note(`Build progress could not be recorded in the workspace state: ${error?.message || error}`);
45
+ }
46
+ };
47
+ const write = () => guard(() => updateWorkspaceState(root, (state) => ({
48
+ ...state,
49
+ [ACTIVE_BUILD_KEY]: record
50
+ })));
51
+ write();
52
+ return {
53
+ step(phase) {
54
+ if (phase === record.phase) return;
55
+ const at = new Date(now()).toISOString();
56
+ record.phase = phase;
57
+ record.phaseStartedAt = at;
58
+ record.phases.push({
59
+ phase,
60
+ startedAt: at
61
+ });
62
+ write();
63
+ },
64
+ durations() {
65
+ return phaseDurations(record.phases, now());
66
+ },
67
+ clear() {
68
+ guard(() => {
69
+ clearWorkspaceStateKey(root, ACTIVE_BUILD_KEY, (value) => parseActiveBuild(value)?.claim.claimId === record.claim.claimId);
70
+ });
71
+ }
72
+ };
73
+ }
74
+ function phaseDurations(phases, now) {
75
+ const out = {};
76
+ phases.forEach(({ phase, startedAt }, index) => {
77
+ const start = Date.parse(startedAt);
78
+ const next = phases[index + 1];
79
+ const end = next ? Date.parse(next.startedAt) : now;
80
+ if (!Number.isFinite(start) || !Number.isFinite(end)) return;
81
+ out[phase] = (out[phase] ?? 0) + Math.max(0, end - start);
82
+ });
83
+ return out;
84
+ }
85
+ function parseActiveBuild(value) {
86
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
87
+ const record = value;
88
+ const claim = record.claim;
89
+ if (record.platform !== "ios" && record.platform !== "android") return null;
90
+ if (!isPhase(record.phase)) return null;
91
+ if (typeof record.startedAt !== "string" || typeof record.phaseStartedAt !== "string") return null;
92
+ if (!claim || typeof claim.root !== "string" || typeof claim.claimId !== "string") return null;
93
+ const phases = Array.isArray(record.phases) ? record.phases.filter((entry) => isPhase(entry?.phase) && typeof entry?.startedAt === "string") : [];
94
+ return {
95
+ platform: record.platform,
96
+ slot: typeof record.slot === "string" ? record.slot : "default",
97
+ startedAt: record.startedAt,
98
+ phase: record.phase,
99
+ phaseStartedAt: record.phaseStartedAt,
100
+ phases,
101
+ claim: {
102
+ root: claim.root,
103
+ path: typeof claim.path === "string" ? claim.path : "",
104
+ claimId: claim.claimId,
105
+ pid: typeof claim.pid === "number" ? claim.pid : 0
106
+ }
107
+ };
108
+ }
109
+ function isPhase(value) {
110
+ return BUILD_PHASES.includes(value);
111
+ }
112
+ function activeBuildState(claim, survey = (0, ownership_claim_exports.readClaimSet)(claim.root)) {
113
+ if (survey.live.some((holder) => holder.claimId === claim.claimId)) return "running";
114
+ if (survey.dead.some((holder) => holder.claimId === claim.claimId)) return "stale";
115
+ return survey.unresolved.length ? "unknown" : "stale";
116
+ }
117
+ function liveOutcome(record) {
118
+ const seen = new Set(record.phases.map((entry) => entry.phase));
119
+ if (COLD_PHASES.some((phase) => seen.has(phase))) return "cold";
120
+ if (DEVICE_PHASES.some((phase) => seen.has(phase))) return "hit";
121
+ return null;
122
+ }
123
+ function median(values) {
124
+ if (!values.length) return null;
125
+ const sorted = values.toSorted((a, b) => a - b);
126
+ const mid = Math.floor(sorted.length / 2);
127
+ return sorted.length % 2 ? sorted[mid] : Math.round((sorted[mid - 1] + sorted[mid]) / 2);
128
+ }
129
+ function estimateBuild(history, platform, known, phase) {
130
+ const lists = history?.[platform];
131
+ const outcome = known ?? latestOutcome(lists);
132
+ const samples = outcome && lists?.[outcome] || [];
133
+ const phaseSamples = samples.flatMap((sample) => phase in sample.phases ? [sample.phases[phase]] : []);
134
+ return {
135
+ outcome,
136
+ expectedMs: median(samples.map((sample) => sample.durationMs)),
137
+ expectedPhaseMs: median(phaseSamples),
138
+ basis: samples.length
139
+ };
140
+ }
141
+ function latestOutcome(lists) {
142
+ let latest = null;
143
+ for (const outcome of ["hit", "cold"]) {
144
+ const last = lists?.[outcome]?.at(-1);
145
+ const at = last ? Date.parse(last.at) : NaN;
146
+ if (Number.isFinite(at) && (!latest || at > latest.at)) latest = {
147
+ outcome,
148
+ at
149
+ };
150
+ }
151
+ return latest?.outcome ?? null;
152
+ }
153
+ function buildReport(record, { state, history }) {
154
+ return {
155
+ platform: record.platform,
156
+ slot: record.slot,
157
+ state,
158
+ phase: record.phase,
159
+ startedAt: record.startedAt,
160
+ phaseStartedAt: record.phaseStartedAt,
161
+ ...estimateBuild(history, record.platform, liveOutcome(record), record.phase)
162
+ };
163
+ }
164
+ function remainingText(remainingMs) {
165
+ return remainingMs < 6e4 ? "under a minute left" : `about ${Math.ceil(remainingMs / 6e4)} min left`;
166
+ }
167
+ function buildStatusLine(report, now) {
168
+ const slot = report.slot === "default" ? "" : ` [${report.slot}]`;
169
+ if (report.state === "stale") return `build: stale ${report.platform}${slot} record from ${report.startedAt} (its run is gone; the next run replaces it)`;
170
+ const elapsedMs = Math.max(0, now - Date.parse(report.startedAt));
171
+ const head = `build: ${report.platform}${slot} ${report.phase}, ${formatElapsed(elapsedMs)} elapsed`;
172
+ if (report.state === "unknown") return `${head} (its native-run claim cannot be resolved, so it may not be running)`;
173
+ if (report.expectedMs === null) return head;
174
+ const basis = `median of ${plural(report.basis, `${report.outcome} run`)}`;
175
+ const remainingMs = report.expectedMs - elapsedMs;
176
+ return remainingMs > 0 ? `${head} -- ${remainingText(remainingMs)} (${basis})` : `${head} (usually ~${formatElapsed(report.expectedMs)}, ${basis})`;
177
+ }
178
+ //#endregion
179
+ export { buildStatusLine as a, startBuildProgress as c, buildReport as i, NO_BUILD_PROGRESS as n, estimateBuild as o, activeBuildState as r, parseActiveBuild as s, ACTIVE_BUILD_KEY as t };
package/dist/cli.mjs CHANGED
@@ -4,21 +4,21 @@ import { readFileSync } from "node:fs";
4
4
  import { Command } from "commander";
5
5
  //#region bin/cli.ts
6
6
  const commands = /* @__PURE__ */ new Map([
7
- ["doctor", () => import("./doctor-B-inAJmt.mjs")],
8
- ["worktree", () => import("./worktree-4aZg2NnI.mjs")],
9
- ["start", () => import("./start-VoFsPSes.mjs")],
10
- ["ports", () => import("./ports-CLSSFOuz.mjs")],
11
- ["stop", () => import("./stop-ZvOeTmKb.mjs")],
12
- ["ios", () => import("./ios-Dd-hYJBh.mjs")],
13
- ["android", () => import("./android-DBkeHwpm.mjs")],
14
- ["reload", () => import("./reload-DoETJ3rl.mjs")],
15
- ["device", () => import("./device-DhTZe7L4.mjs")],
16
- ["logs", () => import("./logs-MARIy6m9.mjs")],
17
- ["settings", () => import("./settings-EWYNwe72.mjs")],
18
- ["status", () => import("./status-DLCD-o3s.mjs")],
7
+ ["doctor", () => import("./doctor-D8QdMRMf.mjs")],
8
+ ["worktree", () => import("./worktree-DhxDTK44.mjs")],
9
+ ["start", () => import("./start-i8zc9VmS.mjs")],
10
+ ["ports", () => import("./ports-DwBaRFJs.mjs")],
11
+ ["stop", () => import("./stop-rst5mL14.mjs")],
12
+ ["ios", () => import("./ios-CQLKpNfr.mjs")],
13
+ ["android", () => import("./android-C1q4l523.mjs")],
14
+ ["reload", () => import("./reload-By9Dwsrt.mjs")],
15
+ ["device", () => import("./device-BwH2iI3O.mjs")],
16
+ ["logs", () => import("./logs-B44a1UeU.mjs")],
17
+ ["settings", () => import("./settings-C0ugALSC.mjs")],
18
+ ["status", () => import("./status-CitcBRsC.mjs")],
19
19
  ["stats", () => import("./stats-DBYJ5bxL.mjs")],
20
- ["gc", () => import("./gc-ChPFZMRC.mjs")],
21
- ["guide", () => import("./guide-DwmaD_Wa.mjs")]
20
+ ["gc", () => import("./gc-COZteyiq.mjs")],
21
+ ["guide", () => import("./guide-NDvTmMi-.mjs")]
22
22
  ]);
23
23
  const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
24
24
  const program = new Command();
@@ -5,7 +5,7 @@ import { j as workspaceLogsDir } from "./metro-BWLOJ_Wr.mjs";
5
5
  import { t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
6
6
  import { a as startIosDeviceConsole, i as parseDeviceConsoleLine } from "./ios-device-BiOsSVzd.mjs";
7
7
  import { a as unregisterCollector, i as registerCollector, r as readCollectors, t as collectorProcessTitle } from "./ownership-D2xbLVoz.mjs";
8
- import { a as watchAppPid, i as waitForAppPid, n as parseLogcatLine, r as startAndroidLogcat, t as androidClockOffset } from "./android-DPov-fv2.mjs";
8
+ import { a as watchAppPid, i as waitForAppPid, n as parseLogcatLine, r as startAndroidLogcat, t as androidClockOffset } from "./android-BA2i_KAV.mjs";
9
9
  import { existsSync } from "node:fs";
10
10
  import { LOG_ROTATE_BYTES } from "@stim-cli/core";
11
11
  import { isAbsolute, join, resolve } from "node:path";
@@ -1,7 +1,7 @@
1
1
  import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
2
- import { n as spawnDeclared } from "./spawn-claims-CUleC2cM.mjs";
2
+ import { a as spawnDeclared } from "./spawn-claims-BJwXTMPt.mjs";
3
3
  import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
4
- import { d as startBuildHeartbeat, m as bundlerPin, n as HEARTBEAT_INTERVAL_MS } from "./xcode-B2tBkf0m.mjs";
4
+ import { d as startBuildHeartbeat, m as bundlerPin, n as HEARTBEAT_INTERVAL_MS } from "./xcode-B5SPD--o.mjs";
5
5
  import { existsSync, readFileSync } from "node:fs";
6
6
  import { delimiter, dirname, join, relative, resolve, sep } from "node:path";
7
7
  import { homedir } from "node:os";
@@ -1,15 +1,17 @@
1
- import { V as parseDeviceSlotOption } from "./config-BEq0Qw_a.mjs";
1
+ import { H as projectDeviceSlots, R as deviceSlotKey, V as parseDeviceSlotOption, f as getProject } from "./config-BEq0Qw_a.mjs";
2
2
  import { o as findProjectRoot, t as NO_PROJECT_REFUSAL } from "./project-BXZzOABt.mjs";
3
3
  import { r as clockTime } from "./command-output-D5z2_bcF.mjs";
4
- import { n as fileLeaseIo, o as parseLeaseDuration, p as takeLease, u as releaseWorkspaceLeases } from "./device-lease-CbEaIk7b.mjs";
5
- import { B as resolvePhysicalDevice, F as physicalDeviceModel, L as probeEmulatorSerial, O as memoizeEmulatorProbe, a as androidPoolNoCandidatesRefusal, i as androidPoolCandidates, w as listAdbDevices } from "./android-787HhP5w.mjs";
6
- import { d as waitForDevice, s as parseDeviceWait, t as selectFromPool } from "./device-pool-Di8Q-9Ro.mjs";
7
- import { a as iosPoolCandidates, c as listIosDevices, o as iosPoolNoCandidatesRefusal, s as isWirelessIosDevice, u as resolveIosPhysicalDevice } from "./ios-device-CtRdEVjx.mjs";
4
+ import { n as fileLeaseIo, o as parseLeaseDuration, p as takeLease, u as releaseWorkspaceLeases } from "./device-lease-D83nqeMX.mjs";
5
+ import { A as memoizeEmulatorProbe, B as probeEmulatorSerial, R as physicalDeviceModel, S as getAvdNameForSerial, T as listAdbDevices, U as resolvePhysicalDevice, a as androidPoolCandidates, o as androidPoolNoCandidatesRefusal } from "./android-C4zG2Tuc.mjs";
6
+ import { d as waitForDevice, s as parseDeviceWait, t as selectFromPool } from "./device-pool-DHj7cdPy.mjs";
7
+ import { a as iosPoolCandidates, c as listIosDevices, o as iosPoolNoCandidatesRefusal, s as isWirelessIosDevice, u as resolveIosPhysicalDevice } from "./ios-device-CwDZo0X4.mjs";
8
8
  import chalk from "chalk";
9
9
  //#region src/commands/device.ts
10
10
  const DEFAULT_FOR = "5m";
11
11
  const DEFAULT_DEPS = {
12
12
  findProjectRoot,
13
+ getProject,
14
+ avdNameOf: getAvdNameForSerial,
13
15
  listIosDevices,
14
16
  listAdbDevices,
15
17
  physicalDeviceModel,
@@ -57,6 +59,29 @@ function resolveDevice(platform, requested, d) {
57
59
  deviceName: d.physicalDeviceModel(resolved.serial) ?? resolved.serial
58
60
  };
59
61
  }
62
+ function findOwnedVirtualDevice(project, platform, id, avdNameOf) {
63
+ let running;
64
+ for (const { slot, platforms } of projectDeviceSlots(project)) {
65
+ if (platform === "ios") {
66
+ const sim = platforms.ios;
67
+ if (sim?.owned && sim.deviceUdid && sim.deviceUdid.toLowerCase() === id.toLowerCase()) return {
68
+ id: sim.deviceUdid,
69
+ deviceName: sim.deviceName ?? null,
70
+ slot
71
+ };
72
+ continue;
73
+ }
74
+ const emulator = platforms.android;
75
+ if (!emulator?.owned || !emulator.avdName || !/^emulator-\d+$/.test(id)) continue;
76
+ running ??= { name: avdNameOf(id) };
77
+ if (running.name === emulator.avdName) return {
78
+ id,
79
+ deviceName: emulator.deviceName ?? emulator.avdName,
80
+ slot
81
+ };
82
+ }
83
+ return null;
84
+ }
60
85
  function isFailure(value) {
61
86
  return "code" in value;
62
87
  }
@@ -139,11 +164,24 @@ async function runLock(platformArg, idArg, opts = {}, overrides = {}) {
139
164
  const idLabel = platform === "ios" ? "udid" : "serial";
140
165
  const deadline = d.now() + wait.seconds * 1e3;
141
166
  const lastLine = { at: null };
142
- const device = idArg ? resolveDevice(platform, idArg, d) : await poolDevice(platform, idLabel, wait.seconds, deadline, root, d, opts.slot);
167
+ const owned = idArg ? findOwnedVirtualDevice(d.getProject(root), platform, idArg, d.avdNameOf) : null;
168
+ if (owned && opts.slot && opts.slot !== owned.slot) return report({
169
+ code: "STIM_BAD_ARG",
170
+ message: `${owned.id} is this workspace's ${platform} device in slot ${owned.slot}, not ${opts.slot}.`,
171
+ remedy: `Run \`stim device lock ${platform} ${owned.id}\` without --slot, or with --slot ${owned.slot}.`
172
+ });
173
+ const held = owned ? d.io.readHolder(root)[deviceSlotKey(platform, owned.slot)] : void 0;
174
+ if (owned && held && held.id !== owned.id) return report({
175
+ code: "STIM_DEVICE_BUSY",
176
+ message: `This workspace already leases ${held.id} for ${platform} in slot ${owned.slot}, and a workspace holds one lease per platform and slot.`,
177
+ remedy: `Release it with \`stim device unlock ${platform}${owned.slot === "default" ? "" : ` --slot ${owned.slot}`}\` once nothing depends on it, then lock ${owned.id}.`
178
+ });
179
+ const slot = owned ? owned.slot === "default" ? void 0 : owned.slot : opts.slot;
180
+ const device = owned ? owned : idArg ? resolveDevice(platform, idArg, d) : await poolDevice(platform, idLabel, wait.seconds, deadline, root, d, opts.slot);
143
181
  if (isFailure(device)) return report(device);
144
182
  for (;;) {
145
183
  const outcome = await d.waitForDevice({
146
- ...opts.slot ? { slot: opts.slot } : {},
184
+ ...slot ? { slot } : {},
147
185
  root,
148
186
  platform,
149
187
  id: device.id,
@@ -161,7 +199,7 @@ async function runLock(platformArg, idArg, opts = {}, overrides = {}) {
161
199
  lease: outcome.refusal.lease
162
200
  });
163
201
  const taken = d.takeLease({
164
- ...opts.slot ? { slot: opts.slot } : {},
202
+ ...slot ? { slot } : {},
165
203
  root,
166
204
  platform,
167
205
  id: device.id,
@@ -236,7 +274,7 @@ async function runUnlock(platformArg, opts = {}, overrides = {}) {
236
274
  }
237
275
  function registerDevice(program, deps = {}) {
238
276
  const device = program.command("device").description("Lease a connected physical device to this workspace, and give it back");
239
- 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", parseDeviceSlotOption).option("--json", "print the lease as JSON on stdout").action(async (platform, id, opts) => {
277
+ device.command("lock").argument("<platform>", "ios or android").argument("[id]", "the UDID or serial to lease, a connected device or this workspace's own simulator or emulator; without one, the first free connected device is used").description("Lease a connected physical device, or this workspace's own simulator or emulator, to this workspace for a declared time, so another workspace's `--device` run waits and agents see the device as driven. 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", parseDeviceSlotOption).option("--json", "print the lease as JSON on stdout").action(async (platform, id, opts) => {
240
278
  if ("code" in await runLock(platform, id, opts, deps)) process.exit(1);
241
279
  });
242
280
  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", parseDeviceSlotOption).option("--json", "print the released leases as JSON on stdout").action(async (platform, opts) => {
@@ -2,12 +2,12 @@ import { B as parseDeviceSlotKey, R as deviceSlotKey, W as validateDeviceSlot, k
2
2
  import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
3
  import { r as register } from "./cache-manifest-Pubfp8wV.mjs";
4
4
  import { P as withDirLock, S as sharedBuildCache, _ as withWorkspaceStateLock, m as readWorkspaceState, p as clearWorkspaceStateKeys, y as writeWorkspaceState } from "./metro-BWLOJ_Wr.mjs";
5
- import { r as stopDeclaringSpawnsOn, t as declareSpawnsOn } from "./spawn-claims-CUleC2cM.mjs";
5
+ import { n as declareSpawnsOn, o as stopDeclaringSpawnsOn } from "./spawn-claims-BJwXTMPt.mjs";
6
6
  import { a as formatElapsed, i as formatDuration, m as shortHash } from "./command-output-D5z2_bcF.mjs";
7
7
  import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
8
8
  import { existsSync as existsSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, rmSync as rmSync$1, writeFileSync as writeFileSync$1 } from "fs";
9
9
  import { join } from "path";
10
- import { buildCacheKey, resolveArtifact, storeArtifact } from "@stim-cli/core";
10
+ import { artifactIn, buildCacheKey, resolveArtifact, storeArtifact } from "@stim-cli/core";
11
11
  import { join as join$1 } from "node:path";
12
12
  import { createHash, randomBytes } from "node:crypto";
13
13
  import * as expoFingerprint from "@expo/fingerprint";
@@ -210,7 +210,7 @@ function storeBuild(platform, key, buildPath, rootOrOptions = {}) {
210
210
  overwrite: Boolean(options.overwrite),
211
211
  writeMetadata: (staging) => {
212
212
  if (Array.isArray(options.sources)) try {
213
- writeFileSync$1(join(staging, SOURCES_FILE), JSON.stringify(options.sources));
213
+ writeFileSync$1(join(staging, SOURCES_FILE), JSON.stringify(options.sources.map(withoutContents)));
214
214
  } catch {}
215
215
  if (options.assetManifest) try {
216
216
  writeFileSync$1(join(staging, ASSET_MANIFEST_FILE), JSON.stringify(options.assetManifest));
@@ -274,6 +274,12 @@ function providerUploadOutcome(result, name) {
274
274
  };
275
275
  }
276
276
  const SOURCES_FILE = "fingerprint-sources.json";
277
+ function withoutContents(source) {
278
+ return source.type === "contents" ? {
279
+ ...source,
280
+ contents: ""
281
+ } : source;
282
+ }
277
283
  function storedSources(platform, key, root = sharedBuildCache()) {
278
284
  try {
279
285
  const parsed = JSON.parse(readFileSync$1(join(entryDir(platform, key, root), SOURCES_FILE), "utf-8"));
@@ -295,37 +301,54 @@ function sourceName(source) {
295
301
  if (typeof s?.id === "string" && s.id !== "") return s.id;
296
302
  return null;
297
303
  }
298
- function diffItemName(item) {
304
+ function sourceReasons(source) {
305
+ const reasons = source?.reasons;
306
+ return Array.isArray(reasons) ? reasons.filter((reason) => typeof reason === "string") : [];
307
+ }
308
+ function diffItemChange(item) {
299
309
  const o = item;
300
- for (const candidate of [
301
- o,
302
- o?.addedSource,
303
- o?.removedSource,
304
- o?.afterSource,
305
- o?.beforeSource
306
- ]) {
307
- const name = sourceName(candidate);
308
- if (name) return name;
309
- }
310
- return null;
310
+ const [change, source] = o?.op === "added" ? ["added", o.addedSource] : o?.op === "removed" ? ["removed", o.removedSource] : ["changed", o?.afterSource ?? o?.beforeSource];
311
+ const name = sourceName(source);
312
+ return name === null ? null : {
313
+ name,
314
+ change,
315
+ reasons: sourceReasons(source)
316
+ };
311
317
  }
312
318
  function compareSourceLists(previous, current) {
313
319
  const previousByName = /* @__PURE__ */ new Map();
314
320
  for (const source of previous) {
315
321
  const name = sourceName(source);
316
- if (name !== null) previousByName.set(name, source.hash ?? null);
322
+ if (name !== null) previousByName.set(name, {
323
+ hash: source.hash ?? null,
324
+ reasons: sourceReasons(source)
325
+ });
317
326
  }
318
- const changed = [];
327
+ const changes = [];
319
328
  const seen = /* @__PURE__ */ new Set();
320
329
  for (const source of current) {
321
330
  const name = sourceName(source);
322
331
  if (name === null || seen.has(name)) continue;
323
332
  seen.add(name);
324
- const hash = source.hash;
325
- if (!previousByName.has(name) || previousByName.get(name) !== (hash ?? null)) changed.push(name);
333
+ const hash = source.hash ?? null;
334
+ const before = previousByName.get(name);
335
+ if (!before) changes.push({
336
+ name,
337
+ change: "added",
338
+ reasons: sourceReasons(source)
339
+ });
340
+ else if (before.hash !== hash) changes.push({
341
+ name,
342
+ change: "changed",
343
+ reasons: sourceReasons(source)
344
+ });
326
345
  }
327
- for (const [name] of previousByName) if (!seen.has(name)) changed.push(name);
328
- return changed;
346
+ for (const [name, before] of previousByName) if (!seen.has(name)) changes.push({
347
+ name,
348
+ change: "removed",
349
+ reasons: before.reasons
350
+ });
351
+ return changes;
329
352
  }
330
353
  function diffFingerprintSources({ previous, previousHash = null, current, differ = null }) {
331
354
  if (typeof differ === "function") try {
@@ -337,43 +360,20 @@ function diffFingerprintSources({ previous, previousHash = null, current, differ
337
360
  hash: current.hash
338
361
  });
339
362
  if (Array.isArray(items)) {
340
- const names = [];
363
+ const changes = [];
341
364
  const seen = /* @__PURE__ */ new Set();
342
365
  for (const item of items) {
343
- const name = diffItemName(item);
344
- if (name !== null && !seen.has(name)) {
345
- seen.add(name);
346
- names.push(name);
366
+ const change = diffItemChange(item);
367
+ if (change !== null && !seen.has(change.name)) {
368
+ seen.add(change.name);
369
+ changes.push(change);
347
370
  }
348
371
  }
349
- return names;
372
+ return changes;
350
373
  }
351
374
  } catch {}
352
375
  return compareSourceLists(previous, current.sources);
353
376
  }
354
- function fingerprintDiffSuffix(changed) {
355
- if (!changed.length) return "";
356
- const shown = changed.slice(0, 3).join(", ");
357
- return ` -- ${changed.length} source${changed.length === 1 ? "" : "s"} changed: ${shown}`;
358
- }
359
- function describeFingerprintMiss({ platform, current, lastBuild, root = sharedBuildCache(), differ = expoFingerprint.diffFingerprints }) {
360
- if (!lastBuild || lastBuild.platform !== platform) return null;
361
- const previousHash = typeof lastBuild.fingerprint === "string" ? lastBuild.fingerprint : null;
362
- const previousKey = typeof lastBuild.cacheKey === "string" ? lastBuild.cacheKey : null;
363
- if (!previousHash || !previousKey || previousHash === current.hash) return null;
364
- const previous = storedSources(platform, previousKey, root);
365
- if (!previous) return null;
366
- const changed = diffFingerprintSources({
367
- previous,
368
- previousHash,
369
- current,
370
- differ
371
- });
372
- return changed.length ? {
373
- changed,
374
- previousHash
375
- } : null;
376
- }
377
377
  async function refingerprintAfterMutation({ projectRoot, platform, previousHash, fingerprint = fingerprintProject }) {
378
378
  let computed = null;
379
379
  try {
@@ -1055,4 +1055,4 @@ function withHolderLock(io, root, fn) {
1055
1055
  return io.withHolderLock ? io.withHolderLock(root, fn) : fn();
1056
1056
  }
1057
1057
  //#endregion
1058
- export { resolveBuild as A, fingerprintDiffRecord as C, providerDownloadPath as D, prepareProviderDownloadDir as E, assetDiffReason as F, captureAssetManifest as I, compareAssetManifests as L, storedAssetManifest as M, untrackedMissLine as N, providerUploadOutcome as O, untrackedNativeFiles as P, readAssetManifest as R, filesystemBuildCapability as S, fingerprintProject as T, waitForBuild as _, parseLease as a, describeFingerprintMiss as b, raiseLease as c, removeExpiredLease as d, selectPoolDevice as f, releaseBuildLock as g, listBuildLocks as h, listLeaseFiles as i, storeBuild as j, refingerprintAfterMutation as k, releaseRunLease as l, acquireBuildLock as m, fileLeaseIo as n, parseLeaseDuration as o, takeLease as p, leaseIsExpired as r, parseWorkspaceLeases as s, deviceLeasePath as t, releaseWorkspaceLeases as u, waitForSharedBuild as v, fingerprintDiffSuffix as w, diffFingerprintSources as x, buildCacheKey as y };
1058
+ export { resolveBuild as A, filesystemBuildCapability as C, providerDownloadPath as D, prepareProviderDownloadDir as E, untrackedNativeFiles as F, assetDiffReason as I, captureAssetManifest as L, storedAssetManifest as M, storedSources as N, providerUploadOutcome as O, untrackedMissLine as P, compareAssetManifests as R, entryDir as S, fingerprintProject as T, waitForBuild as _, parseLease as a, buildCacheKey as b, raiseLease as c, removeExpiredLease as d, selectPoolDevice as f, releaseBuildLock as g, listBuildLocks as h, listLeaseFiles as i, storeBuild as j, refingerprintAfterMutation as k, releaseRunLease as l, acquireBuildLock as m, fileLeaseIo as n, parseLeaseDuration as o, takeLease as p, leaseIsExpired as r, parseWorkspaceLeases as s, deviceLeasePath as t, releaseWorkspaceLeases as u, waitForSharedBuild as v, fingerprintDiffRecord as w, diffFingerprintSources as x, artifactIn as y, readAssetManifest as z };
@@ -1,7 +1,7 @@
1
1
  import { R as deviceSlotKey } from "./config-BEq0Qw_a.mjs";
2
2
  import { a as formatElapsed, r as clockTime } from "./command-output-D5z2_bcF.mjs";
3
- import { a as parseLease, c as raiseLease, f as selectPoolDevice, l as releaseRunLease, n as fileLeaseIo, p as takeLease, r as leaseIsExpired, t as deviceLeasePath } from "./device-lease-CbEaIk7b.mjs";
4
- import { a as VERIFY_TIMEOUT_MS, p as APP_READINESS_TIMEOUT_MS } from "./launch-verify-CQS_Kg-C.mjs";
3
+ import { a as parseLease, c as raiseLease, f as selectPoolDevice, l as releaseRunLease, n as fileLeaseIo, p as takeLease, r as leaseIsExpired, t as deviceLeasePath } from "./device-lease-D83nqeMX.mjs";
4
+ import { a as VERIFY_TIMEOUT_MS, p as APP_READINESS_TIMEOUT_MS } from "./launch-verify-CuPDGXdr.mjs";
5
5
  //#region src/engine/device-lease-run.ts
6
6
  const LEASE_STEP_FLOOR_MS = 6e4;
7
7
  const DEBUG_VERIFY_STEP_MS = VERIFY_TIMEOUT_MS + APP_READINESS_TIMEOUT_MS;
@@ -4,14 +4,14 @@ import { r as createNdjsonWriter } from "./ndjson-CDY9IkVC.mjs";
4
4
  import { N as workspaceStateFile, O as workspaceDir, P as withDirLock, i as pidExists, j as workspaceLogsDir, k as workspaceId } from "./metro-BWLOJ_Wr.mjs";
5
5
  import { c as ownedDeviceLabel, u as resolvePackageJson } from "./project-BXZzOABt.mjs";
6
6
  import { a as clearRemoteSession, l as readRemoteSession, s as readMetroTunnel } from "./state-WGdIbi_d.mjs";
7
- import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BXA96myD.mjs";
7
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-Cx74RSDZ.mjs";
8
8
  import { n as stripAnsi } from "./process-output-Fpl8PYi8.mjs";
9
- import { B as planMetroReach } from "./settings-DOugPlPz.mjs";
10
- import { u as probePublicHttp } from "./ownership-DZjdx8Gq.mjs";
11
- import { C as sanitizeDeviceLabel } from "./ios-B2Fpqvlb.mjs";
12
- import { n as expoBinFromPackage, r as expoBinPath, s as findBinUpward } from "./server-expo-BvOCeakp.mjs";
13
- import { a as LAUNCH_ERROR, i as INSTALL_ERROR, l as devClientDeepLink } from "./app-install-CrJ6t_bv.mjs";
14
- import { c as readMetroRecords, o as isBundleProof } from "./launch-verify-CQS_Kg-C.mjs";
9
+ import { B as planMetroReach } from "./settings-B5oFZEri.mjs";
10
+ import { u as probePublicHttp } from "./ownership-BiojdtAp.mjs";
11
+ import { C as sanitizeDeviceLabel } from "./ios-DArbmwer.mjs";
12
+ import { n as expoBinFromPackage, r as expoBinPath, s as findBinUpward } from "./server-expo-o6H5gcBc.mjs";
13
+ import { a as LAUNCH_ERROR, i as INSTALL_ERROR, l as devClientDeepLink } from "./app-install-DHut6JnV.mjs";
14
+ import { c as readMetroRecords, o as isBundleProof } from "./launch-verify-CuPDGXdr.mjs";
15
15
  import { t as compareStimVersions } from "./stim-installations-CINXy_su.mjs";
16
16
  import { createRequire } from "node:module";
17
17
  import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
@@ -4,18 +4,18 @@ import { D as workspaceDerivedData, S as sharedBuildCache } from "./metro-BWLOJ_
4
4
  import { D as makeTemporaryDirectory, E as filesystemDevice, a as dirtyFingerprintFiles, h as locallyKnownUpstream, k as temporaryRoot, m as listWorktrees, s as gitCommonDir, x as resolveSourceCheckout, y as repoRoot } from "./worktree-BlizKDpZ.mjs";
5
5
  import { a as detectIsExpo, n as appProjectProblem, o as findProjectRoot } from "./project-BXZzOABt.mjs";
6
6
  import { d as quotedPath, f as refuseNoProject, l as phaseLine, u as plural } from "./command-output-D5z2_bcF.mjs";
7
- import { T as fingerprintProject, h as listBuildLocks, x as diffFingerprintSources } from "./device-lease-CbEaIk7b.mjs";
8
- import { S as hostSystemImageArch, w as listAdbDevices } from "./android-787HhP5w.mjs";
9
- import { F as settingsLayers, H as compilerCacheFallbackMessage, L as unknownSettingKeys, N as settingOrigin, O as remoteAndroidSetting, P as settingShapeErrors, T as projectOptimizations, W as resolveOptimizations, a as SETTING_SHAPE_REMEDY, i as SETTINGS_WITH_RESOLVE_TIME_FALLBACK, j as resolveSettings, k as remoteIosSetting, n as PATH_SETTINGS, v as iosSimSlimProfileSetting, y as mergeSettingsLayers } from "./settings-DOugPlPz.mjs";
10
- import { h as POOL_SETTING_REMEDY, y as parkedMaxSetting } from "./ownership-DZjdx8Gq.mjs";
11
- import { O as hostMemoryPressureAdvice, k as readHostMemoryPressure, l as listAllIosSims } from "./ios-B2Fpqvlb.mjs";
12
- import { o as listBuildSlots } from "./in-use-CjUluqIX.mjs";
13
- import { L as ownerFromConfig, M as checkEasAuth$1, R as providerFromConfig, _ as easCliSupport, g as MIN_EAS_CLI_SIMULATOR_VERSION, s as readInstalledEasCliVersion, v as easCliUpgradeRemedy, z as resolveEasCliBin } from "./device-remote-Dy1jGj3U.mjs";
7
+ import { T as fingerprintProject, h as listBuildLocks, x as diffFingerprintSources } from "./device-lease-D83nqeMX.mjs";
8
+ import { C as hostSystemImageArch, T as listAdbDevices } from "./android-C4zG2Tuc.mjs";
9
+ import { F as settingsLayers, H as compilerCacheFallbackMessage, L as unknownSettingKeys, N as settingOrigin, O as remoteAndroidSetting, P as settingShapeErrors, T as projectOptimizations, W as resolveOptimizations, a as SETTING_SHAPE_REMEDY, i as SETTINGS_WITH_RESOLVE_TIME_FALLBACK, j as resolveSettings, k as remoteIosSetting, n as PATH_SETTINGS, v as iosSimSlimProfileSetting, y as mergeSettingsLayers } from "./settings-B5oFZEri.mjs";
10
+ import { h as POOL_SETTING_REMEDY, y as parkedMaxSetting } from "./ownership-BiojdtAp.mjs";
11
+ import { O as hostMemoryPressureAdvice, k as readHostMemoryPressure, l as listAllIosSims } from "./ios-DArbmwer.mjs";
12
+ import { o as listBuildSlots } from "./in-use-DVhlSoyn.mjs";
13
+ import { L as ownerFromConfig, M as checkEasAuth$1, R as providerFromConfig, _ as easCliSupport, g as MIN_EAS_CLI_SIMULATOR_VERSION, s as readInstalledEasCliVersion, v as easCliUpgradeRemedy, z as resolveEasCliBin } from "./device-remote-BQZSnnmE.mjs";
14
14
  import { n as inspectStimVersions, t as compareStimVersions } from "./stim-installations-CINXy_su.mjs";
15
- import { a as liveOwnedDeviceCount, l as MIN_EAS_CLI_BUILD_DOWNLOAD_VERSION, n as simslimIsOnPath } from "./simslim-Ca3487H6.mjs";
16
- import { n as budgetLine, r as inspectBudget } from "./budget-BP4jZZL4.mjs";
17
- import { d as projectCmakeLauncher, g as androidPathRoomRemedy, h as androidPathRoomMessage, i as apkOutputsDir, m as androidPathRoom, r as resolveAndroidCompilerCache, t as readAndroidCasToolchain } from "./android-cas-CITmIeWY.mjs";
18
- import { h as podInstallCommand, i as ccacheEnabled, o as detectXcodeMajor } from "./xcode-B2tBkf0m.mjs";
15
+ import { a as liveOwnedDeviceCount, n as simslimIsOnPath, u as MIN_EAS_CLI_BUILD_DOWNLOAD_VERSION } from "./simslim-CIR3IYJO.mjs";
16
+ import { n as budgetLine, r as inspectBudget } from "./budget-De3P2rq_.mjs";
17
+ import { d as projectCmakeLauncher, g as androidPathRoomRemedy, h as androidPathRoomMessage, i as apkOutputsDir, m as androidPathRoom, r as resolveAndroidCompilerCache, t as readAndroidCasToolchain } from "./android-cas-DfJkwCXW.mjs";
18
+ import { h as podInstallCommand, i as ccacheEnabled, o as detectXcodeMajor } from "./xcode-B5SPD--o.mjs";
19
19
  import { n as recordDoctorRun } from "./guide-status-CWvti2Lg.mjs";
20
20
  import { n as hasInstalledDependencies, r as installedNpmTreeIsValid, t as dependencyState } from "./dependency-state-r9UesA9a.mjs";
21
21
  import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync, rmSync } from "node:fs";
@@ -1266,7 +1266,7 @@ async function detectFingerprintParity(projectRoot, { createFingerprint = expoFi
1266
1266
  previousHash: clean.hash,
1267
1267
  current: project,
1268
1268
  differ
1269
- });
1269
+ }).map((change) => change.name);
1270
1270
  return checkFingerprintParity({
1271
1271
  projectHash: project.hash,
1272
1272
  worktreeHash: clean.hash,
@@ -3,10 +3,10 @@ import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
3
  import { P as withDirLock, m as readWorkspaceState$1, s as resolveProjectMetro } from "./metro-BWLOJ_Wr.mjs";
4
4
  import { d as readWorkspaceLaunches } from "./state-WGdIbi_d.mjs";
5
5
  import { v as launchErrorPreview } from "./command-output-D5z2_bcF.mjs";
6
- import { a as parseLease, n as fileLeaseIo, t as deviceLeasePath } from "./device-lease-CbEaIk7b.mjs";
7
- import { r as androidHome, s as androidToolCwd } from "./android-787HhP5w.mjs";
6
+ import { a as parseLease, n as fileLeaseIo, t as deviceLeasePath } from "./device-lease-D83nqeMX.mjs";
7
+ import { c as androidToolCwd, i as androidHome } from "./android-C4zG2Tuc.mjs";
8
8
  import { r as deviceConsoleLevel } from "./ios-device-BiOsSVzd.mjs";
9
- import { n as parseLogcatLine, t as androidClockOffset } from "./android-DPov-fv2.mjs";
9
+ import { n as parseLogcatLine, t as androidClockOffset } from "./android-BA2i_KAV.mjs";
10
10
  import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
11
11
  import { attachExpoErrorContext, readLogRecords, sortByTs } from "@stim-cli/core/state";
12
12
  import { basename, dirname, join, resolve, sep } from "node:path";