stim 1.9.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 (83) hide show
  1. package/dist/{android-_y4Ohi5z.mjs → android-787HhP5w.mjs} +119 -29
  2. package/dist/{android-BPGizezh.mjs → android-DBkeHwpm.mjs} +168 -95
  3. package/dist/{android-DEgo4oCo.mjs → android-DPov-fv2.mjs} +2 -2
  4. package/dist/{android-cas-eiExNLf7.mjs → android-cas-CITmIeWY.mjs} +9 -8
  5. package/dist/android-cas-compiler.mjs +1 -1
  6. package/dist/{app-install-Uf3a83Tv.mjs → app-install-CrJ6t_bv.mjs} +2 -2
  7. package/dist/budget-BP4jZZL4.mjs +620 -0
  8. package/dist/build-progress-BXx-ZBe4.mjs +512 -0
  9. package/dist/{cache-manifest-Dg7OnMrp.mjs → cache-manifest-Pubfp8wV.mjs} +1 -1
  10. package/dist/cache-manifest.mjs +1 -1
  11. package/dist/cli.mjs +16 -16
  12. package/dist/collector-run.d.mts +5 -1
  13. package/dist/collector-run.mjs +7 -7
  14. package/dist/{command-output-gByVmIjy.mjs → command-output-D5z2_bcF.mjs} +1 -1
  15. package/dist/{config-Cp9lM3wX.mjs → config-BEq0Qw_a.mjs} +14 -131
  16. package/dist/{dependency-state-Bq5DcLGn.mjs → dependency-state-r9UesA9a.mjs} +2 -2
  17. package/dist/{deps-Cip5vUm1.mjs → deps-DP4eWqea.mjs} +3 -3
  18. package/dist/{detached-entry-KEu0_AXL.mjs → detached-entry-DJHoca49.mjs} +1 -1
  19. package/dist/{dev-client-HyqISM0h.mjs → dev-client-92U593KT.mjs} +137 -250
  20. package/dist/{device-D4U-xLty.mjs → device-DhTZe7L4.mjs} +7 -7
  21. package/dist/{build-lock-DPXPLgTc.mjs → device-lease-CbEaIk7b.mjs} +370 -12
  22. package/dist/{device-pool-CnTAz8X1.mjs → device-pool-Di8Q-9Ro.mjs} +4 -4
  23. package/dist/{device-remote-AQ5rQPqr.mjs → device-remote-Dy1jGj3U.mjs} +547 -468
  24. package/dist/{doctor-B_Ei3bZu.mjs → doctor-B-inAJmt.mjs} +58 -22
  25. package/dist/{error-diagnostics-Vfz83GCF.mjs → error-diagnostics-xxWKNO3-.mjs} +11 -12
  26. package/dist/{exec-jIAlgilk.mjs → exec-DK8KaVHK.mjs} +3 -2
  27. package/dist/{gc-Ckw8RQHi.mjs → gc-ChPFZMRC.mjs} +500 -1114
  28. package/dist/{guide-BiAbhs1J.mjs → guide-DwmaD_Wa.mjs} +882 -528
  29. package/dist/guide-status-CWvti2Lg.mjs +125 -0
  30. package/dist/idle-DpRLp5lj.mjs +157 -0
  31. package/dist/in-use-CjUluqIX.mjs +245 -0
  32. package/dist/{ios-BL2NFckr.mjs → ios-B2Fpqvlb.mjs} +25 -59
  33. package/dist/{ios-BzUJWk1K.mjs → ios-Dd-hYJBh.mjs} +130 -89
  34. package/dist/{ios-device-DPsSm0v_.mjs → ios-device-BiOsSVzd.mjs} +1 -1
  35. package/dist/{ios-device-D972i9wf.mjs → ios-device-CtRdEVjx.mjs} +3 -3
  36. package/dist/{launch-verify-Q_OtW1Rf.mjs → launch-verify-CQS_Kg-C.mjs} +5 -5
  37. package/dist/{logs-CI9NQSK7.mjs → logs-MARIy6m9.mjs} +9 -9
  38. package/dist/{metro-DrLHaMn2.mjs → metro-BWLOJ_Wr.mjs} +116 -10
  39. package/dist/{named-ports-CmLQSIRj.mjs → named-ports-DiTfSz6p.mjs} +4 -4
  40. package/dist/{ndjson-9uFNRwfe.mjs → ndjson-CDY9IkVC.mjs} +3 -56
  41. package/dist/{ndjson-DvHSsNU9.d.mts → ndjson-Du3j0AQ9.d.mts} +1 -0
  42. package/dist/{ownership-D15Op8vN.mjs → ownership-D2xbLVoz.mjs} +2 -3
  43. package/dist/ownership-DZjdx8Gq.mjs +1180 -0
  44. package/dist/{ports-BNKoWVWZ.mjs → ports-CLSSFOuz.mjs} +3 -3
  45. package/dist/{project-DS112DYB.mjs → project-BXZzOABt.mjs} +2 -2
  46. package/dist/{reload-ZNJUo1pl.mjs → reload-DoETJ3rl.mjs} +9 -10
  47. package/dist/{server-bare-DWkkcIxV.mjs → server-bare-DVbb7kXH.mjs} +1 -1
  48. package/dist/{server-expo-B_lctBR3.mjs → server-expo-BvOCeakp.mjs} +4 -4
  49. package/dist/server-expo-DBVTdult.mjs +2 -0
  50. package/dist/{settings-D030k0t4.mjs → settings-DOugPlPz.mjs} +16 -428
  51. package/dist/{settings-BKNh5rl4.mjs → settings-EWYNwe72.mjs} +16 -14
  52. package/dist/settings.schema.json +75 -0
  53. package/dist/{simslim-B_LsGLuJ.mjs → simslim-Ca3487H6.mjs} +182 -5
  54. package/dist/{slot-launch-DU9A-gYw.mjs → slot-launch-C5cg0fmk.mjs} +4 -4
  55. package/dist/{spawn-claims-Dn3C_N6w.mjs → spawn-claims-CUleC2cM.mjs} +1 -1
  56. package/dist/{start-DYkkKF0s.mjs → start-F9gv5dcC.mjs} +147 -98
  57. package/dist/start-VoFsPSes.mjs +2 -0
  58. package/dist/{state-NcJzMEBF.mjs → state-WGdIbi_d.mjs} +2 -2
  59. package/dist/{stats-DG9e66Rz.mjs → stats-BAgJaLA4.mjs} +1 -1
  60. package/dist/{stats-B63-JVAY.mjs → stats-DBYJ5bxL.mjs} +4 -4
  61. package/dist/{status-S6ehL4Ui.mjs → status-DLCD-o3s.mjs} +63 -231
  62. package/dist/status-Dr4fRkym.mjs +216 -0
  63. package/dist/{guide-status-BAMVU0ZA.mjs → stim-installations-CINXy_su.mjs} +3 -124
  64. package/dist/{stop-BO28HGqF.mjs → stop-Dj4pfmyv.mjs} +22 -18
  65. package/dist/{stop-T1R-cZRz.mjs → stop-ZvOeTmKb.mjs} +2 -2
  66. package/dist/supervisor-run.d.mts +11 -2
  67. package/dist/supervisor-run.mjs +185 -17
  68. package/dist/{workspace-process-lock-DWaiRD0e.mjs → workspace-process-lock-BXA96myD.mjs} +2 -2
  69. package/dist/workspaces-BTUyFfkv.mjs +1003 -0
  70. package/dist/{worktree-DKhSF0UX.mjs → worktree-4aZg2NnI.mjs} +1 -1
  71. package/dist/{worktree-LGB-UwgG.mjs → worktree-BMS7GeqB.mjs} +40 -122
  72. package/dist/{worktree-R_cUlE7s.mjs → worktree-BlizKDpZ.mjs} +143 -18
  73. package/dist/{xcode-BZqyxh3e.mjs → xcode-B2tBkf0m.mjs} +9 -158
  74. package/package.json +6 -5
  75. package/dist/build-progress-CVqkfPC6.mjs +0 -188
  76. package/dist/device-lease-Crs-HkJY.mjs +0 -366
  77. package/dist/logs-query-Cqma3WIu.mjs +0 -298
  78. package/dist/ownership-DCQPuWEc.mjs +0 -489
  79. package/dist/public-http-probe-CyMDrQ3c.mjs +0 -61
  80. package/dist/server-expo-CeILEu86.mjs +0 -2
  81. package/dist/sim-pool-KaIsp2Ft.mjs +0 -723
  82. package/dist/spawn-entry-nCE6m770.mjs +0 -14
  83. package/dist/workspace-state-DF2gCFhx.mjs +0 -169
