stim 1.10.1 → 1.12.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 (72) hide show
  1. package/dist/{build-progress-BXx-ZBe4.mjs → activity-CCZJs__P.mjs} +9 -182
  2. package/dist/{android-787HhP5w.mjs → android-75Sk2rIR.mjs} +81 -19
  3. package/dist/{android-Bu4mbDzc.mjs → android-B7X-8H7h.mjs} +426 -106
  4. package/dist/{android-DPov-fv2.mjs → android-BQiKJYK0.mjs} +1 -1
  5. package/dist/{android-cas-CITmIeWY.mjs → android-cas-DzLPXiEn.mjs} +7 -7
  6. package/dist/{app-install-Cfmqr3TQ.mjs → app-install-CsZi0SAT.mjs} +1 -1
  7. package/dist/{workspaces-wrH6dXfu.mjs → budget-DbzW5VTD.mjs} +651 -32
  8. package/dist/{dev-client-DExzPiOa.mjs → build-plan-CqntbR7b.mjs} +514 -38
  9. package/dist/build-progress-h60GPV27.mjs +179 -0
  10. package/dist/{cache-manifest-Pubfp8wV.mjs → cache-manifest-DWPF4RKJ.mjs} +1 -1
  11. package/dist/cache-manifest.mjs +1 -1
  12. package/dist/cli.mjs +16 -16
  13. package/dist/collector-run.mjs +4 -4
  14. package/dist/{command-output-D5z2_bcF.mjs → command-output-BWCj5Vh-.mjs} +1 -1
  15. package/dist/{config-BEq0Qw_a.mjs → config-0jCcMM0E.mjs} +10 -1
  16. package/dist/{dependency-state-r9UesA9a.mjs → dependency-state-DNN0yfx6.mjs} +1 -1
  17. package/dist/{deps-DP4eWqea.mjs → deps-CO-_7UOm.mjs} +2 -2
  18. package/dist/{device-Czh5kO8A.mjs → device-9r9xRYDa.mjs} +49 -11
  19. package/dist/{device-lease-CbEaIk7b.mjs → device-lease-B9LAXIUH.mjs} +55 -55
  20. package/dist/{device-pool-BBWLAr55.mjs → device-pool-BoezJd-c.mjs} +4 -4
  21. package/dist/{device-remote-powUHdbK.mjs → device-remote-C0_o-tJH.mjs} +254 -579
  22. package/dist/{doctor-CmqK1LcQ.mjs → doctor-LfDlbR-N.mjs} +20 -19
  23. package/dist/{error-diagnostics-xxWKNO3-.mjs → error-diagnostics-CCxjn31V.mjs} +7 -7
  24. package/dist/{gc-zCDpmdHU.mjs → gc-C4nQFcTX.mjs} +220 -282
  25. package/dist/{guide-DwmaD_Wa.mjs → guide-BPBlh9ML.mjs} +512 -73
  26. package/dist/{guide-status-CWvti2Lg.mjs → guide-status-i9ogHt4v.mjs} +2 -2
  27. package/dist/{idle--jn7pBNe.mjs → idle-S3tFZ53_.mjs} +7 -6
  28. package/dist/{in-use-CNIWU-r-.mjs → in-use-D9XYEgFD.mjs} +7 -7
  29. package/dist/{ios-CbdqTqOc.mjs → ios-CDzWL1mj.mjs} +302 -88
  30. package/dist/{ios-COGcGEcN.mjs → ios-CZWOHTTc.mjs} +24 -19
  31. package/dist/{ios-device-DNjPXBnb.mjs → ios-device-D9cLxVr5.mjs} +1 -1
  32. package/dist/{launch-verify-BhgkYAws.mjs → launch-verify-B2oR_kFN.mjs} +2 -2
  33. package/dist/logs-SzuYudOj.mjs +485 -0
  34. package/dist/merge-state-ytFQCeh4.mjs +262 -0
  35. package/dist/{metro-BWLOJ_Wr.mjs → metro-Dzo30hvJ.mjs} +1 -1
  36. package/dist/{named-ports-DiTfSz6p.mjs → named-ports-Dn1o61D9.mjs} +3 -3
  37. package/dist/{ownership-BfPNRf3-.mjs → ownership-B8d4kXBx.mjs} +49 -16
  38. package/dist/{ownership-D2xbLVoz.mjs → ownership-Dqnrjrio.mjs} +2 -2
  39. package/dist/{ports-CLSSFOuz.mjs → ports-Dp_QTwTC.mjs} +3 -3
  40. package/dist/{project-BXZzOABt.mjs → project-B8rbutVC.mjs} +4 -72
  41. package/dist/{reload-CymyF6Kk.mjs → reload-nocmx8KB.mjs} +9 -9
  42. package/dist/remote-cache-BRsRZru5.mjs +526 -0
  43. package/dist/{server-bare-DVbb7kXH.mjs → server-bare-Gn3ssSdK.mjs} +1 -1
  44. package/dist/{server-expo-BvOCeakp.mjs → server-expo-CeT1nIIk.mjs} +2 -2
  45. package/dist/server-expo-eWi9Coof.mjs +2 -0
  46. package/dist/{settings-DOugPlPz.mjs → settings-BlyDn2Oz.mjs} +14 -5
  47. package/dist/{settings-EWYNwe72.mjs → settings-bE-Gu3Ti.mjs} +23 -11
  48. package/dist/settings.schema.json +73 -1
  49. package/dist/{simslim-DZUxzsjq.mjs → simslim-COtEEP7x.mjs} +152 -96
  50. package/dist/{slot-launch-Dk4oTREM.mjs → slot-launch-DIACGEfH.mjs} +4 -4
  51. package/dist/{spawn-claims-CUleC2cM.mjs → spawn-claims-PT8aQpCa.mjs} +25 -2
  52. package/dist/{start-qWapeB4W.mjs → start-BFstVBlx.mjs} +1 -1
  53. package/dist/{start-RbmFPeZ7.mjs → start-BboE1HIS.mjs} +108 -17
  54. package/dist/{state-WGdIbi_d.mjs → state-DcDgzfu3.mjs} +15 -3
  55. package/dist/{stats-BAgJaLA4.mjs → stats-D8rUTPHd.mjs} +1 -1
  56. package/dist/{stats-DBYJ5bxL.mjs → stats-DiCtmluY.mjs} +4 -4
  57. package/dist/{status-Dr4fRkym.mjs → status-BUASYyy_.mjs} +92 -33
  58. package/dist/{status-Zp8kvexe.mjs → status-DI9Tjo7T.mjs} +255 -38
  59. package/dist/stim-desktop-D0xhpZt8.mjs +29 -0
  60. package/dist/stop-BI7QY76M.mjs +3 -0
  61. package/dist/{stop-DcEv5sk-.mjs → stop-Bn6v-GvS.mjs} +205 -37
  62. package/dist/supervisor-run.mjs +11 -11
  63. package/dist/{workspace-process-lock-BXA96myD.mjs → workspace-process-lock-BvvJTfo_.mjs} +10 -5
  64. package/dist/{worktree-DEG2o9F8.mjs → worktree-B5sTRskL.mjs} +1 -1
  65. package/dist/{worktree-Bd4dRvx7.mjs → worktree-ChDL5fO1.mjs} +17 -16
  66. package/dist/{worktree-BlizKDpZ.mjs → worktree-DeBQSVtR.mjs} +3 -1
  67. package/dist/{xcode-B2tBkf0m.mjs → xcode-BzplpQzc.mjs} +7 -7
  68. package/package.json +5 -5
  69. package/dist/budget-nDCEwtJv.mjs +0 -620
  70. package/dist/logs-MARIy6m9.mjs +0 -209
  71. package/dist/server-expo-DBVTdult.mjs +0 -2
  72. package/dist/stop-DI4BG4Os.mjs +0 -3
