stim 1.8.0 → 1.10.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 (91) hide show
  1. package/README.md +23 -155
  2. package/dist/{android-DWssqHQ8.mjs → android-787HhP5w.mjs} +168 -39
  3. package/dist/{android-BRgBYzU0.mjs → android-DBkeHwpm.mjs} +250 -200
  4. package/dist/{android-DeYdjjBP.mjs → android-DPov-fv2.mjs} +2 -2
  5. package/dist/{android-cas-SWIjzeuL.mjs → android-cas-CITmIeWY.mjs} +22 -14
  6. package/dist/android-cas-compiler.mjs +2 -2
  7. package/dist/app-install-CrJ6t_bv.mjs +819 -0
  8. package/dist/budget-BP4jZZL4.mjs +620 -0
  9. package/dist/build-progress-BXx-ZBe4.mjs +512 -0
  10. package/dist/{cache-manifest-CYJi8-Hx.mjs → cache-manifest-Pubfp8wV.mjs} +4 -4
  11. package/dist/cache-manifest.mjs +1 -1
  12. package/dist/cli.mjs +22 -19
  13. package/dist/collector-run.d.mts +7 -1
  14. package/dist/collector-run.mjs +10 -9
  15. package/dist/{command-output-B3ycpsYv.mjs → command-output-D5z2_bcF.mjs} +11 -2
  16. package/dist/{config-B7iyKWkL.mjs → config-BEq0Qw_a.mjs} +134 -121
  17. package/dist/{dependency-state-D0M7oKA7.mjs → dependency-state-r9UesA9a.mjs} +2 -2
  18. package/dist/{deps-oCXVNEqQ.mjs → deps-DP4eWqea.mjs} +8 -5
  19. package/dist/{detached-entry-Bpo6ZY_I.mjs → detached-entry-DJHoca49.mjs} +1 -1
  20. package/dist/{device-BUoZyMoR.mjs → dev-client-92U593KT.mjs} +1090 -660
  21. package/dist/{device-RdujjHLB.mjs → device-DhTZe7L4.mjs} +13 -20
  22. package/dist/{build-lock-CgtsB6TV.mjs → device-lease-CbEaIk7b.mjs} +481 -27
  23. package/dist/{device-pool-BxRwdufO.mjs → device-pool-Di8Q-9Ro.mjs} +7 -7
  24. package/dist/{device-remote-ByN8P6CB.mjs → device-remote-Dy1jGj3U.mjs} +645 -119
  25. package/dist/{doctor-BMt156qe.mjs → doctor-B-inAJmt.mjs} +137 -113
  26. package/dist/{error-diagnostics-D1Kaf-eT.mjs → error-diagnostics-xxWKNO3-.mjs} +22 -17
  27. package/dist/exec-DK8KaVHK.mjs +133 -0
  28. package/dist/{gc-v2YcoJvm.mjs → gc-ChPFZMRC.mjs} +836 -813
  29. package/dist/{guide-BTAlD1_y.mjs → guide-DwmaD_Wa.mjs} +953 -209
  30. package/dist/guide-status-CWvti2Lg.mjs +125 -0
  31. package/dist/idle-DpRLp5lj.mjs +157 -0
  32. package/dist/{build-slots-C-oAHRT1.mjs → in-use-CjUluqIX.mjs} +102 -8
  33. package/dist/{ios-BcCiI4TS.mjs → ios-B2Fpqvlb.mjs} +60 -71
  34. package/dist/{ios-BP04pY5f.mjs → ios-Dd-hYJBh.mjs} +241 -234
  35. package/dist/{ios-device-CFXA46ez.mjs → ios-device-BiOsSVzd.mjs} +3 -2
  36. package/dist/{ios-device-CauFJiYx.mjs → ios-device-CtRdEVjx.mjs} +149 -52
  37. package/dist/launch-verify-CQS_Kg-C.mjs +365 -0
  38. package/dist/{logs-CakdBTtm.mjs → logs-MARIy6m9.mjs} +20 -15
  39. package/dist/metro-BWLOJ_Wr.mjs +409 -0
  40. package/dist/{named-ports-DzMOlmaX.mjs → named-ports-DiTfSz6p.mjs} +4 -4
  41. package/dist/{ndjson-BZwI1NBv.mjs → ndjson-CDY9IkVC.mjs} +23 -43
  42. package/dist/{ndjson-DvHSsNU9.d.mts → ndjson-Du3j0AQ9.d.mts} +1 -0
  43. package/dist/{ownership-Cp52QAK1.mjs → ownership-D2xbLVoz.mjs} +2 -3
  44. package/dist/{teardown-BJqEzPo_.mjs → ownership-DZjdx8Gq.mjs} +664 -612
  45. package/dist/{ports-BIbJaKNW.mjs → ports-CLSSFOuz.mjs} +3 -3
  46. package/dist/{process-output-Dl5uKE03.mjs → process-output-Fpl8PYi8.mjs} +1 -1
  47. package/dist/{project-DDZWf_16.mjs → project-BXZzOABt.mjs} +8 -3
  48. package/dist/{reload-BdypPbjM.mjs → reload-DoETJ3rl.mjs} +11 -13
  49. package/dist/{server-bare-BQkyxa_b.mjs → server-bare-DVbb7kXH.mjs} +2 -2
  50. package/dist/{server-expo-BVAA1OqJ.mjs → server-expo-BvOCeakp.mjs} +8 -7
  51. package/dist/server-expo-DBVTdult.mjs +2 -0
  52. package/dist/{settings-BXXorVM2.mjs → settings-DOugPlPz.mjs} +59 -159
  53. package/dist/settings-EWYNwe72.mjs +378 -0
  54. package/dist/settings.schema.json +1064 -0
  55. package/dist/simslim-Ca3487H6.mjs +436 -0
  56. package/dist/{slot-launch-ZUUe0TQP.mjs → slot-launch-C5cg0fmk.mjs} +4 -4
  57. package/dist/spawn-claims-CUleC2cM.mjs +49 -0
  58. package/dist/{start-CmxtOKjU.mjs → start-F9gv5dcC.mjs} +159 -114
  59. package/dist/start-VoFsPSes.mjs +2 -0
  60. package/dist/{state-DZobzxLW.mjs → state-WGdIbi_d.mjs} +5 -3
  61. package/dist/{stats-BYcZ6n1h.mjs → stats-BAgJaLA4.mjs} +66 -5
  62. package/dist/{stats-CLg5RuDS.mjs → stats-DBYJ5bxL.mjs} +4 -4
  63. package/dist/status-DLCD-o3s.mjs +764 -0
  64. package/dist/status-Dr4fRkym.mjs +216 -0
  65. package/dist/{guide-status-Craq9z0t.mjs → stim-installations-CINXy_su.mjs} +3 -122
  66. package/dist/{stop-C8JW6kKp.mjs → stop-Dj4pfmyv.mjs} +111 -31
  67. package/dist/{stop-B1kOpYei.mjs → stop-ZvOeTmKb.mjs} +2 -2
  68. package/dist/supervisor-run.d.mts +11 -2
  69. package/dist/supervisor-run.mjs +228 -42
  70. package/dist/{workspace-process-lock-BYfZz_S-.mjs → workspace-process-lock-BXA96myD.mjs} +14 -9
  71. package/dist/workspaces-BTUyFfkv.mjs +1003 -0
  72. package/dist/worktree-4aZg2NnI.mjs +2 -0
  73. package/dist/{worktree-CFLA-6MX.mjs → worktree-BMS7GeqB.mjs} +523 -61
  74. package/dist/{worktree-FG_ZKKdb.mjs → worktree-BlizKDpZ.mjs} +200 -54
  75. package/dist/{xcode-B6YaWeg0.mjs → xcode-B2tBkf0m.mjs} +66 -57
  76. package/package.json +6 -6
  77. package/skill/SKILL.md +2 -0
  78. package/dist/app-install-BDbsSgCl.mjs +0 -1175
  79. package/dist/dev-client-BmT-ioFI.mjs +0 -797
  80. package/dist/device-lease-Ffz_M5t0.mjs +0 -359
  81. package/dist/exec-B29XNuhP.mjs +0 -77
  82. package/dist/logs-query-BGL-Pl12.mjs +0 -290
  83. package/dist/metro-C7bcJ0uo.mjs +0 -233
  84. package/dist/ownership-wolE-0Do.mjs +0 -470
  85. package/dist/public-http-probe-CyMDrQ3c.mjs +0 -61
  86. package/dist/reclaim-KJ-0uWEF.mjs +0 -361
  87. package/dist/server-expo-YDDG3eP7.mjs +0 -2
  88. package/dist/spawn-entry-nCE6m770.mjs +0 -14
  89. package/dist/status-DdkLki47.mjs +0 -426
  90. package/dist/workspace-state-BT5E3SA2.mjs +0 -146
  91. package/dist/{metro-store-CDDZHtPl.mjs → metro-store-DzeWkfqW.mjs} +1 -1