@@ -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 };
@@ -0,0 +1,245 @@
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";
12
+ //#region src/engine/build-slots.ts
13
+ const SLOT_PREFIX = "slot-";
14
+ const SLOT_LABEL = "build slot";
15
+ const SLOT_POLL_MS = 1e3;
16
+ const SLOT_PROGRESS_MS = 3e4;
17
+ const SLOT_CEILING_MS = 54e5;
18
+ function sleepAsync(ms) {
19
+ return new Promise((resolve) => setTimeout(resolve, ms));
20
+ }
21
+ function buildSlotsDir() {
22
+ return join$1(getConfigDir(), "build-slots");
23
+ }
24
+ function buildSlotPath(index) {
25
+ return join$1(buildSlotsDir(), `${SLOT_PREFIX}${index}`);
26
+ }
27
+ function toRecord(holder) {
28
+ const details = holder.details;
29
+ return {
30
+ pid: holder.owner.pid,
31
+ index: typeof details.index === "number" ? details.index : null,
32
+ projectRoot: typeof details.projectRoot === "string" ? details.projectRoot : null,
33
+ startedAt: holder.startedAt || null,
34
+ logFile: typeof details.logFile === "string" ? details.logFile : null
35
+ };
36
+ }
37
+ function tryAcquireBuildSlot({ max, root = null, logFile = null }) {
38
+ if (!max || max <= 0) return {
39
+ acquired: true,
40
+ unlimited: true
41
+ };
42
+ let refusal = null;
43
+ let busy = false;
44
+ for (let index = 0; index < max; index++) {
45
+ const path = buildSlotPath(index);
46
+ let attempt;
47
+ try {
48
+ attempt = (0, ownership_claim_exports.tryAcquireClaim)({
49
+ root: path,
50
+ mode: "exclusive",
51
+ label: SLOT_LABEL,
52
+ details: {
53
+ index,
54
+ projectRoot: root,
55
+ logFile
56
+ }
57
+ });
58
+ } catch (err) {
59
+ if (!(0, ownership_claim_exports.isClaimRefusal)(err)) throw err;
60
+ refusal ??= err;
61
+ continue;
62
+ }
63
+ if (attempt.pending) (0, ownership_claim_exports.releaseClaim)(attempt.pending);
64
+ if (!attempt.acquired) {
65
+ busy = true;
66
+ continue;
67
+ }
68
+ declareSpawnsOn(attempt.acquired);
69
+ return {
70
+ acquired: true,
71
+ path,
72
+ index,
73
+ slot: {
74
+ pid: attempt.acquired.owner.pid,
75
+ index,
76
+ projectRoot: root,
77
+ startedAt: attempt.acquired.startedAt,
78
+ logFile
79
+ },
80
+ claim: attempt.acquired
81
+ };
82
+ }
83
+ if (refusal && !busy) throw refusal;
84
+ return null;
85
+ }
86
+ function slotWaitingLine({ max, elapsedMs }) {
87
+ return `${"build".padEnd(11)} waiting for a build slot (all ${max} in use, ${formatElapsed(elapsedMs)} elapsed) -- stim guide lifecycle concurrency`;
88
+ }
89
+ async function acquireBuildSlot({ max, root = null, logFile = null, now = Date.now, out = () => {}, sleep = sleepAsync, intervalMs = SLOT_POLL_MS, progressMs = SLOT_PROGRESS_MS, ceilingMs = SLOT_CEILING_MS }) {
90
+ if (!max || max <= 0) return {
91
+ acquired: true,
92
+ unlimited: true
93
+ };
94
+ const started = now();
95
+ let lastProgress = started;
96
+ for (;;) {
97
+ const got = tryAcquireBuildSlot({
98
+ max,
99
+ root,
100
+ logFile
101
+ });
102
+ if (got) return got;
103
+ const elapsed = now() - started;
104
+ if (elapsed >= ceilingMs) {
105
+ const err = /* @__PURE__ */ new Error(`Waited ${formatElapsed(elapsed)} for one of ${max} build slots, and every slot is held by a process that is still running, or by a holder Stim cannot identify. Slots live under ` + buildSlotsDir() + "; remove a slot directory whose builder is not really building, or raise concurrency.maxBuilds.");
106
+ err.code = "STIM_BUILD_SLOT_TIMEOUT";
107
+ throw err;
108
+ }
109
+ if (now() - lastProgress >= progressMs) {
110
+ lastProgress = now();
111
+ out(slotWaitingLine({
112
+ max,
113
+ elapsedMs: elapsed
114
+ }));
115
+ }
116
+ await sleep(intervalMs);
117
+ }
118
+ }
119
+ function releaseBuildSlot(handle) {
120
+ if (!handle || handle.unlimited) return false;
121
+ stopDeclaringSpawnsOn(handle.claim);
122
+ return (0, ownership_claim_exports.releaseClaim)(handle.claim);
123
+ }
124
+ function listBuildSlots() {
125
+ const dir = buildSlotsDir();
126
+ let names;
127
+ try {
128
+ names = readdirSync(dir);
129
+ } catch {
130
+ return [];
131
+ }
132
+ const slots = [];
133
+ for (const name of names) {
134
+ if (!name.startsWith(SLOT_PREFIX)) continue;
135
+ const path = join$1(dir, name);
136
+ try {
137
+ if (!statSync(path).isDirectory()) continue;
138
+ } catch {
139
+ continue;
140
+ }
141
+ const survey = (0, ownership_claim_exports.readClaimSet)(path);
142
+ const holder = survey.live[0] ?? survey.dead[0];
143
+ const info = holder ? toRecord(holder) : null;
144
+ const index = Number(name.slice(5));
145
+ slots.push({
146
+ path,
147
+ name,
148
+ index: Number.isFinite(index) ? index : null,
149
+ pid: info?.pid ?? null,
150
+ projectRoot: info?.projectRoot ?? null,
151
+ startedAt: info?.startedAt ?? null,
152
+ logFile: info?.logFile ?? null,
153
+ alive: survey.live.length > 0,
154
+ unresolved: survey.unresolved.length > 0
155
+ });
156
+ }
157
+ return slots;
158
+ }
159
+ //#endregion
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,8 +1,9 @@
1
- import { g as loadConfig } from "./config-Cp9lM3wX.mjs";
2
- import { t as getExecutor } from "./exec-jIAlgilk.mjs";
1
+ import { g as loadConfig$1 } from "./config-BEq0Qw_a.mjs";
2
+ import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
3
  import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