@@ -0,0 +1,179 @@
1
+ import { A as ownership_claim_exports } from "./config-0jCcMM0E.mjs";
2
+ import { f as clearWorkspaceStateKey, g as updateWorkspaceState } from "./metro-Dzo30hvJ.mjs";
3
+ import { a as formatElapsed, u as plural } from "./command-output-BWCj5Vh-.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 };
@@ -1,4 +1,4 @@
1
- import { u as getConfigDir } from "./config-BEq0Qw_a.mjs";
1
+ import { d as getConfigDir } from "./config-0jCcMM0E.mjs";
2
2
  import { existsSync } from "fs";
3
3
  import { isAbsolute, join, resolve } from "path";
4
4
  import { readCacheManifest, updateCacheManifest } from "@stim-cli/core";
@@ -1,2 +1,2 @@
1
- import { a as unregister, i as registeredCaches, n as readManifest, r as register, t as manifestPath } from "./cache-manifest-Pubfp8wV.mjs";
1
+ import { a as unregister, i as registeredCaches, n as readManifest, r as register, t as manifestPath } from "./cache-manifest-DWPF4RKJ.mjs";
2
2
  export { manifestPath, readManifest, register, registeredCaches, unregister };
package/dist/cli.mjs CHANGED
@@ -1,24 +1,24 @@
1
1
  #!/usr/bin/env node
