stim 1.3.1 → 1.4.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 (63) hide show
  1. package/README.md +13 -0
  2. package/dist/{android-CT-PkXUh.mjs → android-BTR1KTMP.mjs} +197 -114
  3. package/dist/{android-hMj83VpN.mjs → android-BkM4qTaN.mjs} +65 -72
  4. package/dist/{android-cas-DXorPCi_.mjs → android-cas-BjLRZyks.mjs} +15 -12
  5. package/dist/{app-install-DIp9riNx.mjs → app-install--PAocWvq.mjs} +2 -2
  6. package/dist/{build-slots-C4AouyNz.mjs → build-slots-D6sDzOe-.mjs} +7 -8
  7. package/dist/{cache-manifest-CXR-Y8pH.mjs → cache-manifest-D02TpYjA.mjs} +1 -1
  8. package/dist/cache-manifest.mjs +1 -1
  9. package/dist/cli.mjs +14 -13
  10. package/dist/collector-run.mjs +4 -4
  11. package/dist/{command-output-8wHMEde0.mjs → command-output-BuIb_ZOI.mjs} +1 -4
  12. package/dist/{config-CcefMY2Q.mjs → config-6vdGngKa.mjs} +51 -51
  13. package/dist/dependency-state-BMirHEPD.mjs +76 -0
  14. package/dist/{deps-0Yt0Inly.mjs → deps-Ck4wuTwa.mjs} +1 -1
  15. package/dist/{dev-client-VfSZNLT4.mjs → dev-client-Cd0v7iZk.mjs} +12 -12
  16. package/dist/{device-Bbv45Z9Z.mjs → device-CKXLnRRb.mjs} +7 -7
  17. package/dist/{xcode-xnxyvJkD.mjs → device-CMDFzx7C.mjs} +208 -967
  18. package/dist/{device-lease-CJwK7OZr.mjs → device-lease-DDJr85OK.mjs} +3 -3
  19. package/dist/{device-pool-CcBNTvM7.mjs → device-pool-DJvbIBGW.mjs} +4 -4
  20. package/dist/{device-remote-DAA0Zm6Z.mjs → device-remote-CMfHhoc8.mjs} +7 -6
  21. package/dist/{doctor-B4EITgQB.mjs → doctor-B_mfURdt.mjs} +536 -114
  22. package/dist/{error-diagnostics-DwhNBFDU.mjs → error-diagnostics-_VmkqRZo.mjs} +7 -6
  23. package/dist/{gc-BRu62v4o.mjs → gc-0AyxmECv.mjs} +61 -27
  24. package/dist/{guide-D95E0u3i.mjs → guide-CGxdmQ0K.mjs} +126 -7
  25. package/dist/{ios-DAju3TVy.mjs → ios-CtmGLx3X.mjs} +2667 -2607
  26. package/dist/{ios-device-CWOM_S44.mjs → ios-device-DJOi9FR5.mjs} +1 -1
  27. package/dist/{logs-CAdwW6c2.mjs → logs-BF3qPA7w.mjs} +5 -5
  28. package/dist/metro-DFLmaVz8.mjs +99 -0
  29. package/dist/named-ports-BO7Zpjp0.mjs +137 -0
  30. package/dist/{ownership-8O073GIC.mjs → ownership-BvSpZVBk.mjs} +5 -5
  31. package/dist/{ownership-DXcol4oU.mjs → ownership-C08Yzrp7.mjs} +6 -6
  32. package/dist/ports-BiKqP92J.mjs +39 -0
  33. package/dist/ports-ClqG8GPt.mjs +90 -0
  34. package/dist/{project-fKmuIaMI.mjs → project-CBFCwnN5.mjs} +2 -2
  35. package/dist/{reclaim-CUmwucWG.mjs → reclaim-DpDVo-AP.mjs} +20 -13
  36. package/dist/{reload-CWpXoOgN.mjs → reload-ByTqg6ZI.mjs} +8 -8
  37. package/dist/rolldown-runtime-BsOwBuO_.mjs +30 -0
  38. package/dist/{server-bare-DEzPZLvf.mjs → server-bare-Buk-k8tI.mjs} +3 -6
  39. package/dist/{server-expo-DizT4OMk.mjs → server-expo-D4P7iTvf.mjs} +3 -3
  40. package/dist/server-expo-Dho5Raiw.mjs +2 -0
  41. package/dist/{settings-DEJf6_kJ.mjs → settings-DwfltIXQ.mjs} +2 -2
  42. package/dist/{slot-launch-CZklomj6.mjs → slot-launch-DeKen9e5.mjs} +4 -4
  43. package/dist/{start-B5G4NjiA.mjs → start-BpKUHcU_.mjs} +15 -13
  44. package/dist/state-CDbvmZdt.mjs +131 -0
  45. package/dist/{stats-BR-kwj0B.mjs → stats-C59QLKm2.mjs} +1 -1
  46. package/dist/{stats-CaaQr4yN.mjs → stats-k5qMjyxo.mjs} +4 -4
  47. package/dist/{status-CI9clRSb.mjs → status-Y2ij3dm6.mjs} +12 -11
  48. package/dist/{stop-CjlgDk0a.mjs → stop-_P2OQwmZ.mjs} +15 -14
  49. package/dist/{stop-CQhffpmX.mjs → stop-vKmmxhea.mjs} +2 -2
  50. package/dist/supervisor-run.d.mts +3 -27
  51. package/dist/supervisor-run.mjs +7 -6
  52. package/dist/{teardown--gESs9ck.mjs → teardown-KsPpvldq.mjs} +148 -109
  53. package/dist/{workspace-process-lock-IBj1Towr.mjs → workspace-process-lock-UEIrvZMJ.mjs} +4 -4
  54. package/dist/workspace-state-Do1e2mOH.mjs +146 -0
  55. package/dist/{worktree-DtQGO9-s.mjs → worktree-BvcAIMdJ.mjs} +24 -25
  56. package/dist/{worktree-Eq6TLH8N.mjs → worktree-DWNx4tUg.mjs} +32 -2
  57. package/dist/xcode-D9tVItl-.mjs +860 -0
  58. package/package.json +7 -7
  59. package/dist/doctor-BueDfLYG.mjs +0 -530
  60. package/dist/metro-CORqXyTL.mjs +0 -184
  61. package/dist/ownership-claim-BtPtJVmq.mjs +0 -584
  62. package/dist/server-expo-44em5Pmg.mjs +0 -2
  63. package/dist/state-B3XA2wp2.mjs +0 -268
