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,409 @@
1
+ import { D as process_identity_exports, M as isOnMountedVolume, N as listMountedVolumes, g as loadConfig$1, n as allMetroPorts, r as claimMetroPort, y as releaseMetroPort } from "./config-BEq0Qw_a.mjs";
2
+ import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
+ import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
4
+ import { existsSync as existsSync$1, readlinkSync, realpathSync as realpathSync$1 } from "fs";
5
+ import { join, resolve, sep } from "path";
6
+ import { buildCacheRoot as sharedBuildCache, withDirLock, workspaceId, workspaceName as workspaceName$1, workspaceStateDir as workspaceDir } from "@stim-cli/core";
7
+ import { emulatorLogFile, readJsonObject, readWorkspaceState, readWorkspaceState as readWorkspaceState$1, sharedCcache, sharedCompilationCache, supervisorLogFile, supervisorPidFile, workspaceDerivedData, workspaceLastUsed, workspaceLogErrorIndex, workspaceLogsDir, workspaceMetadataFile, workspaceStateFile, workspaceStateLock } from "@stim-cli/core/state";
8
+ import { dirname as dirname$1 } from "node:path";
9
+ import { request } from "http";
10
+ import { connect } from "net";
11
+ //#region src/workspace/paths.ts
12
+ function ensureWorkspaceStorage(projectRoot) {
13
+ const canonicalRoot = resolve(projectRoot);
14
+ const dir = workspaceDir(canonicalRoot);
15
+ const file = workspaceMetadataFile(canonicalRoot);
16
+ mkdirSync(dir, { recursive: true });
17
+ return withDirLock(join(dir, "metadata.lock"), () => {
18
+ if (!existsSync(file)) {
19
+ const metadata = {
20
+ projectRoot: canonicalRoot,
21
+ workspace: workspaceName$1(canonicalRoot),
22
+ version: 1
23
+ };
24
+ const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
25
+ writeFileSync(tmp, `${JSON.stringify(metadata, null, 2)}\n`);
26
+ try {
27
+ renameSync(tmp, file);
28
+ } catch (error) {
29
+ rmSync(tmp, { force: true });
30
+ throw error;
31
+ }
32
+ return dir;
33
+ }
34
+ if (readJsonObject(file)?.projectRoot === canonicalRoot) return dir;
35
+ const error = /* @__PURE__ */ new Error(`Stim workspace collision at ${dir}: its workspace.json does not belong to ${canonicalRoot}.`);
36
+ error.code = "STIM_WORKSPACE_COLLISION";
37
+ throw error;
38
+ });
39
+ }
40
+ //#endregion
41
+ //#region src/workspace/workspace-state.ts
42
+ function withWorkspaceStateLock(root, fn) {
43
+ const file = workspaceStateFile(root);
44
+ return withDirLock(workspaceStateLock(root), fn, { ensureParent: () => {
45
+ ensureWorkspaceStorage(root);
46
+ mkdirSync(dirname$1(file), { recursive: true });
47
+ } });
48
+ }
49
+ function writeWorkspaceState(root, patch) {
50
+ return updateWorkspaceState(root, (state) => ({
51
+ ...state,
52
+ ...patch
53
+ }));
54
+ }
55
+ function updateWorkspaceState(root, update) {
56
+ return withWorkspaceStateLock(root, () => replaceWorkspaceState(root, update(readWorkspaceState(root) ?? {})));
57
+ }
58
+ function replaceWorkspaceState(root, state) {
59
+ const file = workspaceStateFile(root);
60
+ mkdirSync(dirname$1(file), { recursive: true });
61
+ const tmp = `${file}.tmp-${process.pid}`;
62
+ writeFileSync(tmp, `${JSON.stringify(state, null, 2)}\n`);
63
+ renameSync(tmp, file);
64
+ return state;
65
+ }
66
+ function clearWorkspaceStateKeys(root, keys) {
67
+ if (!existsSync(workspaceStateFile(root))) return;
68
+ withWorkspaceStateLock(root, () => {
69
+ const state = readWorkspaceState(root);
70
+ const file = workspaceStateFile(root);
71
+ if (!state) {
72
+ try {
73
+ rmSync(file, { force: true });
74
+ } catch {}
75
+ return;
76
+ }
77
+ let changed = false;
78
+ for (const key of keys) {
79
+ if (!(key in state)) continue;
80
+ delete state[key];
81
+ changed = true;
82
+ }
83
+ if (!changed) return;
84
+ if (Object.keys(state).length === 0) {
85
+ rmSync(file, { force: true });
86
+ return;
87
+ }
88
+ replaceWorkspaceState(root, state);
89
+ });
90
+ }
91
+ function clearWorkspaceStateKey(root, key, shouldClear) {
92
+ return withWorkspaceStateLock(root, () => {
93
+ const state = readWorkspaceState(root);
94
+ if (!state || !(key in state)) return true;
95
+ if (!shouldClear(state[key])) return false;
96
+ delete state[key];
97
+ const file = workspaceStateFile(root);
98
+ if (Object.keys(state).length === 0) {
99
+ try {
100
+ rmSync(file, { force: true });
101
+ } catch {}
102
+ return true;
103
+ }
104
+ replaceWorkspaceState(root, state);
105
+ return true;
106
+ });
107
+ }
108
+ function recordWorkspaceUse(root, now = /* @__PURE__ */ new Date()) {
109
+ try {
110
+ writeWorkspaceState(root, { lastUsedAt: now.toISOString() });
111
+ } catch {}
112
+ }
113
+ //#endregion
114
+ //#region src/ports.ts
115
+ function isMetroRunning(port) {
116
+ return new Promise((resolve) => {
117
+ const req = request({
118
+ hostname: "localhost",
119
+ port,
120
+ path: "/status",
121
+ timeout: 2e3
122
+ }, (res) => {
123
+ let data = "";
124
+ res.on("data", (chunk) => {
125
+ data += chunk;
126
+ });
127
+ res.on("end", () => resolve(data.includes("packager-status:running")));
128
+ });
129
+ req.on("error", () => resolve(false));
130
+ req.on("timeout", () => {
131
+ req.destroy();
132
+ resolve(false);
133
+ });
134
+ req.end();
135
+ });
136
+ }
137
+ function isPortFree(port, { timeoutMs = 400 } = {}) {
138
+ return new Promise((resolve) => {
139
+ const sock = connect({
140
+ port,
141
+ host: "127.0.0.1"
142
+ });
143
+ const done = (free) => {
144
+ sock.removeAllListeners();
145
+ sock.destroy();
146
+ resolve(free);
147
+ };
148
+ sock.setTimeout(timeoutMs);
149
+ sock.once("connect", () => done(false));
150
+ sock.once("error", () => done(true));
151
+ sock.once("timeout", () => done(false));
152
+ });
153
+ }
154
+ const FIRST_PORT = 8082;
155
+ async function computeNextPort(isFree = isPortFree) {
156
+ const taken = new Set(allMetroPorts());
157
+ for (let port = FIRST_PORT; port < 8282; port++) {
158
+ if (taken.has(port)) continue;
159
+ if (await isFree(port)) return port;
160
+ }
161
+ 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>\`).`);
162
+ }
163
+ async function findReclaimablePort(excludeProjectPath, probe = isMetroRunning, { isMounted = isOnMountedVolume, mountedVolumes } = {}) {
164
+ const cfg = loadConfig$1();
165
+ if (!cfg?.projects) return null;
166
+ const mounted = mountedVolumes || listMountedVolumes();
167
+ const candidates = [];
168
+ for (const [path, proj] of Object.entries(cfg.projects)) {
169
+ if (path === excludeProjectPath) continue;
170
+ if (Object.keys(proj.ports ?? {}).length) continue;
171
+ if (existsSync$1(path)) continue;
172
+ if (!isMounted(path, mounted)) continue;
173
+ if (typeof proj.metroPort === "number") candidates.push({
174
+ port: proj.metroPort,
175
+ ownerPath: path
176
+ });
177
+ }
178
+ for (const c of candidates) if (!await probe(c.port)) return c;
179
+ return null;
180
+ }
181
+ async function allocatePort(projectPath, probe = isMetroRunning, isFree = isPortFree) {
182
+ const reclaim = await findReclaimablePort(projectPath, probe);
183
+ if (reclaim && await isFree(reclaim.port)) {
184
+ releaseMetroPort(reclaim.ownerPath, reclaim.port);
185
+ return reclaim.port;
186
+ }
187
+ return computeNextPort(isFree);
188
+ }
189
+ const RESERVE_ATTEMPTS = 5;
190
+ async function reserveMetroPort(projectPath, probe = isMetroRunning, isFree = isPortFree) {
191
+ for (let attempt = 0; attempt < RESERVE_ATTEMPTS; attempt++) {
192
+ const port = await allocatePort(projectPath, probe, isFree);
193
+ const claimed = claimMetroPort(projectPath, port);
194
+ if (claimed !== null) return claimed;
195
+ }
196
+ throw new Error(`Could not reserve a Metro port after ${RESERVE_ATTEMPTS} attempts: another Stim run claimed each one first. Retry.`);
197
+ }
198
+ //#endregion
199
+ //#region src/metro.ts
200
+ /**
201
+ * True when some process with this pid exists right now. It cannot tell a recycled pid from the
202
+ * process that first held it, so it is only valid for a pid this run spawned and still holds a
203
+ * handle on. For a pid read from a record on disk, decide with `inspectProcessIdentity` on that
204
+ * record's process token, or take an ownership claim (`ownership-claim.ts`): this answer would keep
205
+ * a dead holder's record looking live until some unrelated process exits.
206
+ */
207
+ function pidExists(pid) {
208
+ if (!Number.isSafeInteger(pid) || pid <= 0) return false;
209
+ try {
210
+ process.kill(pid, 0);
211
+ return true;
212
+ } catch (error) {
213
+ return error.code !== "ESRCH";
214
+ }
215
+ }
216
+ function parseLsofPids(out) {
217
+ if (!out) return [];
218
+ return String(out).split("\n").map((s) => parseInt(s.trim(), 10)).filter((n) => Number.isFinite(n));
219
+ }
220
+ function addressPort(address) {
221
+ const colon = String(address ?? "").lastIndexOf(":");
222
+ return colon < 0 ? NaN : Number(String(address).slice(colon + 1));
223
+ }
224
+ function parseNetstatPids(out, port) {
225
+ const pids = [];
226
+ for (const line of String(out ?? "").split("\n")) {
227
+ const cols = line.trim().split(/\s+/);
228
+ if (cols.length < 5 || cols[0]?.toUpperCase() !== "TCP") continue;
229
+ if (addressPort(cols[1]) !== port || addressPort(cols[2]) !== 0) continue;
230
+ const pid = Number(cols.at(-1));
231
+ if (Number.isSafeInteger(pid) && pid > 0 && !pids.includes(pid)) pids.push(pid);
232
+ }
233
+ return pids;
234
+ }
235
+ function listeningPids(port, platform = process.platform) {
236
+ const pids = parseLsofPids(getExecutor().runQuiet(`lsof -nP -iTCP:${port} -sTCP:LISTEN -t`));
237
+ if (pids.length > 0 || platform !== "win32") return pids;
238
+ return parseNetstatPids(getExecutor().runQuiet("netstat -ano"), port);
239
+ }
240
+ function parseLsofCwd(out) {
241
+ if (!out) return null;
242
+ const lines = String(out).split("\n");
243
+ const idx = lines.findIndex((l) => l === "fcwd");
244
+ if (idx === -1) return null;
245
+ const nLine = lines.slice(idx + 1).find((l) => l.startsWith("n"));
246
+ return nLine ? nLine.slice(1) : null;
247
+ }
248
+ function lsofOutput(args) {
249
+ return getExecutor().runFileAsync("lsof", args).catch((error) => typeof error?.stdout === "string" ? error.stdout : null);
250
+ }
251
+ function parseLsofListeners(out) {
252
+ const byPort = /* @__PURE__ */ new Map();
253
+ let pid = null;
254
+ for (const line of String(out ?? "").split("\n")) if (line.startsWith("p")) pid = parseInt(line.slice(1), 10);
255
+ else if (line.startsWith("n") && pid !== null && Number.isFinite(pid)) {
256
+ const port = addressPort(line.slice(1));
257
+ const pids = byPort.get(port) ?? [];
258
+ if (!pids.includes(pid)) pids.push(pid);
259
+ byPort.set(port, pids);
260
+ }
261
+ return byPort;
262
+ }
263
+ /**
264
+ * `listeningPids` for several ports with one lsof call. A port lsof could not be asked about is
265
+ * missing from the map.
266
+ */
267
+ async function listeningPidsByPort(ports) {
268
+ const result = /* @__PURE__ */ new Map();
269
+ if (ports.length === 0) return result;
270
+ const out = await lsofOutput([
271
+ "-nP",
272
+ `-iTCP:${ports.join(",")}`,
273
+ "-sTCP:LISTEN",
274
+ "-Fpn"
275
+ ]);
276
+ if (out === null) return result;
277
+ const found = parseLsofListeners(out);
278
+ for (const port of ports) result.set(port, found.get(port) ?? []);
279
+ return result;
280
+ }
281
+ function parseLsofCwds(out) {
282
+ const cwds = /* @__PURE__ */ new Map();
283
+ let pid = null;
284
+ let inCwd = false;
285
+ for (const line of String(out ?? "").split("\n")) if (line.startsWith("p")) {
286
+ pid = parseInt(line.slice(1), 10);
287
+ inCwd = false;
288
+ } else if (line.startsWith("f")) inCwd = line === "fcwd";
289
+ else if (inCwd && line.startsWith("n") && pid !== null && !cwds.has(pid)) cwds.set(pid, line.slice(1));
290
+ return cwds;
291
+ }
292
+ /** `processCwd` for several pids with at most one lsof call. A pid lsof could not be asked about is missing from the map. */
293
+ async function processCwds(pids) {
294
+ const cwds = /* @__PURE__ */ new Map();
295
+ const viaLsof = [];
296
+ for (const pid of new Set(pids)) {
297
+ let cwd = null;
298
+ if (process.platform === "linux") try {
299
+ cwd = readlinkSync(`/proc/${pid}/cwd`);
300
+ } catch {}
301
+ if (cwd) cwds.set(pid, cwd);
302
+ else viaLsof.push(pid);
303
+ }
304
+ if (viaLsof.length === 0) return cwds;
305
+ const out = await lsofOutput([
306
+ "-a",
307
+ "-p",
308
+ viaLsof.join(","),
309
+ "-d",
310
+ "cwd",
311
+ "-Fn"
312
+ ]);
313
+ if (out === null) return cwds;
314
+ const found = parseLsofCwds(out);
315
+ for (const pid of viaLsof) cwds.set(pid, found.get(pid) ?? null);
316
+ return cwds;
317
+ }
318
+ function processCwd(pid) {
319
+ if (process.platform === "linux") try {
320
+ return readlinkSync(`/proc/${pid}/cwd`);
321
+ } catch {}
322
+ return parseLsofCwd(getExecutor().runQuiet(`lsof -a -p ${pid} -d cwd -Fn`));
323
+ }
324
+ function canonicalPath(path) {
325
+ try {
326
+ return realpathSync$1(path);
327
+ } catch {
328
+ return path;
329
+ }
330
+ }
331
+ function isInsideProject(cwd, projectPath) {
332
+ if (!cwd || !projectPath) return false;
333
+ const a = canonicalPath(cwd);
334
+ const b = canonicalPath(projectPath);
335
+ return a === b || a.startsWith(b.endsWith(sep) ? b : b + sep);
336
+ }
337
+ const NOT_OURS_UNRESPONSIVE = "unresponsive";
338
+ const NOT_OURS_UNREADABLE_CWD = "unreadable-cwd";
339
+ const NOT_OURS_FOREIGN_CWD = "foreign-cwd";
340
+ function ownedSupervisor(projectPath, port) {
341
+ const supervisor = readWorkspaceState$1(canonicalPath(projectPath))?.supervisor;
342
+ if (supervisor?.port !== port || (0, process_identity_exports.inspectProcessIdentity)(supervisor) !== "same") return null;
343
+ return supervisor;
344
+ }
345
+ async function resolveProjectMetro(port, projectPath, { probe = isMetroRunning, pidsOf = listeningPids, cwdOf = processCwd } = {}) {
346
+ const pid = (await pidsOf(port))[0];
347
+ if (pid === void 0) return { missing: true };
348
+ if (!await probe(port)) return {
349
+ notOurs: `pid ${pid} on port ${port} does not answer Metro's /status`,
350
+ kind: NOT_OURS_UNRESPONSIVE,
351
+ pid
352
+ };
353
+ const cwd = await cwdOf(pid);
354
+ if (!cwd) {
355
+ const owner = ownedSupervisor(projectPath, port);
356
+ if (owner && (pid === owner.pid || pid === owner.serverPid)) return { metro: {
357
+ pid,
358
+ leader: owner.pid,
359
+ cwd: canonicalPath(projectPath),
360
+ processToken: owner.processToken
361
+ } };
362
+ return {
363
+ notOurs: `pid ${pid} on port ${port}: working directory could not be read`,
364
+ kind: NOT_OURS_UNREADABLE_CWD,
365
+ pid
366
+ };
367
+ }
368
+ if (!isInsideProject(cwd, projectPath)) return {
369
+ notOurs: `pid ${pid} on port ${port} runs from ${cwd}, outside ${projectPath}`,
370
+ kind: NOT_OURS_FOREIGN_CWD,
371
+ pid
372
+ };
373
+ const owner = ownedSupervisor(projectPath, port);
374
+ return { metro: {
375
+ pid,
376
+ leader: owner ? owner.pid : pid,
377
+ cwd,
378
+ ...owner ? { processToken: owner.processToken } : {}
379
+ } };
380
+ }
381
+ /**
382
+ * Terminate a process and everything it spawned. On POSIX `group` signals the process group, which
383
+ * is what a detached leader owns. Windows has no process groups, and its SIGTERM is
384
+ * TerminateProcess: no handler runs and no child is reached, so a spawned dev server or `adb
385
+ * logcat` would outlive its parent. taskkill /T is the only route to the tree there, and it is
386
+ * always forceful.
387
+ */
388
+ function signalProcessTree(pid, signal = "SIGTERM", { group = false, platform = process.platform } = {}) {
389
+ if (platform !== "win32") return process.kill(group ? -pid : pid, signal);
390
+ return getExecutor().runFileQuiet("taskkill", [
391
+ "/PID",
392
+ String(pid),
393
+ "/T",
394
+ "/F"
395
+ ]) !== null;
396
+ }
397
+ function killMetroTree(leader, processToken) {
398
+ if (!leader || leader === process.pid || (0, process_identity_exports.inspectProcessIdentity)({
399
+ pid: leader,
400
+ processToken
401
+ }) !== "same") return false;
402
+ try {
403
+ return signalProcessTree(leader, "SIGTERM", { group: true });
404
+ } catch {
405
+ return false;
406
+ }
407
+ }
408
+ //#endregion
409
+ export { workspaceLogErrorIndex as A, sharedCcache as C, workspaceDerivedData as D, supervisorPidFile as E, workspaceName$1 as M, workspaceStateFile as N, workspaceDir as O, withDirLock as P, sharedBuildCache as S, supervisorLogFile as T, withWorkspaceStateLock as _, processCwd as a, emulatorLogFile as b, signalProcessTree as c, reserveMetroPort as d, clearWorkspaceStateKey as f, updateWorkspaceState as g, recordWorkspaceUse as h, pidExists as i, workspaceLogsDir as j, workspaceId as k, isMetroRunning as l, readWorkspaceState$1 as m, listeningPids as n, processCwds as o, clearWorkspaceStateKeys as p, listeningPidsByPort as r, resolveProjectMetro as s, killMetroTree as t, isPortFree as u, workspaceLastUsed as v, sharedCompilationCache as w, ensureWorkspaceStorage as x, writeWorkspaceState as y };
@@ -1,7 +1,7 @@
1
- import { t as getExecutor } from "./exec-B29XNuhP.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-B7iyKWkL.mjs";
3
- import { c as isPortFree, o as signalProcessTree, r as listeningPids } from "./metro-C7bcJ0uo.mjs";
4
- import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BYfZz_S-.mjs";
1
+ import { D as process_identity_exports, T as withConfigLock, f as getProject, g as loadConfig, s as ensureConfig, u as getConfigDir, x as saveConfig } from "./config-BEq0Qw_a.mjs";
2
+ import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
+ import { c as signalProcessTree, n as listeningPids, u as isPortFree } from "./metro-BWLOJ_Wr.mjs";
4
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BXA96myD.mjs";
5
5
  import { realpathSync } from "node:fs";