2
- import { _ as refuseRelativeStimPaths } from "./config-BEq0Qw_a.mjs";
2
+ import { v as refuseRelativeStimPaths } from "./config-0jCcMM0E.mjs";
3
3
  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-CmqK1LcQ.mjs")],
8
- ["worktree", () => import("./worktree-DEG2o9F8.mjs")],
9
- ["start", () => import("./start-qWapeB4W.mjs")],
10
- ["ports", () => import("./ports-CLSSFOuz.mjs")],
11
- ["stop", () => import("./stop-DI4BG4Os.mjs")],
12
- ["ios", () => import("./ios-CbdqTqOc.mjs")],
13
- ["android", () => import("./android-Bu4mbDzc.mjs")],
14
- ["reload", () => import("./reload-CymyF6Kk.mjs")],
15
- ["device", () => import("./device-Czh5kO8A.mjs")],
16
- ["logs", () => import("./logs-MARIy6m9.mjs")],
17
- ["settings", () => import("./settings-EWYNwe72.mjs")],
18
- ["status", () => import("./status-Zp8kvexe.mjs")],
19
- ["stats", () => import("./stats-DBYJ5bxL.mjs")],
20
- ["gc", () => import("./gc-zCDpmdHU.mjs")],
21
- ["guide", () => import("./guide-DwmaD_Wa.mjs")]
7
+ ["doctor", () => import("./doctor-LfDlbR-N.mjs")],
8
+ ["worktree", () => import("./worktree-B5sTRskL.mjs")],
9
+ ["start", () => import("./start-BFstVBlx.mjs")],
10
+ ["ports", () => import("./ports-Dp_QTwTC.mjs")],
11
+ ["stop", () => import("./stop-BI7QY76M.mjs")],
12
+ ["ios", () => import("./ios-CDzWL1mj.mjs")],
13
+ ["android", () => import("./android-B7X-8H7h.mjs")],
14
+ ["reload", () => import("./reload-nocmx8KB.mjs")],
15
+ ["device", () => import("./device-9r9xRYDa.mjs")],
16
+ ["logs", () => import("./logs-SzuYudOj.mjs")],
17
+ ["settings", () => import("./settings-bE-Gu3Ti.mjs")],
18
+ ["status", () => import("./status-DI9Tjo7T.mjs")],
19
+ ["stats", () => import("./stats-DiCtmluY.mjs")],
20
+ ["gc", () => import("./gc-C4nQFcTX.mjs")],
21
+ ["guide", () => import("./guide-BPBlh9ML.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();
@@ -1,11 +1,11 @@
1
- import { D as process_identity_exports, R as deviceSlotKey, W as validateDeviceSlot } from "./config-BEq0Qw_a.mjs";
1
+ import { G as validateDeviceSlot, O as process_identity_exports, z as deviceSlotKey } from "./config-0jCcMM0E.mjs";
2
2
  import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
3
  import { r as createNdjsonWriter } from "./ndjson-CDY9IkVC.mjs";
4
- import { j as workspaceLogsDir } from "./metro-BWLOJ_Wr.mjs";
4
+ import { j as workspaceLogsDir } from "./metro-Dzo30hvJ.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
- 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";
7
+ import { a as unregisterCollector, i as registerCollector, r as readCollectors, t as collectorProcessTitle } from "./ownership-Dqnrjrio.mjs";
8
+ import { a as watchAppPid, i as waitForAppPid, n as parseLogcatLine, r as startAndroidLogcat, t as androidClockOffset } from "./android-BQiKJYK0.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,4 +1,4 @@
1
- import { t as NO_PROJECT_REFUSAL } from "./project-BXZzOABt.mjs";
1
+ import { t as NO_PROJECT_REFUSAL } from "./project-B8rbutVC.mjs";
2
2
  import { n as stripAnsi } from "./process-output-Fpl8PYi8.mjs";
3
3
  import { quotedPath } from "@stim-cli/core";
4
4
  import chalk from "chalk";
@@ -379,6 +379,15 @@ function releaseAndroidConsolePort(projectPath, consolePort, slot = "default") {
379
379
  return true;
380
380
  });
381
381
  }