@@ -0,0 +1,125 @@
1
+ import { f as getProject, u as getConfigDir, w as upsertProject } from "./config-BEq0Qw_a.mjs";
2
+ import { n as appProjectProblem } from "./project-BXZzOABt.mjs";
3
+ import { t as compareStimVersions } from "./stim-installations-CINXy_su.mjs";
4
+ import { mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ //#region src/guide-status.ts
7
+ const DAY_MS = 864e5;
8
+ const DOCTOR_STALE_MS = 7 * DAY_MS;
9
+ const UPDATE_CHECK_INTERVAL_MS = DAY_MS;
10
+ const UPDATE_CHECK_TIMEOUT_MS = 2e3;
11
+ const LATEST_URL = "https://registry.npmjs.org/stim/latest";
12
+ const PLATFORMS = ["ios", "android"];
13
+ function recordDoctorRun(projectRoot, platform, version, now = /* @__PURE__ */ new Date()) {
14
+ const run = {
15
+ at: now.toISOString(),
16
+ version
17
+ };
18
+ try {
19
+ if (appProjectProblem(projectRoot)) return;
20
+ upsertProject(projectRoot, (existing) => {
21
+ const doctorRuns = { ...existing.doctorRuns };
22
+ for (const target of platform ? [platform] : PLATFORMS) doctorRuns[target] = run;
23
+ return { doctorRuns };
24
+ });
25
+ } catch {}
26
+ }
27
+ function doctorDueReason(record, running, now) {
28
+ const at = record ? Date.parse(record.at) : NaN;
29
+ if (!record || Number.isNaN(at)) return "never run";
30
+ if (record.version !== running) return `last run with stim ${record.version}`;
31
+ const age = now.getTime() - at;
32
+ if (age > DOCTOR_STALE_MS) return `last run ${Math.floor(age / DAY_MS)} days ago`;
33
+ return null;
34
+ }
35
+ function updateCacheFile() {
36
+ return join(getConfigDir(), "update-check.json");
37
+ }
38
+ function readUpdateCache() {
39
+ try {
40
+ const parsed = JSON.parse(readFileSync(updateCacheFile(), "utf-8"));
41
+ if (typeof parsed?.checkedAt !== "string") return null;
42
+ return {
43
+ checkedAt: parsed.checkedAt,
44
+ latest: typeof parsed.latest === "string" ? parsed.latest : null
45
+ };
46
+ } catch {
47
+ return null;
48
+ }
49
+ }
50
+ function writeUpdateCache(cache) {
51
+ const target = updateCacheFile();
52
+ const tmp = `${target}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
53
+ try {
54
+ mkdirSync(getConfigDir(), { recursive: true });
55
+ writeFileSync(tmp, `${JSON.stringify(cache)}\n`);
56
+ renameSync(tmp, target);
57
+ } catch {
58
+ try {
59
+ unlinkSync(tmp);
60
+ } catch {}
61
+ }
62
+ }
63
+ async function fetchLatest(fetchImpl) {
64
+ const res = await fetchImpl(LATEST_URL, { signal: AbortSignal.timeout(UPDATE_CHECK_TIMEOUT_MS) });
65
+ if (!res.ok) return null;
66
+ const body = await res.json();
67
+ return typeof body.version === "string" ? body.version : null;
68
+ }
69
+ async function checkForUpdate(running, { now = /* @__PURE__ */ new Date(), fetch: fetchImpl = fetch } = {}) {
70
+ if (process.env.STIM_NO_UPDATE_CHECK) return null;
71
+ const cached = readUpdateCache();
72
+ const checkedAt = cached ? Date.parse(cached.checkedAt) : NaN;
73
+ let latest = cached?.latest ?? null;
74
+ if (Number.isNaN(checkedAt) || now.getTime() - checkedAt > UPDATE_CHECK_INTERVAL_MS) {
75
+ try {
76
+ latest = await fetchLatest(fetchImpl);
77
+ } catch {
78
+ latest = null;
79
+ }
80
+ writeUpdateCache({
81
+ checkedAt: now.toISOString(),
82
+ latest
83
+ });
84
+ }
85
+ if (!latest) return null;
86
+ const comparison = compareStimVersions(latest, running);
87
+ return comparison !== null && comparison > 0 ? latest : null;
88
+ }
89
+ function renderGuideStatus({ running, doctor, latest }) {
90
+ const lines = [];
91
+ if (doctor.length) {
92
+ const named = doctor.map((due) => `${due.platform} (${due.reason})`).join(" and ");
93
+ const command = doctor.length === 1 ? `stim doctor --platform ${doctor[0]?.platform}` : "stim doctor";
94
+ lines.push(` Doctor is due for ${named}.`, ` Run before native work: ${command}`);
95
+ }
96
+ if (latest) lines.push(` stim ${latest} is available (running ${running}): npm install -g stim@latest`);
97
+ if (!lines.length) return null;
98
+ return ["STATUS", ...lines].join("\n");
99
+ }
100
+ async function guideStatus({ projectRoot, running, now = /* @__PURE__ */ new Date(), fetch: fetchImpl = fetch }) {
101
+ const doctor = [];
102
+ if (projectRoot && !appProjectProblem(projectRoot)) {
103
+ let runs = null;
104
+ try {
105
+ runs = getProject(projectRoot)?.doctorRuns ?? {};
106
+ } catch {}
107
+ if (runs) for (const platform of PLATFORMS) {
108
+ const reason = doctorDueReason(runs[platform], running, now);
109
+ if (reason) doctor.push({
110
+ platform,
111
+ reason
112
+ });
113
+ }
114
+ }
115
+ return renderGuideStatus({
116
+ running,
117
+ doctor,
118
+ latest: await checkForUpdate(running, {
119
+ now,
120
+ fetch: fetchImpl
121
+ })
122
+ });
123
+ }
124
+ //#endregion
125
+ export { recordDoctorRun as n, guideStatus as t };
@@ -0,0 +1,157 @@
1
+ import { H as projectDeviceSlots } from "./config-BEq0Qw_a.mjs";
2
+ import { m as readWorkspaceState } from "./metro-BWLOJ_Wr.mjs";
3
+ import { o as formatLongDuration } from "./command-output-D5z2_bcF.mjs";
4
+ import { c as createActivityReader, l as idleForMs, o as parseActiveBuild, r as activeBuildState, t as ACTIVE_BUILD_KEY } from "./build-progress-BXx-ZBe4.mjs";
5
+ import { N as ownedAvdSerialResolver } from "./android-787HhP5w.mjs";
6
+ import { d as teardownOwnedAvd, f as teardownOwnedIosSim } from "./ownership-DZjdx8Gq.mjs";
7
+ import chalk from "chalk";
8
+ //#region src/commands/gc/idle.ts
9
+ function findOwnedDeviceActivity({ config, sims, androidSerial, readActivity, deadProjects = [] }) {
10
+ const booted = new Map(sims.filter((sim) => sim.state === "Booted").map((sim) => [sim.udid, sim.name]));
11
+ const dead = new Set(deadProjects);
12
+ const found = [];
13
+ for (const [project, record] of Object.entries(config?.projects ?? {})) {
14
+ if (dead.has(project)) continue;
15
+ for (const { slot, platforms } of projectDeviceSlots(record)) {
16
+ const ios = platforms.ios;
17
+ if (ios?.owned && ios.deviceUdid && booted.has(ios.deviceUdid)) found.push({
18
+ kind: "ios",
19
+ id: ios.deviceUdid,
20
+ name: booted.get(ios.deviceUdid) ?? ios.deviceUdid,
21
+ project,
22
+ slot,
23
+ activity: readActivity({
24
+ platform: "ios",
25
+ id: ios.deviceUdid,
26
+ slot,
27
+ workspace: project
28
+ })
29
+ });
30
+ const android = platforms.android;
31
+ const serial = android?.owned && android.avdName ? androidSerial(android.avdName) : null;
32
+ if (android?.avdName && serial) found.push({
33
+ kind: "android",
34
+ id: android.avdName,
35
+ name: android.avdName,
36
+ project,
37
+ slot,
38
+ activity: readActivity({
39
+ platform: "android",
40
+ id: serial,
41
+ slot,
42
+ workspace: project
43
+ })
44
+ });
45
+ }
46
+ }
47
+ return found;
48
+ }
49
+ function findIdleDevices({ buildInProgress, now, ...inputs }) {
50
+ return findOwnedDeviceActivity(inputs).filter((device) => device.activity.state === "idle").map(({ kind, id, name, project, slot, activity }) => ({
51
+ kind,
52
+ id,
53
+ name,
54
+ project,
55
+ slot,
56
+ lastActivityAt: activity.lastActivityAt ?? null,
57
+ idleForMs: idleForMs(activity, now),
58
+ buildInProgress: buildInProgress(project)
59
+ }));
60
+ }
61
+ function idleShutdownCandidates(devices, idleMs) {
62
+ return devices.filter((d) => !d.buildInProgress && d.idleForMs !== null && d.idleForMs >= idleMs);
63
+ }
64
+ function parseIdleDuration(value) {
65
+ const match = /^([1-9][0-9]*)(m|h|d)$/.exec(value.trim());
66
+ if (!match) return null;
67
+ const unit = {
68
+ m: 6e4,
69
+ h: 36e5,
70
+ d: 864e5
71
+ }[match[2]];
72
+ return Number(match[1]) * unit;
73
+ }
74
+ function workspaceBuildInProgress(project) {
75
+ const record = parseActiveBuild(readWorkspaceState(project)?.[ACTIVE_BUILD_KEY]);
76
+ if (!record) return false;
77
+ try {
78
+ return activeBuildState(record.claim) !== "stale";
79
+ } catch {
80
+ return true;
81
+ }
82
+ }
83
+ function deviceActivityInputs(config, sims, now) {
84
+ const resolve = ownedAvdSerialResolver({ timeoutMs: 5e3 });
85
+ return {
86
+ config,
87
+ sims,
88
+ readActivity: createActivityReader({ now }),
89
+ androidSerial: (avdName) => {
90
+ try {
91
+ return resolve(avdName).serial ?? null;
92
+ } catch {
93
+ return null;
94
+ }
95
+ }
96
+ };
97
+ }
98
+ function collectOwnedDeviceActivity(config, sims, now = Date.now()) {
99
+ return findOwnedDeviceActivity(deviceActivityInputs(config, sims, now));
100
+ }
101
+ function collectIdleDevices(config, sims, deadProjects, now = Date.now()) {
102
+ return findIdleDevices({
103
+ ...deviceActivityInputs(config, sims, now),
104
+ deadProjects,
105
+ now,
106
+ buildInProgress: workspaceBuildInProgress
107
+ });
108
+ }
109
+ function idleDeviceLines(devices) {
110
+ if (devices.length === 0) return [];
111
+ return [`Idle owned devices (${devices.length}) - booted with no driver, claim, or recent activity:`, ...devices.map((d) => {
112
+ const slot = d.slot === "default" ? "" : ` [${d.slot}]`;
113
+ const idle = d.idleForMs === null ? "idle for an unknown time" : `idle ${formatLongDuration(d.idleForMs)}`;
114
+ const build = d.buildInProgress ? ", build in progress" : "";
115
+ return ` ${d.kind}${slot} ${d.name} (${d.project}) -- ${idle}${build}`;
116
+ })];
117
+ }
118
+ function shutDownIdleDevices(devices, idleMs, recollect) {
119
+ const candidates = idleShutdownCandidates(devices, idleMs);
120
+ const shutDown = [];
121
+ if (candidates.length === 0) {
122
+ console.log(chalk.dim(`No owned device has been idle for ${formatLongDuration(idleMs)} or more.`));
123
+ return {
124
+ failures: 0,
125
+ shutDown
126
+ };
127
+ }
128
+ const fresh = idleShutdownCandidates(recollect(), idleMs);
129
+ let failures = 0;
130
+ for (const device of candidates) {
131
+ const what = `${device.kind} ${device.name}`;
132
+ const current = fresh.find((d) => d.kind === device.kind && d.id === device.id && d.project === device.project && d.slot === device.slot);
133
+ if (!current) {
134
+ console.log(chalk.dim(`Kept ${what}: it is no longer idle for ${formatLongDuration(idleMs)}.`));
135
+ continue;
136
+ }
137
+ const outcome = device.kind === "ios" ? teardownOwnedIosSim(device.id, { label: device.name }) : teardownOwnedAvd(device.id, { owner: {
138
+ projectPath: device.project,
139
+ slot: device.slot
140
+ } });
141
+ if (outcome.status === "torn-down") {
142
+ shutDown.push(device);
143
+ console.log(chalk.green(`Shut down ${what}, idle ${formatLongDuration(current.idleForMs)}`));
144
+ } else if (outcome.status === "missing") console.log(chalk.dim(`${what} is already gone; nothing to shut down.`));
145
+ else if (outcome.status === "skipped") console.log(chalk.yellow(`Skipped ${what}: ${outcome.reason}`));
146
+ else {
147
+ failures++;
148
+ console.log(chalk.red(`Failed to shut down ${what}: ${outcome.reason}`));
149
+ }
150
+ }
151
+ return {
152
+ failures,
153
+ shutDown
154
+ };
155
+ }
156
+ //#endregion
157
+ export { parseIdleDuration as a, idleShutdownCandidates as i, collectOwnedDeviceActivity as n, shutDownIdleDevices as o, idleDeviceLines as r, workspaceBuildInProgress as s, collectIdleDevices as t };
@@ -1,7 +1,14 @@
1
- import { E as ownership_claim_exports, u as getConfigDir } from "./config-B7iyKWkL.mjs";
2
- import { a as formatElapsed } from "./command-output-B3ycpsYv.mjs";
3
- import { readdirSync, statSync } from "node:fs";
4
- import { join } from "node:path";
1
+ import { f as getProject, k as ownership_claim_exports, u as getConfigDir } from "./config-BEq0Qw_a.mjs";
2
+ import { O as workspaceDir, m as readWorkspaceState } from "./metro-BWLOJ_Wr.mjs";
3
+ import { r as stopDeclaringSpawnsOn, t as declareSpawnsOn } from "./spawn-claims-CUleC2cM.mjs";
4
+ import { n as workspaceProcessLockError, r as workspaceProcessLockPath, t as withWorkspaceProcessLock } from "./workspace-process-lock-BXA96myD.mjs";
5
+ import { a as formatElapsed } from "./command-output-D5z2_bcF.mjs";
6
+ import { h as listBuildLocks } from "./device-lease-CbEaIk7b.mjs";
7
+ import { n as managedLockHolders, t as resolveSupervisorTarget } from "./ownership-DZjdx8Gq.mjs";
8
+ import { lstatSync, readdirSync, rmSync, rmdirSync, statSync, unlinkSync } from "node:fs";
9
+ import { realpathSync as realpathSync$1 } from "fs";
10
+ import { resolve } from "path";
11
+ import { join as join$1 } from "node:path";
5
12
  //#region src/engine/build-slots.ts
6
13
  const SLOT_PREFIX = "slot-";
7
14
  const SLOT_LABEL = "build slot";
@@ -12,10 +19,10 @@ function sleepAsync(ms) {
12
19
  return new Promise((resolve) => setTimeout(resolve, ms));
13
20
  }
14
21
  function buildSlotsDir() {
15
- return join(getConfigDir(), "build-slots");
22
+ return join$1(getConfigDir(), "build-slots");
16
23
  }
17
24
  function buildSlotPath(index) {
18
- return join(buildSlotsDir(), `${SLOT_PREFIX}${index}`);
25
+ return join$1(buildSlotsDir(), `${SLOT_PREFIX}${index}`);
19
26
  }
20
27
  function toRecord(holder) {
21
28
  const details = holder.details;
@@ -58,6 +65,7 @@ function tryAcquireBuildSlot({ max, root = null, logFile = null }) {
58
65
  busy = true;
59
66
  continue;
60
67
  }
68
+ declareSpawnsOn(attempt.acquired);
61
69
  return {
62
70
  acquired: true,
63
71
  path,
@@ -110,6 +118,7 @@ async function acquireBuildSlot({ max, root = null, logFile = null, now = Date.n
110
118
  }
111
119
  function releaseBuildSlot(handle) {
112
120
  if (!handle || handle.unlimited) return false;
121
+ stopDeclaringSpawnsOn(handle.claim);
113
122
  return (0, ownership_claim_exports.releaseClaim)(handle.claim);
114
123
  }
115
124
  function listBuildSlots() {
@@ -123,7 +132,7 @@ function listBuildSlots() {
123
132
  const slots = [];
124
133
  for (const name of names) {
125
134
  if (!name.startsWith(SLOT_PREFIX)) continue;
126
- const path = join(dir, name);
135
+ const path = join$1(dir, name);
127
136
  try {
128
137
  if (!statSync(path).isDirectory()) continue;
129
138
  } catch {
@@ -148,4 +157,89 @@ function listBuildSlots() {
148
157
  return slots;
149
158
  }
150
159
  //#endregion
151
- export { listBuildSlots as n, releaseBuildSlot as r, acquireBuildSlot as t };
160
+ //#region src/commands/gc/paths.ts
161
+ function canonicalPath(p) {
162
+ try {
163
+ return realpathSync$1(p);
164
+ } catch {
165
+ return resolve(p);
166
+ }
167
+ }
168
+ //#endregion
169
+ //#region src/workspace/in-use.ts
170
+ const NATIVE_RUN = "native-run";
171
+ const NATIVE_RUN_HELD = "a stim ios, android or stop run holds its native-run.lock";
172
+ function workspaceInUse(root, { supervisor = true, managedLocks = true, nativeRun = true } = {}) {
173
+ const reasons = [];
174
+ if (supervisor) {
175
+ const project = getProject(root);
176
+ const target = resolveSupervisorTarget({
177
+ state: readWorkspaceState(root)?.supervisor,
178
+ record: project?.supervisor,
179
+ reservedPort: project?.metroPort
180
+ });
181
+ if (target.status === "ours") reasons.push(`its dev server supervisor (pid ${target.pid}) is running`);
182
+ else if (target.status === "unverified") reasons.push(`its dev server supervisor cannot be verified: ${target.reason ?? "unknown identity"}`);
183
+ }
184
+ if (nativeRun) {
185
+ const claims = (0, ownership_claim_exports.readClaimSet)(workspaceProcessLockPath(workspaceDir(root), NATIVE_RUN, true));
186
+ if (claims.live.length) reasons.push(NATIVE_RUN_HELD);
187
+ else if (claims.unresolved[0]) reasons.push(`its native-run.lock cannot be resolved: ${claims.unresolved[0].reason}`);
188
+ }
189
+ const self = canonicalPath(root);
190
+ for (const [kind, entries] of [["build lock", listBuildLocks()], ["build slot", listBuildSlots()]]) for (const entry of entries) {
191
+ if (!entry.alive && !entry.unresolved) continue;
192
+ if (entry.projectRoot !== null && canonicalPath(entry.projectRoot) === self) reasons.push(`a ${entry.alive ? "live" : "unresolvable"} ${kind} at ${entry.path} names it`);
193
+ }
194
+ if (managedLocks) reasons.push(...managedLockHolders(root));
195
+ return reasons;
196
+ }
197
+ function emptyWorkspaceDir(dir) {
198
+ if (lstatSync(dir).isSymbolicLink()) {
199
+ unlinkSync(dir);
200
+ return;
201
+ }
202
+ for (const name of readdirSync(dir)) if (name !== `${NATIVE_RUN}.lock`) rmSync(join$1(dir, name), {
203
+ recursive: true,
204
+ force: true
205
+ });
206
+ }
207
+ async function withIdleWorkspace(root, fn, { purpose, ...checks }) {
208
+ try {
209
+ return await withWorkspaceProcessLock(workspaceDir(root), NATIVE_RUN, async () => {
210
+ const reasons = workspaceInUse(root, {
211
+ ...checks,
212
+ nativeRun: false
213
+ });
214
+ if (reasons.length) return {
215
+ ran: false,
216
+ reasons
217
+ };
218
+ return {
219
+ ran: true,
220
+ value: await fn()
221
+ };
222
+ }, {
223
+ external: true,
224
+ waitMs: 0,
225
+ ownerPurpose: purpose
226
+ });
227
+ } catch (error) {
228
+ if (workspaceProcessLockError(error)) return {
229
+ ran: false,
230
+ reasons: [NATIVE_RUN_HELD]
231
+ };
232
+ if ((0, ownership_claim_exports.isClaimRefusal)(error) || (0, ownership_claim_exports.isClaimUnavailable)(error)) return {
233
+ ran: false,
234
+ reasons: [`its native-run.lock cannot be taken: ${error.reason}`]
235
+ };
236
+ throw error;
237
+ } finally {
238
+ try {
239
+ const dir = workspaceDir(root);
240
+ if (!lstatSync(dir).isSymbolicLink()) rmdirSync(dir);
241
+ } catch {}
242
+ }
243
+ }
244
+ //#endregion
245
+ export { acquireBuildSlot as a, canonicalPath as i, withIdleWorkspace as n, listBuildSlots as o, workspaceInUse as r, releaseBuildSlot as s, emptyWorkspaceDir as t };
@@ -1,7 +1,9 @@
1
- import { t as getExecutor } from "./exec-B29XNuhP.mjs";
2
- import { g as loadConfig } from "./config-B7iyKWkL.mjs";
3
- import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-Dl5uKE03.mjs";
4
- import { existsSync, mkdtempSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
1
+ import { g as loadConfig$1 } from "./config-BEq0Qw_a.mjs";
2
+ import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
+ import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
4
+ import { J as recordCreatedDevice, K as isStimOwnedSim, q as forgetCreatedDevice } from "./android-787HhP5w.mjs";
5
+ import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
6
+ import { IOS_SIMULATOR_APPS } from "@stim-cli/core/state";
5
7
  import { dirname, join, resolve } from "node:path";
6
8
  import { tmpdir } from "node:os";
7
9
  //#region src/host-memory.ts
@@ -29,15 +31,22 @@ const OPEN_OPTIONS = {
29
31
  killSignal: "SIGKILL"
30
32
  };
31
33
  function parseIosSimulatorApp(value, source = "iosSimulatorApp in machine config") {
32
- if (value === void 0 || value === "xcode") return "xcode";
33
- if (value === "siniulator") return "siniulator";
34
- const error = /* @__PURE__ */ new Error(`Invalid ${source}. Use "xcode" or "siniulator".`);
34
+ if (value === void 0) return "xcode";
35
+ if (IOS_SIMULATOR_APPS.includes(value)) return value;
36
+ const error = /* @__PURE__ */ new Error(`Invalid ${source}. Use "xcode", "siniulator", or "stim-desktop".`);
35
37
  Object.assign(error, { code: "STIM_BAD_ARG" });
36
38
  throw error;
37
39
  }
38
40
  function configuredIosSimulatorViewer(override) {
39
- const app = override ?? parseIosSimulatorApp(loadConfig()?.iosSimulatorApp);
41
+ const app = override ?? parseIosSimulatorApp(loadConfig$1()?.iosSimulatorApp);
40
42
  const exec = getExecutor();
43
+ if (app === "stim-desktop") return { open: (udid) => {
44
+ exec.runFileQuiet("open", [
45
+ "-a",
46
+ "Stim",
47
+ `stim-desktop://open?udid=${udid}`
48
+ ], OPEN_OPTIONS);
49
+ } };
41
50
  if (app === "siniulator") return { open: (udid) => {
42
51
  exec.runFileQuiet("open", [
43
52
  "-a",
@@ -96,17 +105,29 @@ function parseSimctlList(jsonOutput, { includeUnavailable = false } = {}) {
96
105
  }
97
106
  return sims;
98
107
  }
108
+ const SIMCTL_CREATE_TIMEOUT_MS = 18e4;
109
+ const SIMCTL_OPTIONS = {
110
+ timeoutMs: 6e4,
111
+ killSignal: "SIGKILL"
112
+ };
113
+ const SIMCTL_LIST_ARGS = [
114
+ "simctl",
115
+ "list",
116
+ "devices",
117
+ "--json"
118
+ ];
99
119
  function listAllIosSims({ timeoutMs = 3e4, includeUnavailable = false } = {}) {
100
- return parseSimctlList(getExecutor().runFile("xcrun", [
101
- "simctl",
102
- "list",
103
- "devices",
104
- "--json"
105
- ], {
120
+ return parseSimctlList(getExecutor().runFile("xcrun", SIMCTL_LIST_ARGS, {
106
121
  timeoutMs,
107
122
  killSignal: "SIGKILL"
108
123
  }), { includeUnavailable });
109
124
  }
125
+ async function listAllIosSimsAsync({ timeoutMs = 3e4 } = {}) {
126
+ return parseSimctlList(await getExecutor().runFileAsync("xcrun", SIMCTL_LIST_ARGS, {
127
+ timeoutMs,
128
+ killSignal: "SIGKILL"
129
+ }));
130
+ }
110
131
  function parseRuntimeVersion(runtimeId) {
111
132
  const m = runtimeId.match(/iOS-(\d+)(?:-(\d+))?$/);
112
133
  if (!m) return runtimeId;
@@ -252,7 +273,7 @@ async function bootIosSim(udid, { timeoutMs = IOS_BOOT_TIMEOUT_MS, attemptMs = B
252
273
  viewer.open(udid);
253
274
  }
254
275
  function shutdownIosSim(udid) {
255
- getExecutor().runQuiet(`xcrun simctl shutdown ${udid}`);
276
+ getExecutor().runQuiet(`xcrun simctl shutdown ${udid}`, SIMCTL_OPTIONS);
256
277
  }
257
278
  function listIosDeviceTypes() {
258
279
  const out = getExecutor().runFile("xcrun", [
@@ -349,8 +370,13 @@ function createOwnedIosSim(label, { deviceType, runtime, suffix = "" } = {}, cho
349
370
  model: choice.deviceType,
350
371
  runtime: choice.runtime
351
372
  }, suffix);
373
+ const udid = getExecutor().run(`xcrun simctl create "${name}" "${choice.deviceTypeId}" "${choice.runtimeId}"`, {
374
+ timeoutMs: SIMCTL_CREATE_TIMEOUT_MS,
375
+ killSignal: "SIGKILL"
376
+ }).trim();
377
+ recordCreatedDevice("ios", udid);
352
378
  return {
353
- udid: getExecutor().run(`xcrun simctl create "${name}" "${choice.deviceTypeId}" "${choice.runtimeId}"`).trim(),
379
+ udid,
354
380
  name,
355
381
  deviceType: choice.deviceType,
356
382
  runtime: choice.runtime
@@ -362,7 +388,14 @@ function renameIosSim(udid, name) {
362
388
  "rename",
363
389
  udid,
364
390
  name
365
- ]);
391
+ ], SIMCTL_OPTIONS);
392
+ }
393
+ function eraseIosSim(udid) {
394
+ getExecutor().runFile("xcrun", [
395
+ "simctl",
396
+ "erase",
397
+ udid
398
+ ], SIMCTL_OPTIONS);
366
399
  }
367
400
  function resetIosPrivacy(udid) {
368
401
  getExecutor().runFile("xcrun", [
@@ -371,7 +404,7 @@ function resetIosPrivacy(udid) {
371
404
  udid,
372
405
  "reset",
373
406
  "all"
374
- ]);
407
+ ], SIMCTL_OPTIONS);
375
408
  }
376
409
  function resetIosKeychain(udid) {
377
410
  getExecutor().runFile("xcrun", [
@@ -379,7 +412,7 @@ function resetIosKeychain(udid) {
379
412
  "keychain",
380
413
  udid,
381
414
  "reset"
382
- ]);
415
+ ], SIMCTL_OPTIONS);
383
416
  }
384
417
  function uninstallIosApp(udid, bundleId) {
385
418
  getExecutor().runFile("xcrun", [
@@ -387,7 +420,7 @@ function uninstallIosApp(udid, bundleId) {
387
420
  "uninstall",
388
421
  udid,
389
422
  bundleId
390
- ]);
423
+ ], SIMCTL_OPTIONS);
391
424
  }
392
425
  function parseUserApps(jsonOutput) {
393
426
  const data = JSON.parse(jsonOutput);
@@ -426,63 +459,18 @@ function listUserApps(udid) {
426
459
  });
427
460
  }
428
461
  }
429
- const CONTAINER_METADATA = ".com.apple.mobile_container_manager.metadata.plist";
430
- const CLEARED_CONTAINER_DIRS = [
431
- "Documents",
432
- "Library",
433
- "tmp",
434
- "SystemData"
435
- ];
436
- const PLUTIL_TIMEOUT_MS = 1e4;
437
- function findAppDataContainer(dataPath, bundleId) {
438
- const root = join(dataPath, "Containers", "Data", "Application");
439
- let entries;
440
- try {
441
- entries = readdirSync(root);
442
- } catch (error) {
443
- if (error?.code === "ENOENT") return null;
444
- throw error;
445
- }
446
- const exec = getExecutor();
447
- for (const entry of entries) {
448
- const dir = join(root, entry);
449
- if (exec.runFile("plutil", [
450
- "-extract",
451
- "MCMMetadataIdentifier",
452
- "raw",
453
- join(dir, CONTAINER_METADATA)
454
- ], { timeoutMs: PLUTIL_TIMEOUT_MS }).trim() === bundleId) return dir;
455
- }
456
- return null;
457
- }
458
- function clearAppDataContainer(container) {
459
- for (const name of CLEARED_CONTAINER_DIRS) {
460
- const dir = join(container, name);
461
- let children;
462
- try {
463
- if (!statSync(dir).isDirectory()) throw new Error(`Expected app data path ${dir} to be a directory.`);
464
- children = readdirSync(dir);
465
- } catch (error) {
466
- if (error?.code === "ENOENT") continue;
467
- throw error;
468
- }
469
- for (const child of children) rmSync(join(dir, child), {
470
- recursive: true,
471
- force: true
472
- });
473
- }
474
- }
475
462
  function resolveOwnedIosSim(udid) {
476
463
  const sim = listAllIosSims().find((s) => s.udid === udid);
477
464
  if (!sim) return { missing: true };
478
- if (!sim.name?.startsWith("stim-")) return { notOwned: sim.name };
465
+ if (!isStimOwnedSim(sim)) return { notOwned: sim.name };
479
466
  return { sim };
480
467
  }
481
468
  function deleteIosSim(udid) {
482
469
  const result = resolveOwnedIosSim(udid);
483
470
  if (result.missing) return;
484
- if (result.notOwned) throw new Error(`Refusing to delete simulator "${result.notOwned}" (${udid}): not a Stim-owned sim (name must start with "stim-").`);
485
- getExecutor().run(`xcrun simctl delete ${udid}`);
471
+ if (result.notOwned) throw new Error(`Refusing to delete simulator "${result.notOwned}" (${udid}): not a Stim-owned sim; Stim has no record of creating it.`);
472
+ getExecutor().run(`xcrun simctl delete ${udid}`, SIMCTL_OPTIONS);
473
+ forgetCreatedDevice("ios", udid);
486
474
  }
487
475
  const PARKED_DELETE_TIMEOUT_MS = 3e4;
488
476
  function deleteParkedIosSim(udid) {
@@ -491,12 +479,13 @@ function deleteParkedIosSim(udid) {
491
479
  timeoutMs: PARKED_DELETE_TIMEOUT_MS
492
480
  }).find((entry) => entry.udid === udid);
493
481
  if (!sim) return;
494
- if (!sim.name.startsWith("stim-")) throw new Error(`Simulator ${udid} is now named "${sim.name}" and is not Stim-owned; refusing to delete it.`);
482
+ if (!isStimOwnedSim(sim)) throw new Error(`Simulator ${udid} is now named "${sim.name}" and is not Stim-owned; refusing to delete it.`);
495
483
  getExecutor().runFile("xcrun", [
496
484
  "simctl",
497
485
  "delete",
498
486
  udid
499
487
  ], { timeoutMs: PARKED_DELETE_TIMEOUT_MS });
488
+ forgetCreatedDevice("ios", udid);
500
489
  }
501
490
  function listIosRuntimes() {
502
491
  const out = getExecutor().runFile("xcrun", [
@@ -519,4 +508,4 @@ function listIosRuntimes() {
519
508
  }));
520
509
  }
521
510
  //#endregion
522
- export { shutdownIosSim as C, hostMemoryPressureAdvice as D, parseIosSimulatorApp as E, readHostMemoryPressure as O, sanitizeDeviceLabel as S, configuredIosSimulatorViewer as T, renameIosSim as _, deleteIosSim as a, resolveIosCreation as b, iosRuntimeMatches as c, listIosDeviceTypes as d, listIosRuntimes as f, parseRuntimeVersion as g, parkedSimName as h, createOwnedIosSim as i, iosSimulatorFailureAdvice as l, ownedSimName as m, bootIosSim as n, deleteParkedIosSim as o, listUserApps as p, clearAppDataContainer as r, findAppDataContainer as s, IOS_BOOT_TIMEOUT_MS as t, listAllIosSims as u, resetIosKeychain as v, uninstallIosApp as w, resolveOwnedIosSim as x, resetIosPrivacy as y };
511
+ export { sanitizeDeviceLabel as C, parseIosSimulatorApp as D, configuredIosSimulatorViewer as E, hostMemoryPressureAdvice as O, resolveOwnedIosSim as S, uninstallIosApp as T, parseSimctlList as _, deleteParkedIosSim as a, resetIosPrivacy as b, iosSimulatorFailureAdvice as c, listIosDeviceTypes as d, listIosRuntimes as f, parseRuntimeVersion as g, parkedSimName as h, deleteIosSim as i, readHostMemoryPressure as k, listAllIosSims as l, ownedSimName as m, bootIosSim as n, eraseIosSim as o, listUserApps as p, createOwnedIosSim as r, iosRuntimeMatches as s, IOS_BOOT_TIMEOUT_MS as t, listAllIosSimsAsync as u, renameIosSim as v, shutdownIosSim as w, resolveIosCreation as x, resetIosKeychain as y };