@@ -1,5 +1,5 @@
1
1
  import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { r as INSTALL_ERROR } from "./app-install-DIp9riNx.mjs";
2
+ import { r as INSTALL_ERROR } from "./app-install--PAocWvq.mjs";
3
3
  import { mkdtempSync, readFileSync, rmSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
@@ -1,9 +1,9 @@
1
- import { V as validateDeviceSlot, g as loadConfig, h as isPathPrefix } from "./config-CcefMY2Q.mjs";
1
+ import { g as loadConfig, h as isPathPrefix, z as validateDeviceSlot } from "./config-6vdGngKa.mjs";
2
2
  import { n as SOURCES, t as LEVELS } from "./ndjson-BZwI1NBv.mjs";
3
- import { D as workspaceLogsDir, p as readWorkspaceState } from "./state-B3XA2wp2.mjs";
4
- import { a as findProjectRoot } from "./project-fKmuIaMI.mjs";
5
- import { _ as launchErrorPreview } from "./command-output-8wHMEde0.mjs";
6
- import { i as captureWorkspaceCrashes, t as errorDiagnostics } from "./error-diagnostics-DwhNBFDU.mjs";
3
+ import { _ as workspaceLogsDir, r as readWorkspaceState } from "./workspace-state-Do1e2mOH.mjs";
4
+ import { a as findProjectRoot } from "./project-CBFCwnN5.mjs";
5
+ import { _ as launchErrorPreview } from "./command-output-BuIb_ZOI.mjs";
6
+ import { i as captureWorkspaceCrashes, t as errorDiagnostics } from "./error-diagnostics-_VmkqRZo.mjs";
7
7
  import { a as followLogs, c as queryLogs, i as fileSizes, n as buildCriteria, o as logFiles, r as compileGrep, s as parseSince } from "./logs-query-Cdp5aaCH.mjs";
8
8
  import { realpathSync } from "node:fs";
9
9
  import { relative, sep } from "node:path";
@@ -0,0 +1,99 @@
1
+ import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
+ import { w as process_identity_exports } from "./config-6vdGngKa.mjs";
3
+ import { r as readWorkspaceState } from "./workspace-state-Do1e2mOH.mjs";
4
+ import { t as isMetroRunning } from "./ports-ClqG8GPt.mjs";
5
+ import { readlinkSync, realpathSync } from "fs";
6
+ import { sep } from "path";
7
+ //#region src/metro.ts
8
+ /**
9
+ * True when some process with this pid exists right now. It cannot tell a recycled pid from the
10
+ * process that first held it, so it is only valid for a pid this run spawned and still holds a
11
+ * handle on. For a pid read from a record on disk, decide with `inspectProcessIdentity` on that
12
+ * record's process token, or take an ownership claim (`ownership-claim.ts`): this answer would keep
13
+ * a dead holder's record looking live until some unrelated process exits.
14
+ */
15
+ function pidExists(pid) {
16
+ if (!Number.isSafeInteger(pid) || pid <= 0) return false;
17
+ try {
18
+ process.kill(pid, 0);
19
+ return true;
20
+ } catch (error) {
21
+ return error.code !== "ESRCH";
22
+ }
23
+ }
24
+ function parseLsofPids(out) {
25
+ if (!out) return [];
26
+ return String(out).split("\n").map((s) => parseInt(s.trim(), 10)).filter((n) => Number.isFinite(n));
27
+ }
28
+ function parseLsofCwd(out) {
29
+ if (!out) return null;
30
+ const lines = String(out).split("\n");
31
+ const idx = lines.findIndex((l) => l === "fcwd");
32
+ if (idx === -1) return null;
33
+ const nLine = lines.slice(idx + 1).find((l) => l.startsWith("n"));
34
+ return nLine ? nLine.slice(1) : null;
35
+ }
36
+ function processCwd(pid) {
37
+ if (process.platform === "linux") try {
38
+ return readlinkSync(`/proc/${pid}/cwd`);
39
+ } catch {}
40
+ return parseLsofCwd(getExecutor().runQuiet(`lsof -a -p ${pid} -d cwd -Fn`));
41
+ }
42
+ function canonicalPath(path) {
43
+ try {
44
+ return realpathSync(path);
45
+ } catch {
46
+ return path;
47
+ }
48
+ }
49
+ function isInsideProject(cwd, projectPath) {
50
+ if (!cwd || !projectPath) return false;
51
+ const a = canonicalPath(cwd);
52
+ const b = canonicalPath(projectPath);
53
+ return a === b || a.startsWith(b.endsWith(sep) ? b : b + sep);
54
+ }
55
+ const NOT_OURS_UNRESPONSIVE = "unresponsive";
56
+ const NOT_OURS_UNREADABLE_CWD = "unreadable-cwd";
57
+ const NOT_OURS_FOREIGN_CWD = "foreign-cwd";
58
+ async function resolveProjectMetro(port, projectPath, { probe = isMetroRunning } = {}) {
59
+ const pid = parseLsofPids(getExecutor().runQuiet(`lsof -nP -iTCP:${port} -sTCP:LISTEN -t`))[0];
60
+ if (pid === void 0) return { missing: true };
61
+ if (!await probe(port)) return {
62
+ notOurs: `pid ${pid} on port ${port} does not answer Metro's /status`,
63
+ kind: NOT_OURS_UNRESPONSIVE,
64
+ pid
65
+ };
66
+ const cwd = processCwd(pid);
67
+ if (!cwd) return {
68
+ notOurs: `pid ${pid} on port ${port}: working directory could not be read`,
69
+ kind: NOT_OURS_UNREADABLE_CWD,
70
+ pid
71
+ };
72
+ if (!isInsideProject(cwd, projectPath)) return {
73
+ notOurs: `pid ${pid} on port ${port} runs from ${cwd}, outside ${projectPath}`,
74
+ kind: NOT_OURS_FOREIGN_CWD,
75
+ pid
76
+ };
77
+ const supervisor = readWorkspaceState(canonicalPath(projectPath))?.supervisor;
78
+ const owned = supervisor?.port === port && (0, process_identity_exports.inspectProcessIdentity)(supervisor) === "same";
79
+ return { metro: {
80
+ pid,
81
+ leader: owned ? supervisor.pid : pid,
82
+ cwd,
83
+ ...owned ? { processToken: supervisor.processToken } : {}
84
+ } };
85
+ }
86
+ function killMetroTree(leader, processToken) {
87
+ if (!leader || leader === process.pid || (0, process_identity_exports.inspectProcessIdentity)({
88
+ pid: leader,
89
+ processToken
90
+ }) !== "same") return false;
91
+ try {
92
+ process.kill(-leader, "SIGTERM");
93
+ return true;
94
+ } catch {
95
+ return false;
96
+ }
97
+ }
98
+ //#endregion
99
+ export { resolveProjectMetro as i, killMetroTree as n, pidExists as r, NOT_OURS_FOREIGN_CWD as t };
@@ -0,0 +1,137 @@
1
+ import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
+ import { C as withConfigLock, f as getProject, g as loadConfig, s as ensureConfig, u as getConfigDir, w as process_identity_exports, y as saveConfig } from "./config-6vdGngKa.mjs";
3
+ import { n as isPortFree } from "./ports-ClqG8GPt.mjs";
4
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-UEIrvZMJ.mjs";
5
+ import { realpathSync } from "node:fs";
6
+ //#region src/named-ports.ts
7
+ const FIRST_PORT = 8900;
8
+ const LAST_PORT = 8999;
9
+ function validatePortLabel(label) {
10
+ if (label === "metro") throw new Error("metro is managed by stim start and stim stop; choose another label.");
11
+ if (!/^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/.test(label)) throw new Error("A port label must start with a letter and contain at most 64 letters, digits, underscores or hyphens.");
12
+ }
13
+ function portListeners(port) {
14
+ let out;
15
+ try {
16
+ out = getExecutor().runFile("lsof", [
17
+ "-nP",
18
+ `-iTCP:${port}`,
19
+ "-sTCP:LISTEN",
20
+ "-t"
21
+ ], { timeoutMs: 5e3 });
22
+ } catch (error) {
23
+ const result = error;
24
+ if (result.status === 1 && !String(result.stdout ?? "").trim() && !String(result.stderr ?? "").trim()) return [];
25
+ throw new Error(`Could not inspect TCP port ${port} with lsof: ${error.message}`, { cause: error });
26
+ }
27
+ const pids = out.trim() ? out.trim().split(/\s+/).map(Number) : [];
28
+ if (pids.some((pid) => !Number.isSafeInteger(pid) || pid <= 0)) throw new Error(`lsof returned an invalid listener for TCP port ${port}.`);
29
+ return [...new Set(pids)];
30
+ }
31
+ function withPortsLock(fn) {
32
+ return withWorkspaceProcessLock(getConfigDir(), "ports", fn, { external: true });
33
+ }
34
+ async function getNamedPort(projectPath, label, { isFree = async (port) => await isPortFree(port) && portListeners(port).length === 0, log = console.error } = {}) {
35
+ validatePortLabel(label);
36
+ const root = realpathSync(projectPath);
37
+ return withPortsLock(async () => {
38
+ const existing = getProject(root)?.ports;
39
+ if (existing && Object.hasOwn(existing, label)) return existing[label];
40
+ for (let port = FIRST_PORT; port <= LAST_PORT; port++) {
41
+ if (Object.values(loadConfig()?.projects ?? {}).some((project) => project.metroPort === port || Object.values(project.ports ?? {}).includes(port))) continue;
42
+ if (!await isFree(port)) {
43
+ log(`Port ${port} already in use, trying next...`);
44
+ continue;
45
+ }
46
+ if (withConfigLock(() => {
47
+ const cfg = ensureConfig();
48
+ if (Object.values(cfg.projects).some((project) => project.metroPort === port || Object.values(project.ports ?? {}).includes(port))) return false;
49
+ const project = cfg.projects[root] ??= {
50
+ metroPort: null,
51
+ platforms: {}
52
+ };
53
+ project.ports = {
54
+ ...project.ports,
55
+ [label]: port
56
+ };
57
+ saveConfig(cfg);
58
+ return true;
59
+ })) return port;
60
+ }
61
+ throw new Error(`No free named port between ${FIRST_PORT} and ${LAST_PORT}. Use stim ports stop or stim ports release in a workspace that no longer needs its ports.`);
62
+ });
63
+ }
64
+ async function stopListeners(port, label, dryRun, log) {
65
+ for (const pid of portListeners(port)) {
66
+ if (pid === process.pid) throw new Error(`Refusing to stop Stim itself on ${label} (${port}).`);
67
+ const identity = (0, process_identity_exports.captureProcessIdentity)(pid);
68
+ if (!identity.ok) {
69
+ if (!portListeners(port).includes(pid)) continue;
70
+ throw new Error(`Cannot identify pid ${pid} on ${label} (${port}): ${identity.reason}`);
71
+ }
72
+ const record = {
73
+ pid,
74
+ processToken: identity.token
75
+ };
76
+ const command = getExecutor().runFile("ps", [
77
+ "-p",
78
+ String(pid),
79
+ "-o",
80
+ "args="
81
+ ], { timeoutMs: 5e3 }).trim();
82
+ if (dryRun) {
83
+ log(`would stop ${label} (${port}): pid ${pid} ${command}`);
84
+ continue;
85
+ }
86
+ if (!portListeners(port).includes(pid)) continue;
87
+ const status = (0, process_identity_exports.inspectProcessIdentity)(record);
88
+ if (status === "gone" || status === "different") continue;
89
+ if (status !== "same") throw new Error(`Cannot verify pid ${pid} on ${label} (${port}); allocation kept.`);
90
+ try {
91
+ process.kill(pid, "SIGTERM");
92
+ if (!await (0, process_identity_exports.waitForProcessExit)(record, 2e3)) {
93
+ if ((0, process_identity_exports.inspectProcessIdentity)(record) !== "same") throw new Error(`Cannot verify pid ${pid} before SIGKILL.`);
94
+ process.kill(pid, "SIGKILL");
95
+ if (!await (0, process_identity_exports.waitForProcessExit)(record, 2e3)) throw new Error(`Pid ${pid} did not exit.`);
96
+ }
97
+ } catch (error) {
98
+ if (error.code !== "ESRCH") throw error;
99
+ }
100
+ log(`stopped ${label} (${port}): pid ${pid} ${command}`);
101
+ }
102
+ if (!dryRun && portListeners(port).length) throw new Error(`Port ${port} still has a listener; allocation kept.`);
103
+ }
104
+ async function clearNamedPorts(projectPath, { label, stop = false, dryRun = false, log = console.error } = {}) {
105
+ if (label !== void 0) validatePortLabel(label);
106
+ await withPortsLock(async () => {
107
+ const ports = getProject(projectPath)?.ports ?? {};
108
+ const failures = [];
109
+ for (const [name, port] of Object.entries(ports)) {
110
+ if (label !== void 0 && name !== label) continue;
111
+ try {
112
+ if (!Number.isInteger(port) || port < FIRST_PORT || port > LAST_PORT) throw new Error(`Invalid named port ${name} (${port}); repair its config record before cleanup.`);
113
+ if (stop) {
114
+ if (Object.values(loadConfig()?.projects ?? {}).some((project) => project.metroPort === port)) throw new Error(`Port ${port} is reserved for managed Metro; allocation kept.`);
115
+ await stopListeners(port, name, dryRun, log);
116
+ }
117
+ if (dryRun) {
118
+ log(`would release ${name} (${port})`);
119
+ continue;
120
+ }
121
+ withConfigLock(() => {
122
+ const cfg = loadConfig();
123
+ const current = cfg?.projects?.[projectPath]?.ports;
124
+ if (!cfg || current?.[name] !== port) return;
125
+ delete current[name];
126
+ saveConfig(cfg);
127
+ });
128
+ log(`released ${name} (${port})`);
129
+ } catch (error) {
130
+ failures.push(`${name} (${port}): ${error.message}`);
131
+ }
132
+ }
133
+ if (failures.length) throw new Error(failures.join("\n"));
134
+ });
135
+ }
136
+ //#endregion
137
+ export { getNamedPort as n, clearNamedPorts as t };
@@ -1,6 +1,6 @@
1
- import { D as sameProcessRecord, E as inspectProcessIdentity } from "./config-CcefMY2Q.mjs";
2
- import { _ as writeWorkspaceState, m as withWorkspaceStateLock, p as readWorkspaceState } from "./state-B3XA2wp2.mjs";
3
- import { r as pidExists } from "./metro-CORqXyTL.mjs";
1
+ import { w as process_identity_exports } from "./config-6vdGngKa.mjs";
2
+ import { a as withWorkspaceStateLock, o as writeWorkspaceState, r as readWorkspaceState } from "./workspace-state-Do1e2mOH.mjs";
3
+ import { r as pidExists } from "./metro-DFLmaVz8.mjs";
4
4
  import { realpathSync } from "node:fs";
5
5
  //#region src/collector/state.ts
6
6
  function collectorRoot(root) {
@@ -42,12 +42,12 @@ function collectorProcessTitle(platform, root) {
42
42
  }
43
43
  function verifyCollectorOwnership({ pid, platform, root, isAlive = pidExists, expected }) {
44
44
  const record = readCollectors(root)[platform];
45
- if (expected !== void 0 && !sameProcessRecord(record, expected)) return {
45
+ if (expected !== void 0 && !(0, process_identity_exports.sameProcessRecord)(record, expected)) return {
46
46
  status: "unverified",
47
47
  reason: `the ${platform} collector registration changed during cleanup`
48
48
  };
49
49
  if (record?.pid === pid) {
50
- const identity = inspectProcessIdentity(record);
50
+ const identity = (0, process_identity_exports.inspectProcessIdentity)(record);
51
51
  if (identity === "same") return { status: "ours" };
52
52
  if (identity === "gone" || identity === "different") return { status: "gone" };
53
53
  }
@@ -1,8 +1,8 @@
1
1
  import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { E as inspectProcessIdentity, T as captureProcessToken, u as getConfigDir } from "./config-CcefMY2Q.mjs";
2
+ import { u as getConfigDir, w as process_identity_exports } from "./config-6vdGngKa.mjs";
3
3
  import { t as createLineReader } from "./process-output-C7IlIzd7.mjs";
4
- import { r as pidExists } from "./metro-CORqXyTL.mjs";
5
- import { t as withWorkspaceProcessLock } from "./workspace-process-lock-IBj1Towr.mjs";
4
+ import { r as pidExists } from "./metro-DFLmaVz8.mjs";
5
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-UEIrvZMJ.mjs";
6
6
  import { closeSync, mkdirSync, openSync, readFileSync, unlinkSync } from "node:fs";
7
7
  import { createHash, randomUUID } from "node:crypto";
8
8
  import { basename, join, resolve, sep } from "node:path";
@@ -198,7 +198,7 @@ async function waitUntilReachable({ url, now, sleep, probe, timeoutMs }) {
198
198
  }
199
199
  const CLEANUP_TIMEOUT_MS = 1e3;
200
200
  const CLEANUP_POLL_MS = 25;
201
- async function startTunnel({ provider, port, spawnFn = null, urlTimeoutMs = URL_TIMEOUT_MS, reachableTimeoutMs = REACHABLE_TIMEOUT_MS, probeReachable = defaultProbeReachable, now = Date.now, sleep = defaultSleep, ngrokUrl = null, requireReachable = true, cleanupTimeoutMs = CLEANUP_TIMEOUT_MS, isChildAlive = pidExists, readProcessToken = captureProcessToken, logFile = null }) {
201
+ async function startTunnel({ provider, port, spawnFn = null, urlTimeoutMs = URL_TIMEOUT_MS, reachableTimeoutMs = REACHABLE_TIMEOUT_MS, probeReachable = defaultProbeReachable, now = Date.now, sleep = defaultSleep, ngrokUrl = null, requireReachable = true, cleanupTimeoutMs = CLEANUP_TIMEOUT_MS, isChildAlive = pidExists, readProcessToken = process_identity_exports.captureProcessToken, logFile = null }) {
202
202
  const spawn = spawnFn || ((cmd, args, opts) => getExecutor().spawn(cmd, args, opts));
203
203
  const outputFile = logFile || (spawnFn ? null : createTunnelLogFile(provider));
204
204
  const { bin, args } = tunnelArgv(provider, port, ngrokUrl, outputFile);
@@ -377,7 +377,7 @@ function defaultKill(pid) {
377
377
  function isEsrch(err) {
378
378
  return err?.code === "ESRCH";
379
379
  }
380
- async function stopTunnel(record, { isAlive = pidExists, inspectIdentity = inspectProcessIdentity, kill = defaultKill, now = Date.now, sleep = defaultSleep, timeoutMs = STOP_TIMEOUT_MS } = {}) {
380
+ async function stopTunnel(record, { isAlive = pidExists, inspectIdentity = process_identity_exports.inspectProcessIdentity, kill = defaultKill, now = Date.now, sleep = defaultSleep, timeoutMs = STOP_TIMEOUT_MS } = {}) {
381
381
  const missing = () => {
382
382
  removeRecordedTunnelLogFile(record?.logFile);
383
383
  return { status: "missing" };
@@ -418,7 +418,7 @@ async function stopTunnel(record, { isAlive = pidExists, inspectIdentity = inspe
418
418
  }
419
419
  //#endregion
420
420
  //#region src/supervisor/ownership.ts
421
- function resolveSupervisorTarget({ state, record, reservedPort, isAlive = pidExists, inspectIdentity = inspectProcessIdentity } = {}) {
421
+ function resolveSupervisorTarget({ state, record, reservedPort, isAlive = pidExists, inspectIdentity = process_identity_exports.inspectProcessIdentity } = {}) {
422
422
  const statePid = numberOrNull(state?.pid);
423
423
  const recordPid = numberOrNull(record?.pid);
424
424
  const pid = statePid ?? recordPid;
@@ -0,0 +1,39 @@
1
+ import { f as getProject } from "./config-6vdGngKa.mjs";
2
+ import { a as findProjectRoot } from "./project-CBFCwnN5.mjs";
3
+ import { n as getNamedPort, t as clearNamedPorts } from "./named-ports-BO7Zpjp0.mjs";
4
+ //#region src/commands/ports.ts
5
+ async function inProject(action) {
6
+ try {
7
+ const root = findProjectRoot(process.cwd());
8
+ if (!root) throw new Error("No package.json found. Run stim ports from the workspace that owns the server.");
9
+ await action(root);
10
+ } catch (error) {
11
+ console.error(error.message);
12
+ process.exitCode = 1;
13
+ }
14
+ }
15
+ function portsCommand(program) {
16
+ const ports = program.command("ports").description("Reserve workspace ports for servers Stim does not manage");
17
+ ports.action(() => inProject(async (root) => {
18
+ const project = getProject(root);
19
+ const rows = Object.entries(project?.ports ?? {});
20
+ if (typeof project?.metroPort === "number") rows.push(["metro (managed)", project.metroPort]);
21
+ if (!rows.length) {
22
+ console.log("No ports allocated. Use stim ports get <label>.");
23
+ return;
24
+ }
25
+ const width = Math.max(...rows.map(([name]) => name.length));
26
+ for (const [name, port] of rows) console.log(`${name.padEnd(width)} ${port}`);
27
+ }));
28
+ ports.command("get <label>").description("Allocate once and print only the port number").action((label) => inProject(async (root) => {
29
+ console.log(await getNamedPort(root, label));
30
+ }));
31
+ ports.command("stop [label]").description("Stop TCP listeners and release named ports; excludes Metro").option("--dry-run", "show the listeners and allocations that would be stopped and released").action((label, opts) => inProject((root) => clearNamedPorts(root, {
32
+ label,
33
+ stop: true,
34
+ dryRun: opts.dryRun
35
+ })));
36
+ ports.command("release [label]").description("Release named ports without stopping their listeners; excludes Metro").action((label) => inProject((root) => clearNamedPorts(root, { label })));
37
+ }
38
+ //#endregion
39
+ export { portsCommand as default };
@@ -0,0 +1,90 @@
1
+ import { A as isOnMountedVolume, g as loadConfig, j as listMountedVolumes, n as allMetroPorts, r as claimMetroPort, v as removeProject } from "./config-6vdGngKa.mjs";
2
+ import { existsSync } from "fs";
3
+ import { request } from "http";
4
+ import { connect } from "net";
5
+ //#region src/ports.ts
6
+ function isMetroRunning(port) {
7
+ return new Promise((resolve) => {
8
+ const req = request({
9
+ hostname: "localhost",
10
+ port,
11
+ path: "/status",
12
+ timeout: 2e3
13
+ }, (res) => {
14
+ let data = "";
15
+ res.on("data", (chunk) => {
16
+ data += chunk;
17
+ });
18
+ res.on("end", () => resolve(data.includes("packager-status:running")));
19
+ });
20
+ req.on("error", () => resolve(false));
21
+ req.on("timeout", () => {
22
+ req.destroy();
23
+ resolve(false);
24
+ });
25
+ req.end();
26
+ });
27
+ }
28
+ function isPortFree(port, { timeoutMs = 400 } = {}) {
29
+ return new Promise((resolve) => {
30
+ const sock = connect({
31
+ port,
32
+ host: "127.0.0.1"
33
+ });
34
+ const done = (free) => {
35
+ sock.removeAllListeners();
36
+ sock.destroy();
37
+ resolve(free);
38
+ };
39
+ sock.setTimeout(timeoutMs);
40
+ sock.once("connect", () => done(false));
41
+ sock.once("error", () => done(true));
42
+ sock.once("timeout", () => done(false));
43
+ });
44
+ }
45
+ const FIRST_PORT = 8082;
46
+ async function computeNextPort(isFree = isPortFree) {
47
+ const taken = new Set(allMetroPorts());
48
+ for (let port = FIRST_PORT; port < 8282; port++) {
49
+ if (taken.has(port)) continue;
50
+ if (await isFree(port)) return port;
51
+ }
52
+ throw new Error(`Found no free Metro port between ${FIRST_PORT} and 8281. Free one up, or stop a stale bundler (\`stim status\`, \`stim stop <port>\`).`);
53
+ }
54
+ async function findReclaimablePort(excludeProjectPath, probe = isMetroRunning, { isMounted = isOnMountedVolume, mountedVolumes } = {}) {
55
+ const cfg = loadConfig();
56
+ if (!cfg?.projects) return null;
57
+ const mounted = mountedVolumes || listMountedVolumes();
58
+ const candidates = [];
59
+ for (const [path, proj] of Object.entries(cfg.projects)) {
60
+ if (path === excludeProjectPath) continue;
61
+ if (Object.keys(proj.ports ?? {}).length) continue;
62
+ if (existsSync(path)) continue;
63
+ if (!isMounted(path, mounted)) continue;
64
+ if (typeof proj.metroPort === "number") candidates.push({
65
+ port: proj.metroPort,
66
+ ownerPath: path
67
+ });
68
+ }
69
+ for (const c of candidates) if (!await probe(c.port)) return c;
70
+ return null;
71
+ }
72
+ async function allocatePort(projectPath, probe = isMetroRunning, isFree = isPortFree) {
73
+ const reclaim = await findReclaimablePort(projectPath, probe);
74
+ if (reclaim && await isFree(reclaim.port)) {
75
+ removeProject(reclaim.ownerPath);
76
+ return reclaim.port;
77
+ }
78
+ return computeNextPort(isFree);
79
+ }
80
+ const RESERVE_ATTEMPTS = 5;
81
+ async function reserveMetroPort(projectPath, probe = isMetroRunning, isFree = isPortFree) {
82
+ for (let attempt = 0; attempt < RESERVE_ATTEMPTS; attempt++) {
83
+ const port = await allocatePort(projectPath, probe, isFree);
84
+ const claimed = claimMetroPort(projectPath, port);
85
+ if (claimed !== null) return claimed;
86
+ }
87
+ throw new Error(`Could not reserve a Metro port after ${RESERVE_ATTEMPTS} attempts: another Stim run claimed each one first. Retry.`);
88
+ }
89
+ //#endregion
90
+ export { isPortFree as n, reserveMetroPort as r, isMetroRunning as t };
@@ -1,5 +1,5 @@
1
- import { c as findEnclosingWorktreeRoot, f as getProject } from "./config-CcefMY2Q.mjs";
2
- import { g as repoRoot } from "./worktree-Eq6TLH8N.mjs";
1
+ import { c as findEnclosingWorktreeRoot, f as getProject } from "./config-6vdGngKa.mjs";
2
+ import { _ as repoRoot } from "./worktree-DWNx4tUg.mjs";
3
3
  import { existsSync, readFileSync, readdirSync, realpathSync } from "fs";
4
4
  import { basename, dirname, join, resolve } from "path";
5
5
  import { createRequire } from "module";
@@ -1,11 +1,13 @@
1
- import { D as sameProcessRecord, O as waitForProcessExit, f as getProject, v as removeProject, z as projectDeviceSlots } from "./config-CcefMY2Q.mjs";
2
- import { T as workspaceDir, a as clearRemoteSession, c as readMetroTunnel, d as readRemoteSessionId, i as clearManagedMetroTunnel, p as readWorkspaceState } from "./state-B3XA2wp2.mjs";
3
- import { i as resolveProjectMetro, n as killMetroTree, r as pidExists } from "./metro-CORqXyTL.mjs";
4
- import { n as verifyCollectorOwnership } from "./ownership-8O073GIC.mjs";
5
- import { c as parkedMaxSetting, n as teardownOwnedIosSim, t as teardownOwnedAvd, x as resolveEasCliBin } from "./teardown--gESs9ck.mjs";
6
- import { l as releaseWorkspaceLeases } from "./device-lease-CJwK7OZr.mjs";
7
- import { i as endRecordedSession } from "./device-remote-DAA0Zm6Z.mjs";
8
- import { r as stopTunnel, t as resolveSupervisorTarget } from "./ownership-DXcol4oU.mjs";
1
+ import { E as ownership_claim_exports, L as projectDeviceSlots, T as acquireAvdClaim, f as getProject, v as removeProject, w as process_identity_exports } from "./config-6vdGngKa.mjs";
2
+ import { h as workspaceDir, r as readWorkspaceState } from "./workspace-state-Do1e2mOH.mjs";
3
+ import { a as clearRemoteSession, i as clearManagedMetroTunnel, s as readMetroTunnel, u as readRemoteSessionId } from "./state-CDbvmZdt.mjs";
4
+ import { i as resolveProjectMetro, n as killMetroTree, r as pidExists } from "./metro-DFLmaVz8.mjs";
5
+ import { n as verifyCollectorOwnership } from "./ownership-BvSpZVBk.mjs";
6
+ import { S as resolveEasCliBin, l as parkedMaxSetting, n as teardownOwnedIosSim, t as teardownOwnedAvd } from "./teardown-KsPpvldq.mjs";
7
+ import { l as releaseWorkspaceLeases } from "./device-lease-DDJr85OK.mjs";
8
+ import { i as endRecordedSession } from "./device-remote-CMfHhoc8.mjs";
9
+ import { t as clearNamedPorts } from "./named-ports-BO7Zpjp0.mjs";
10
+ import { r as stopTunnel, t as resolveSupervisorTarget } from "./ownership-C08Yzrp7.mjs";
9
11
  import { existsSync, rmSync } from "node:fs";
10
12
  //#region src/reclaim.ts
11
13
  async function reapCollectors(root, { verify = verifyCollectorOwnership, collectors }) {
@@ -35,7 +37,7 @@ async function reapCollectors(root, { verify = verifyCollectorOwnership, collect
35
37
  }
36
38
  try {
37
39
  process.kill(pid, "SIGTERM");
38
- if (await waitForProcessExit(rec, 5e3)) continue;
40
+ if (await (0, process_identity_exports.waitForProcessExit)(rec, 5e3)) continue;
39
41
  } catch (error) {
40
42
  if (error.code === "ESRCH") continue;
41
43
  }
@@ -138,7 +140,7 @@ async function reclaimMetroTunnel(root, { stopMetroTunnel = defaultStopMetroTunn
138
140
  };
139
141
  }
140
142
  function describeDereferenced(project) {
141
- const devices = [];
143
+ const devices = Object.entries(project?.ports ?? {}).map(([label, port]) => `port ${label} (${port})`);
142
144
  for (const { platforms } of projectDeviceSlots(project)) {
143
145
  const ios = platforms.ios;
144
146
  if (ios?.deviceUdid) devices.push(`ios sim ${ios.deviceUdid}`);
@@ -211,7 +213,10 @@ function reclaimOwnedDevices(project, projectPath, { park = false } = {}) {
211
213
  const bound = parkedMaxSetting("android");
212
214
  const r = teardownOwnedAvd(android.avdName, {
213
215
  del: true,
214
- owner: { projectPath },
216
+ owner: {
217
+ projectPath,
218
+ slot
219
+ },
215
220
  ...park && !bound.error && bound.max > 0 ? { park: {
216
221
  projectPath,
217
222
  slot,
@@ -251,7 +256,9 @@ function reclaimOwnedDevices(project, projectPath, { park = false } = {}) {
251
256
  };
252
257
  }
253
258
  async function reclaimProject(path, { deleteOwnedDevices = false, parkOwnedDevices = false, preserveProjectRecord = false, stopSession = defaultStopSession, stopMetroTunnel = defaultStopMetroTunnel, releaseLeases = releaseWorkspaceLeases, verifyCollector = verifyCollectorOwnership } = {}) {
259
+ await clearNamedPorts(path, { stop: true });
254
260
  const project = getProject(path);
261
+ for (const { platforms } of projectDeviceSlots(project)) if (platforms.android?.owned && platforms.android.avdName) (0, ownership_claim_exports.releaseClaim)(acquireAvdClaim(platforms.android.avdName));
255
262
  const dereferenced = describeDereferenced(project);
256
263
  const initialState = readWorkspaceState(path);
257
264
  let killedPid = null;
@@ -263,7 +270,7 @@ async function reclaimProject(path, { deleteOwnedDevices = false, parkOwnedDevic
263
270
  reservedPort: project?.metroPort
264
271
  });
265
272
  if (supervisor.status === "ours") {
266
- if (killMetroTree(supervisor.pid, supervisor.processToken) && await waitForProcessExit(supervisor, 1e4)) killedPid = supervisor.pid;
273
+ if (killMetroTree(supervisor.pid, supervisor.processToken) && await (0, process_identity_exports.waitForProcessExit)(supervisor, 1e4)) killedPid = supervisor.pid;
267
274
  else {
268
275
  skippedMetro = `could not confirm supervisor pid ${supervisor.pid} exited`;
269
276
  supervisorHeld = true;
@@ -281,7 +288,7 @@ async function reclaimProject(path, { deleteOwnedDevices = false, parkOwnedDevic
281
288
  function retainReplacementProcesses() {
282
289
  const currentState = readWorkspaceState(path);
283
290
  const currentProject = getProject(path);
284
- if (Boolean(currentState?.supervisor && !sameProcessRecord(currentState.supervisor, initialState?.supervisor) || currentProject?.supervisor && !sameProcessRecord(currentProject.supervisor, project?.supervisor) || Object.entries(currentState?.collectors ?? {}).some(([platform, record]) => !sameProcessRecord(record, initialState?.collectors?.[platform])))) {
291
+ if (Boolean(currentState?.supervisor && !(0, process_identity_exports.sameProcessRecord)(currentState.supervisor, initialState?.supervisor) || currentProject?.supervisor && !(0, process_identity_exports.sameProcessRecord)(currentProject.supervisor, project?.supervisor) || Object.entries(currentState?.collectors ?? {}).some(([platform, record]) => !(0, process_identity_exports.sameProcessRecord)(record, initialState?.collectors?.[platform])))) {
285
292
  supervisorHeld = true;
286
293
  skippedMetro = "A replacement process appeared during cleanup; its ownership records are retained";
287
294
  }
@@ -1,12 +1,12 @@
1
- import { L as deviceSlotPlatforms, R as parseDeviceSlotKey, f as getProject } from "./config-CcefMY2Q.mjs";
2
- import { f as readWorkspaceLaunches } from "./state-B3XA2wp2.mjs";
3
- import { a as findProjectRoot } from "./project-fKmuIaMI.mjs";
4
- import { i as resolveProjectMetro } from "./metro-CORqXyTL.mjs";
5
- import { l as phaseLine } from "./command-output-8wHMEde0.mjs";
6
- import { N as resolveOwnedAvdSerial } from "./android-hMj83VpN.mjs";
7
- import { u as androidAppProcess, y as iosAppProcess } from "./app-install-DIp9riNx.mjs";
1
+ import { F as deviceSlotPlatforms, I as parseDeviceSlotKey, f as getProject } from "./config-6vdGngKa.mjs";
2
+ import { a as findProjectRoot } from "./project-CBFCwnN5.mjs";
3
+ import { d as readWorkspaceLaunches } from "./state-CDbvmZdt.mjs";
4
+ import { i as resolveProjectMetro } from "./metro-DFLmaVz8.mjs";
5
+ import { l as phaseLine } from "./command-output-BuIb_ZOI.mjs";
6
+ import { N as resolveOwnedAvdSerial } from "./android-BkM4qTaN.mjs";
7
+ import { u as androidAppProcess, y as iosAppProcess } from "./app-install--PAocWvq.mjs";
8
8
  import { x as resolveOwnedIosSim } from "./ios-Deh5js5V.mjs";
9
- import { n as nativeRunCommand } from "./slot-launch-CZklomj6.mjs";
9
+ import { n as nativeRunCommand } from "./slot-launch-DeKen9e5.mjs";
10
10
  import chalk from "chalk";
11
11
  import { WebSocket } from "ws";
12
12
  //#region src/engine/reload.ts
@@ -0,0 +1,30 @@
1
+ import { createRequire } from "node:module";
2
+ //#region \0rolldown/runtime.js
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
8
+ var __exportAll = (all, no_symbols) => {
9
+ let target = {};
10
+ for (var name in all) __defProp(target, name, {
11
+ get: all[name],
12
+ enumerable: true
13
+ });
14
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
15
+ return target;
16
+ };
17
+ var __copyProps = (to, from, except, desc) => {
18
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
19
+ key = keys[i];
20
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
21
+ get: ((k) => from[k]).bind(null, key),
22
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
23
+ });
24
+ }
25
+ return to;
26
+ };
27
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
28
+ var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
29
+ //#endregion
30
+ export { __require as i, __exportAll as n, __reExport as r, __commonJSMin as t };
@@ -1,13 +1,10 @@
1
- import { p as readWorkspaceState } from "./state-B3XA2wp2.mjs";
1
+ import { i as __require, t as __commonJSMin } from "./rolldown-runtime-BsOwBuO_.mjs";
2
+ import { r as readWorkspaceState } from "./workspace-state-Do1e2mOH.mjs";
2
3
  import { n as supervisorError } from "./errors-B6W7FkxQ.mjs";
3
- import { C as projectMetroSharedCache } from "./settings-DEJf6_kJ.mjs";
4
+ import { C as projectMetroSharedCache } from "./settings-DwfltIXQ.mjs";
4
5
  import { a as metroStoreRoot, o as registerMetroStore, t as appendCacheStore } from "./metro-store-CDDZHtPl.mjs";
5
6
  import { createRequire } from "node:module";
6
7
  import { dirname, isAbsolute, join, relative, sep } from "node:path";
7
- //#region \0rolldown/runtime.js
8
- var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
9
- var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
10
- //#endregion
11
8
  //#region shim/bundle-response.cjs
12
9
  var require_bundle_response = /* @__PURE__ */ __commonJSMin(((exports, module) => {
13
10
  const { randomUUID } = __require("node:crypto");
@@ -1,10 +1,10 @@
1
1
  import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
2
  import { r as createNdjsonWriter } from "./ndjson-BZwI1NBv.mjs";
3
- import { p as readWorkspaceState } from "./state-B3XA2wp2.mjs";
4
- import { l as resolvePackageJson } from "./project-fKmuIaMI.mjs";
3
+ import { r as readWorkspaceState } from "./workspace-state-Do1e2mOH.mjs";
4
+ import { l as resolvePackageJson } from "./project-CBFCwnN5.mjs";
5
5
  import { n as supervisorError } from "./errors-B6W7FkxQ.mjs";
6
6
  import { n as stripAnsi, t as createLineReader } from "./process-output-C7IlIzd7.mjs";
7
- import { C as projectMetroSharedCache } from "./settings-DEJf6_kJ.mjs";
7
+ import { C as projectMetroSharedCache } from "./settings-DwfltIXQ.mjs";
8
8
  import { a as metroStoreRoot, i as metroStoreConfirmedRoot, n as expoMetroConfigPath, o as registerMetroStore, r as expoMetroStoreEnv } from "./metro-store-CDDZHtPl.mjs";
9
9
  import { accessSync, constants, existsSync, readFileSync } from "node:fs";
10
10
  import { dirname, join, parse } from "node:path";
@@ -0,0 +1,2 @@
1
+ import { p as startExpoServer } from "./server-expo-D4P7iTvf.mjs";
2
+ export { startExpoServer };
@@ -1,5 +1,5 @@
1
- import { d as getConfigPath, g as loadConfig, m as getRepoSettings, p as getProjectSettings } from "./config-CcefMY2Q.mjs";
2
- import { g as repoRoot, s as gitCommonDir } from "./worktree-Eq6TLH8N.mjs";
1
+ import { d as getConfigPath, g as loadConfig, m as getRepoSettings, p as getProjectSettings } from "./config-6vdGngKa.mjs";
2
+ import { _ as repoRoot, s as gitCommonDir } from "./worktree-DWNx4tUg.mjs";
3
3
  import { createHash } from "node:crypto";
4
4
  import { isAbsolute } from "node:path";
5
5
  import { existsSync, readFileSync, realpathSync, statSync } from "fs";