4
- import { I as IOS_SIMULATOR_APPS } from "./settings-D030k0t4.mjs";
5
- import { existsSync, mkdtempSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
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";
6
7
  import { dirname, join, resolve } from "node:path";
7
8
  import { tmpdir } from "node:os";
8
9
  //#region src/host-memory.ts
@@ -37,7 +38,7 @@ function parseIosSimulatorApp(value, source = "iosSimulatorApp in machine config
37
38
  throw error;
38
39
  }
39
40
  function configuredIosSimulatorViewer(override) {
40
- const app = override ?? parseIosSimulatorApp(loadConfig()?.iosSimulatorApp);
41
+ const app = override ?? parseIosSimulatorApp(loadConfig$1()?.iosSimulatorApp);
41
42
  const exec = getExecutor();
42
43
  if (app === "stim-desktop") return { open: (udid) => {
43
44
  exec.runFileQuiet("open", [
@@ -369,11 +370,13 @@ function createOwnedIosSim(label, { deviceType, runtime, suffix = "" } = {}, cho
369
370
  model: choice.deviceType,
370
371
  runtime: choice.runtime
371
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);
372
378
  return {
373
- udid: getExecutor().run(`xcrun simctl create "${name}" "${choice.deviceTypeId}" "${choice.runtimeId}"`, {
374
- timeoutMs: SIMCTL_CREATE_TIMEOUT_MS,
375
- killSignal: "SIGKILL"
376
- }).trim(),
379
+ udid,
377
380
  name,
378
381
  deviceType: choice.deviceType,
379
382
  runtime: choice.runtime
@@ -387,6 +390,13 @@ function renameIosSim(udid, name) {
387
390
  name
388
391
  ], SIMCTL_OPTIONS);
389
392
  }
393
+ function eraseIosSim(udid) {
394
+ getExecutor().runFile("xcrun", [
395
+ "simctl",
396
+ "erase",
397
+ udid
398
+ ], SIMCTL_OPTIONS);
399
+ }
390
400
  function resetIosPrivacy(udid) {
391
401
  getExecutor().runFile("xcrun", [
392
402
  "simctl",
@@ -449,63 +459,18 @@ function listUserApps(udid) {
449
459
  });
450
460
  }
451
461
  }
452
- const CONTAINER_METADATA = ".com.apple.mobile_container_manager.metadata.plist";
453
- const CLEARED_CONTAINER_DIRS = [
454
- "Documents",
455
- "Library",
456
- "tmp",
457
- "SystemData"
458
- ];
459
- const PLUTIL_TIMEOUT_MS = 1e4;
460
- function findAppDataContainer(dataPath, bundleId) {
461
- const root = join(dataPath, "Containers", "Data", "Application");
462
- let entries;
463
- try {
464
- entries = readdirSync(root);
465
- } catch (error) {
466
- if (error?.code === "ENOENT") return null;
467
- throw error;
468
- }
469
- const exec = getExecutor();
470
- for (const entry of entries) {
471
- const dir = join(root, entry);
472
- if (exec.runFile("plutil", [
473
- "-extract",
474
- "MCMMetadataIdentifier",
475
- "raw",
476
- join(dir, CONTAINER_METADATA)
477
- ], { timeoutMs: PLUTIL_TIMEOUT_MS }).trim() === bundleId) return dir;
478
- }
479
- return null;
480
- }
481
- function clearAppDataContainer(container) {
482
- for (const name of CLEARED_CONTAINER_DIRS) {
483
- const dir = join(container, name);
484
- let children;
485
- try {
486
- if (!statSync(dir).isDirectory()) throw new Error(`Expected app data path ${dir} to be a directory.`);
487
- children = readdirSync(dir);
488
- } catch (error) {
489
- if (error?.code === "ENOENT") continue;
490
- throw error;
491
- }
492
- for (const child of children) rmSync(join(dir, child), {
493
- recursive: true,
494
- force: true
495
- });
496
- }
497
- }
498
462
  function resolveOwnedIosSim(udid) {
499
463
  const sim = listAllIosSims().find((s) => s.udid === udid);
500
464
  if (!sim) return { missing: true };
501
- if (!sim.name?.startsWith("stim-")) return { notOwned: sim.name };
465
+ if (!isStimOwnedSim(sim)) return { notOwned: sim.name };
502
466
  return { sim };
503
467
  }
504
468
  function deleteIosSim(udid) {
505
469
  const result = resolveOwnedIosSim(udid);
506
470
  if (result.missing) return;
507
- if (result.notOwned) throw new Error(`Refusing to delete simulator "${result.notOwned}" (${udid}): not a Stim-owned sim (name must start with "stim-").`);
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.`);
508
472
  getExecutor().run(`xcrun simctl delete ${udid}`, SIMCTL_OPTIONS);
473
+ forgetCreatedDevice("ios", udid);
509
474
  }
510
475
  const PARKED_DELETE_TIMEOUT_MS = 3e4;
511
476
  function deleteParkedIosSim(udid) {
@@ -514,12 +479,13 @@ function deleteParkedIosSim(udid) {
514
479
  timeoutMs: PARKED_DELETE_TIMEOUT_MS
515
480
  }).find((entry) => entry.udid === udid);
516
481
  if (!sim) return;
517
- 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.`);
518
483
  getExecutor().runFile("xcrun", [
519
484
  "simctl",
520
485
  "delete",
521
486
  udid
522
487
  ], { timeoutMs: PARKED_DELETE_TIMEOUT_MS });
488
+ forgetCreatedDevice("ios", udid);
523
489
  }
524
490
  function listIosRuntimes() {
525
491
  const out = getExecutor().runFile("xcrun", [
@@ -542,4 +508,4 @@ function listIosRuntimes() {
542
508
  }));
543
509
  }
544
510
  //#endregion
545
- export { readHostMemoryPressure as A, resolveOwnedIosSim as C, configuredIosSimulatorViewer as D, uninstallIosApp as E, parseIosSimulatorApp as O, resolveIosCreation as S, shutdownIosSim as T, parseRuntimeVersion as _, deleteIosSim as a, resetIosKeychain as b, iosRuntimeMatches as c, listAllIosSimsAsync as d, listIosDeviceTypes as f, parkedSimName as g, ownedSimName as h, createOwnedIosSim as i, hostMemoryPressureAdvice as k, iosSimulatorFailureAdvice as l, listUserApps as m, bootIosSim as n, deleteParkedIosSim as o, listIosRuntimes as p, clearAppDataContainer as r, findAppDataContainer as s, IOS_BOOT_TIMEOUT_MS as t, listAllIosSims as u, parseSimctlList as v, sanitizeDeviceLabel as w, resetIosPrivacy as x, renameIosSim 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 };