6
6
  //#region src/named-ports.ts
7
7
  const FIRST_PORT = 8900;
@@ -1,47 +1,9 @@
1
1
  import { closeSync, mkdirSync, openSync, writeSync } from "fs";
2
2
  import { dirname } from "path";
3
+ import { rotateLog } from "@stim-cli/core";
4
+ import { LEVELS, SOURCES, levelRank, parseNdjsonLine, readNdjsonGenerations } from "@stim-cli/core/state";
3
5
  //#region src/ndjson.ts
4
- const LEVELS = [
5
- "debug",
6
- "info",
7
- "warn",
8
- "error",
9
- "fatal"
10
- ];
11
- const SOURCES = [
12
- "metro",
13
- "client",
14
- "device",
15
- "build"
16
- ];
17
- function levelRank(level) {
18
- const i = LEVELS.indexOf(level);
19
- return i < 0 ? 0 : i;
20
- }
21
- function parseNdjsonLine(line) {
22
- if (typeof line !== "string") return null;
23
- const trimmed = line.trim();
24
- if (!trimmed) return null;
25
- let value;
26
- try {
27
- value = JSON.parse(trimmed);
28
- } catch {
29
- return null;
30
- }
31
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
32
- return value;
33
- }
34
- function parseNdjsonText(text) {
35
- if (typeof text !== "string" || text === "") return [];
36
- const out = [];
37
- const lines = text.split("\n");
38
- lines.pop();
39
- for (const line of lines) {
40
- const record = parseNdjsonLine(line);
41
- if (record) out.push(record);
42
- }
43
- return out;
44
- }
6
+ const ROTATE_CHECK_MS = 1e3;
45
7
  function formatNdjsonLine(record) {
46
8
  try {
47
9
  return `${JSON.stringify(record)}\n`;
@@ -49,9 +11,11 @@ function formatNdjsonLine(record) {
49
11
  return null;
50
12
  }
51
13
  }
52
- function createNdjsonWriter(file, { truncate = false, fields = {} } = {}) {
14
+ function createNdjsonWriter(file, { truncate = false, fields = {}, maxBytes, now = Date.now } = {}) {
53
15
  let fd = null;
54
16
  let freshFile = truncate;
17
+ let uncheckedBytes = 0;
18
+ let checkedAt = 0;
55
19
  let written = 0;
56
20
  let dropped = 0;
57
21
  let lastError = null;
@@ -61,6 +25,20 @@ function createNdjsonWriter(file, { truncate = false, fields = {} } = {}) {
61
25
  fd = openSync(file, freshFile ? "w" : "a");
62
26
  freshFile = false;
63
27
  }
28
+ function rotateIfDue(limit) {
29
+ if (fd !== null && uncheckedBytes < limit / 8 && now() - checkedAt < ROTATE_CHECK_MS) return;
30
+ if (fd !== null) {
31
+ try {
32
+ closeSync(fd);
33
+ } catch {}
34
+ fd = null;
35
+ }
36
+ uncheckedBytes = 0;
37
+ checkedAt = now();
38
+ try {
39
+ rotateLog(file, limit);
40
+ } catch {}
41
+ }
64
42
  function write(record) {
65
43
  if (closed) {
66
44
  dropped += 1;
@@ -76,8 +54,10 @@ function createNdjsonWriter(file, { truncate = false, fields = {} } = {}) {
76
54
  return false;
77
55
  }
78
56
  try {
57
+ if (maxBytes !== void 0) rotateIfDue(maxBytes);
79
58
  if (fd === null) open();
80
59
  writeSync(fd, line);
60
+ uncheckedBytes += Buffer.byteLength(line);
81
61
  written += 1;
82
62
  return true;
83
63
  } catch (err) {
@@ -131,4 +111,4 @@ function stamp(record) {
131
111
  };
132
112
  }
133
113
  //#endregion
134
- export { parseNdjsonLine as a, levelRank as i, SOURCES as n, parseNdjsonText as o, createNdjsonWriter as r, LEVELS as t };
114
+ export { parseNdjsonLine as a, levelRank as i, SOURCES as n, readNdjsonGenerations as o, createNdjsonWriter as r, LEVELS as t };
@@ -1,3 +1,4 @@
1
+ import "@stim-cli/core/state";
1
2
  //#region src/ndjson.d.ts
2
3
  interface NdjsonWriter {
3
4
  readonly file: string;
@@ -1,6 +1,5 @@
1
- import { w as process_identity_exports } from "./config-B7iyKWkL.mjs";
2
- import { a as withWorkspaceStateLock, o as writeWorkspaceState, r as readWorkspaceState } from "./workspace-state-BT5E3SA2.mjs";
3
- import { i as pidExists } from "./metro-C7bcJ0uo.mjs";
1
+ import { D as process_identity_exports } from "./config-BEq0Qw_a.mjs";
2
+ import { _ as withWorkspaceStateLock, i as pidExists, m as readWorkspaceState, y as writeWorkspaceState } from "./metro-BWLOJ_Wr.mjs";
4
3
  import { realpathSync } from "node:fs";
5
4
  //#region src/collector/state.ts
6
5
  function collectorRoot(root) {