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.
- package/README.md +23 -155
- package/dist/{android-DWssqHQ8.mjs → android-787HhP5w.mjs} +168 -39
- package/dist/{android-BRgBYzU0.mjs → android-DBkeHwpm.mjs} +250 -200
- package/dist/{android-DeYdjjBP.mjs → android-DPov-fv2.mjs} +2 -2
- package/dist/{android-cas-SWIjzeuL.mjs → android-cas-CITmIeWY.mjs} +22 -14
- package/dist/android-cas-compiler.mjs +2 -2
- package/dist/app-install-CrJ6t_bv.mjs +819 -0
- package/dist/budget-BP4jZZL4.mjs +620 -0
- package/dist/build-progress-BXx-ZBe4.mjs +512 -0
- package/dist/{cache-manifest-CYJi8-Hx.mjs → cache-manifest-Pubfp8wV.mjs} +4 -4
- package/dist/cache-manifest.mjs +1 -1
- package/dist/cli.mjs +22 -19
- package/dist/collector-run.d.mts +7 -1
- package/dist/collector-run.mjs +10 -9
- package/dist/{command-output-B3ycpsYv.mjs → command-output-D5z2_bcF.mjs} +11 -2
- package/dist/{config-B7iyKWkL.mjs → config-BEq0Qw_a.mjs} +134 -121
- package/dist/{dependency-state-D0M7oKA7.mjs → dependency-state-r9UesA9a.mjs} +2 -2
- package/dist/{deps-oCXVNEqQ.mjs → deps-DP4eWqea.mjs} +8 -5
- package/dist/{detached-entry-Bpo6ZY_I.mjs → detached-entry-DJHoca49.mjs} +1 -1
- package/dist/{device-BUoZyMoR.mjs → dev-client-92U593KT.mjs} +1090 -660
- package/dist/{device-RdujjHLB.mjs → device-DhTZe7L4.mjs} +13 -20
- package/dist/{build-lock-CgtsB6TV.mjs → device-lease-CbEaIk7b.mjs} +481 -27
- package/dist/{device-pool-BxRwdufO.mjs → device-pool-Di8Q-9Ro.mjs} +7 -7
- package/dist/{device-remote-ByN8P6CB.mjs → device-remote-Dy1jGj3U.mjs} +645 -119
- package/dist/{doctor-BMt156qe.mjs → doctor-B-inAJmt.mjs} +137 -113
- package/dist/{error-diagnostics-D1Kaf-eT.mjs → error-diagnostics-xxWKNO3-.mjs} +22 -17
- package/dist/exec-DK8KaVHK.mjs +133 -0
- package/dist/{gc-v2YcoJvm.mjs → gc-ChPFZMRC.mjs} +836 -813
- package/dist/{guide-BTAlD1_y.mjs → guide-DwmaD_Wa.mjs} +953 -209
- package/dist/guide-status-CWvti2Lg.mjs +125 -0
- package/dist/idle-DpRLp5lj.mjs +157 -0
- package/dist/{build-slots-C-oAHRT1.mjs → in-use-CjUluqIX.mjs} +102 -8
- package/dist/{ios-BcCiI4TS.mjs → ios-B2Fpqvlb.mjs} +60 -71
- package/dist/{ios-BP04pY5f.mjs → ios-Dd-hYJBh.mjs} +241 -234
- package/dist/{ios-device-CFXA46ez.mjs → ios-device-BiOsSVzd.mjs} +3 -2
- package/dist/{ios-device-CauFJiYx.mjs → ios-device-CtRdEVjx.mjs} +149 -52
- package/dist/launch-verify-CQS_Kg-C.mjs +365 -0
- package/dist/{logs-CakdBTtm.mjs → logs-MARIy6m9.mjs} +20 -15
- package/dist/metro-BWLOJ_Wr.mjs +409 -0
- package/dist/{named-ports-DzMOlmaX.mjs → named-ports-DiTfSz6p.mjs} +4 -4
- package/dist/{ndjson-BZwI1NBv.mjs → ndjson-CDY9IkVC.mjs} +23 -43
- package/dist/{ndjson-DvHSsNU9.d.mts → ndjson-Du3j0AQ9.d.mts} +1 -0
- package/dist/{ownership-Cp52QAK1.mjs → ownership-D2xbLVoz.mjs} +2 -3
- package/dist/{teardown-BJqEzPo_.mjs → ownership-DZjdx8Gq.mjs} +664 -612
- package/dist/{ports-BIbJaKNW.mjs → ports-CLSSFOuz.mjs} +3 -3
- package/dist/{process-output-Dl5uKE03.mjs → process-output-Fpl8PYi8.mjs} +1 -1
- package/dist/{project-DDZWf_16.mjs → project-BXZzOABt.mjs} +8 -3
- package/dist/{reload-BdypPbjM.mjs → reload-DoETJ3rl.mjs} +11 -13
- package/dist/{server-bare-BQkyxa_b.mjs → server-bare-DVbb7kXH.mjs} +2 -2
- package/dist/{server-expo-BVAA1OqJ.mjs → server-expo-BvOCeakp.mjs} +8 -7
- package/dist/server-expo-DBVTdult.mjs +2 -0
- package/dist/{settings-BXXorVM2.mjs → settings-DOugPlPz.mjs} +59 -159
- package/dist/settings-EWYNwe72.mjs +378 -0
- package/dist/settings.schema.json +1064 -0
- package/dist/simslim-Ca3487H6.mjs +436 -0
- package/dist/{slot-launch-ZUUe0TQP.mjs → slot-launch-C5cg0fmk.mjs} +4 -4
- package/dist/spawn-claims-CUleC2cM.mjs +49 -0
- package/dist/{start-CmxtOKjU.mjs → start-F9gv5dcC.mjs} +159 -114
- package/dist/start-VoFsPSes.mjs +2 -0
- package/dist/{state-DZobzxLW.mjs → state-WGdIbi_d.mjs} +5 -3
- package/dist/{stats-BYcZ6n1h.mjs → stats-BAgJaLA4.mjs} +66 -5
- package/dist/{stats-CLg5RuDS.mjs → stats-DBYJ5bxL.mjs} +4 -4
- package/dist/status-DLCD-o3s.mjs +764 -0
- package/dist/status-Dr4fRkym.mjs +216 -0
- package/dist/{guide-status-Craq9z0t.mjs → stim-installations-CINXy_su.mjs} +3 -122
- package/dist/{stop-C8JW6kKp.mjs → stop-Dj4pfmyv.mjs} +111 -31
- package/dist/{stop-B1kOpYei.mjs → stop-ZvOeTmKb.mjs} +2 -2
- package/dist/supervisor-run.d.mts +11 -2
- package/dist/supervisor-run.mjs +228 -42
- package/dist/{workspace-process-lock-BYfZz_S-.mjs → workspace-process-lock-BXA96myD.mjs} +14 -9
- package/dist/workspaces-BTUyFfkv.mjs +1003 -0
- package/dist/worktree-4aZg2NnI.mjs +2 -0
- package/dist/{worktree-CFLA-6MX.mjs → worktree-BMS7GeqB.mjs} +523 -61
- package/dist/{worktree-FG_ZKKdb.mjs → worktree-BlizKDpZ.mjs} +200 -54
- package/dist/{xcode-B6YaWeg0.mjs → xcode-B2tBkf0m.mjs} +66 -57
- package/package.json +6 -6
- package/skill/SKILL.md +2 -0
- package/dist/app-install-BDbsSgCl.mjs +0 -1175
- package/dist/dev-client-BmT-ioFI.mjs +0 -797
- package/dist/device-lease-Ffz_M5t0.mjs +0 -359
- package/dist/exec-B29XNuhP.mjs +0 -77
- package/dist/logs-query-BGL-Pl12.mjs +0 -290
- package/dist/metro-C7bcJ0uo.mjs +0 -233
- package/dist/ownership-wolE-0Do.mjs +0 -470
- package/dist/public-http-probe-CyMDrQ3c.mjs +0 -61
- package/dist/reclaim-KJ-0uWEF.mjs +0 -361
- package/dist/server-expo-YDDG3eP7.mjs +0 -2
- package/dist/spawn-entry-nCE6m770.mjs +0 -14
- package/dist/status-DdkLki47.mjs +0 -426
- package/dist/workspace-state-BT5E3SA2.mjs +0 -146
- 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 {
|
|
2
|
-
import {
|
|
3
|
-
import { c as
|
|
4
|
-
import { t as withWorkspaceProcessLock } from "./workspace-process-lock-
|
|
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
|
|
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,
|
|
114
|
+
export { parseNdjsonLine as a, levelRank as i, SOURCES as n, readNdjsonGenerations as o, createNdjsonWriter as r, LEVELS as t };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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) {
|