382
+ function clearAndroidBootPending(projectPath, avdName, slot = "default") {
383
+ withConfigLock(() => {
384
+ const cfg = loadConfig();
385
+ const android = deviceSlotPlatforms(cfg?.projects?.[projectPath], slot)?.android;
386
+ if (!cfg || android?.avdName !== avdName || !android.bootPending) return;
387
+ delete android.bootPending;
388
+ saveConfig(cfg);
389
+ });
390
+ }
382
391
  function clearDevice(projectPath, platform, slot = "default", expectedId) {
383
392
  return withConfigLock(() => {
384
393
  const cfg = loadConfig();
@@ -512,4 +521,4 @@ function allConsolePortsAndSerials({ isMounted = isOnMountedVolume } = {}) {
512
521
  return result;
513
522
  }
514
523
  //#endregion
515
- export { directorySize as A, parseDeviceSlotKey as B, setSupervisor as C, process_identity_exports as D, writeConfigSetting as E, volumeRootFor as F, projectDeviceSlots as H, assignSlotDevice as I, deviceSlotFileKey as L, isOnMountedVolume as M, listMountedVolumes as N, acquireAvdClaim as O, measuredDirectorySize as P, deviceSlotKey as R, setDevice as S, withConfigLock$1 as T, removeSlotDevice as U, parseDeviceSlotOption as V, validateDeviceSlot as W, refuseRelativeStimPaths as _, clearSupervisor as a, removeProject as b, findEnclosingWorktreeRoot as c, getConfigPath$1 as d, getProject as f, loadConfig$1 as g, isPathPrefix as h, clearDevice as i, formatBytes as j, ownership_claim_exports 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, releaseAndroidConsolePort as v, upsertProject as w, saveConfig as x, releaseMetroPort as y, deviceSlotPlatforms as z };
524
+ export { ownership_claim_exports as A, deviceSlotPlatforms as B, setDevice as C, writeConfigSetting as D, withConfigLock$1 as E, measuredDirectorySize as F, validateDeviceSlot as G, parseDeviceSlotOption as H, volumeRootFor as I, assignSlotDevice as L, formatBytes as M, isOnMountedVolume as N, process_identity_exports as O, listMountedVolumes as P, deviceSlotFileKey as R, saveConfig as S, upsertProject as T, projectDeviceSlots as U, parseDeviceSlotKey as V, removeSlotDevice as W, loadConfig$1 as _, clearDevice as a, releaseMetroPort as b, ensureConfig as c, getConfigDir as d, getConfigPath$1 as f, isPathPrefix as g, getRepoSettings as h, clearAndroidBootPending as i, directorySize as j, acquireAvdClaim as k, findEnclosingWorktreeRoot as l, getProjectSettings as m, allMetroPorts as n, clearSupervisor as o, getProject as p, claimMetroPort as r, configCorruptRepair as s, allConsolePortsAndSerials as t, getConcurrencyLimits as u, refuseRelativeStimPaths as v, setSupervisor as w, removeProject as x, releaseAndroidConsolePort as y, deviceSlotKey as z };
@@ -1,5 +1,5 @@
1
1
  import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
2
- import { y as repoRoot } from "./worktree-BlizKDpZ.mjs";
2
+ import { y as repoRoot } from "./worktree-DeBQSVtR.mjs";
3
3
  import { existsSync } from "node:fs";
4
4
  import { dirname, join, relative } from "node:path";
5
5
  //#region src/dependency-state.ts
@@ -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-PT8aQpCa.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-BzplpQzc.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";
2
- import { o as findProjectRoot, t as NO_PROJECT_REFUSAL } from "./project-BXZzOABt.mjs";
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-BBWLAr55.mjs";
7
- import { a as iosPoolCandidates, c as listIosDevices, o as iosPoolNoCandidatesRefusal, s as isWirelessIosDevice, u as resolveIosPhysicalDevice } from "./ios-device-DNjPXBnb.mjs";
1
+ import { H as parseDeviceSlotOption, U as projectDeviceSlots, p as getProject, z as deviceSlotKey } from "./config-0jCcMM0E.mjs";
2
+ import { i as findProjectRoot, t as NO_PROJECT_REFUSAL } from "./project-B8rbutVC.mjs";
3
+ import { r as clockTime } from "./command-output-BWCj5Vh-.mjs";
4
+ import { n as fileLeaseIo, o as parseLeaseDuration, p as takeLease, u as releaseWorkspaceLeases } from "./device-lease-B9LAXIUH.mjs";
5
+ import { C as getAvdNameForSerial, E as listAdbDevices, G as resolvePhysicalDevice, V as probeEmulatorSerial, j as memoizeEmulatorProbe, o as androidPoolCandidates, s as androidPoolNoCandidatesRefusal, z as physicalDeviceModel } from "./android-75Sk2rIR.mjs";
6
+ import { d as waitForDevice, s as parseDeviceWait, t as selectFromPool } from "./device-pool-BoezJd-c.mjs";
7
+ import { a as iosPoolCandidates, c as listIosDevices, o as iosPoolNoCandidatesRefusal, s as isWirelessIosDevice, u as resolveIosPhysicalDevice } from "./ios-device-D9cLxVr5.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) => {
@@ -1,13 +1,13 @@
1
- import { B as parseDeviceSlotKey, R as deviceSlotKey, W as validateDeviceSlot, k as ownership_claim_exports, u as getConfigDir } from "./config-BEq0Qw_a.mjs";
1
+ import { A as ownership_claim_exports, G as validateDeviceSlot, V as parseDeviceSlotKey, d as getConfigDir, z as deviceSlotKey } from "./config-0jCcMM0E.mjs";
2
2
  import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
- import { r as register } from "./cache-manifest-Pubfp8wV.mjs";
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";
6
- import { a as formatElapsed, i as formatDuration, m as shortHash } from "./command-output-D5z2_bcF.mjs";
3
+ import { r as register } from "./cache-manifest-DWPF4RKJ.mjs";
4
+ import { P as withDirLock, S as sharedBuildCache, _ as withWorkspaceStateLock, m as readWorkspaceState, p as clearWorkspaceStateKeys, y as writeWorkspaceState } from "./metro-Dzo30hvJ.mjs";
5
+ import { n as declareSpawnsOn, o as stopDeclaringSpawnsOn } from "./spawn-claims-PT8aQpCa.mjs";
6
+ import { a as formatElapsed, i as formatDuration, m as shortHash } from "./command-output-BWCj5Vh-.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
- import { R as deviceSlotKey } from "./config-BEq0Qw_a.mjs";
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-BhgkYAws.mjs";
1
+ import { z as deviceSlotKey } from "./config-0jCcMM0E.mjs";
2
+ import { a as formatElapsed, r as clockTime } from "./command-output-BWCj5Vh-.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-B9LAXIUH.mjs";
4
+ import { a as VERIFY_TIMEOUT_MS, p as APP_READINESS_TIMEOUT_MS } from "./launch-verify-B2oR_kFN.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;