stim 1.2.0 → 1.3.1
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 +34 -3
- package/dist/{android-Bcmev2s2.mjs → android-CT-PkXUh.mjs} +157 -62
- package/dist/{android-cas-D6HPZ6UQ.mjs → android-cas-DXorPCi_.mjs} +5 -5
- package/dist/android-hMj83VpN.mjs +1374 -0
- package/dist/{app-install-NFxUahnH.mjs → app-install-DIp9riNx.mjs} +6 -6
- package/dist/{build-slots-D0b6sJyo.mjs → build-slots-C4AouyNz.mjs} +2 -2
- package/dist/{cache-manifest-Cu4_Znw9.mjs → cache-manifest-CXR-Y8pH.mjs} +1 -1
- package/dist/cache-manifest.mjs +1 -1
- package/dist/cli.mjs +13 -13
- package/dist/collector-run.d.mts +3 -1
- package/dist/collector-run.mjs +33 -8
- package/dist/{config-CMtIaTk9.mjs → config-CcefMY2Q.mjs} +93 -12
- package/dist/{deps-BLBsTNDS.mjs → deps-0Yt0Inly.mjs} +1 -1
- package/dist/{dev-client-RpvXxCXV.mjs → dev-client-VfSZNLT4.mjs} +186 -9
- package/dist/{device-CoCPmazS.mjs → device-Bbv45Z9Z.mjs} +19 -11
- package/dist/{device-lease-Dpik41RY.mjs → device-lease-CJwK7OZr.mjs} +107 -83
- package/dist/{device-pool-B5pEVa3t.mjs → device-pool-CcBNTvM7.mjs} +18 -12
- package/dist/{device-remote-E81KAdXf.mjs → device-remote-DAA0Zm6Z.mjs} +7 -7
- package/dist/{doctor-CqPeMsSm.mjs → doctor-B4EITgQB.mjs} +11 -12
- package/dist/{doctor-D_CX7ib5.mjs → doctor-BueDfLYG.mjs} +4 -4
- package/dist/{error-diagnostics-Dw8Gn3yw.mjs → error-diagnostics-DwhNBFDU.mjs} +62 -52
- package/dist/{gc-B7KkDinF.mjs → gc-BRu62v4o.mjs} +66 -57
- package/dist/{guide-BS2Xc24o.mjs → guide-D95E0u3i.mjs} +307 -74
- package/dist/{ios-B7KLgIp5.mjs → ios-DAju3TVy.mjs} +183 -54
- package/dist/{ios-D1h1Di39.mjs → ios-Deh5js5V.mjs} +78 -40
- package/dist/{ios-device-xNt0Lg6v.mjs → ios-device-CWOM_S44.mjs} +1 -1
- package/dist/{logs-DcX9AvRF.mjs → logs-CAdwW6c2.mjs} +34 -6
- package/dist/{logs-query-oqn47Ffx.mjs → logs-query-Cdp5aaCH.mjs} +23 -6
- package/dist/{metro-COslRK_F.mjs → metro-CORqXyTL.mjs} +2 -2
- package/dist/{ndjson-DcAtEx_K.mjs → ndjson-BZwI1NBv.mjs} +5 -2
- package/dist/{ownership-CGPart6T.mjs → ownership-8O073GIC.mjs} +3 -3
- package/dist/{ownership-CYOyqxUo.mjs → ownership-DXcol4oU.mjs} +3 -3
- package/dist/{ownership-claim-7zRxxTfZ.mjs → ownership-claim-BtPtJVmq.mjs} +38 -23
- package/dist/{project-BBTVABUz.mjs → project-fKmuIaMI.mjs} +2 -2
- package/dist/{reclaim-C5Jmuahx.mjs → reclaim-CUmwucWG.mjs} +80 -74
- package/dist/{reload-Z8nKifAk.mjs → reload-CWpXoOgN.mjs} +32 -24
- package/dist/{server-bare-DEk37Mz2.mjs → server-bare-DEzPZLvf.mjs} +2 -2
- package/dist/server-expo-44em5Pmg.mjs +2 -0
- package/dist/{server-expo-C3ktAwUG.mjs → server-expo-DizT4OMk.mjs} +4 -4
- package/dist/{settings-DwU1_U_q.mjs → settings-DEJf6_kJ.mjs} +2 -2
- package/dist/slot-launch-CZklomj6.mjs +19 -0
- package/dist/{start-BrY4EuIg.mjs → start-B5G4NjiA.mjs} +11 -11
- package/dist/{state-CZ-ER-8d.mjs → state-B3XA2wp2.mjs} +9 -9
- package/dist/{stats-BCWEVLrM.mjs → stats-BR-kwj0B.mjs} +1 -1
- package/dist/{stats-BsF15Bti.mjs → stats-CaaQr4yN.mjs} +3 -3
- package/dist/{status-DFI1Hfpg.mjs → status-CI9clRSb.mjs} +55 -24
- package/dist/{stop-B7DR4awK.mjs → stop-CQhffpmX.mjs} +2 -2
- package/dist/{stop-Btg770vV.mjs → stop-CjlgDk0a.mjs} +93 -42
- package/dist/supervisor-run.d.mts +1 -2
- package/dist/supervisor-run.mjs +5 -5
- package/dist/{remote-cache-DDmJ1Roq.mjs → teardown--gESs9ck.mjs} +985 -887
- package/dist/{workspace-process-lock-BKfdys1q.mjs → workspace-process-lock-IBj1Towr.mjs} +1 -1
- package/dist/{worktree-BPTbI-LN.mjs → worktree-DtQGO9-s.mjs} +121 -111
- package/dist/{worktree-C79xx_kM.mjs → worktree-Eq6TLH8N.mjs} +1 -1
- package/dist/{xcode-DeKCribl.mjs → xcode-xnxyvJkD.mjs} +159 -96
- package/package.json +5 -5
- package/dist/android-K63_0UfF.mjs +0 -727
- package/dist/build-lock-BsVoLDaq.mjs +0 -645
- package/dist/server-expo-Drd7ksHu.mjs +0 -2
|
@@ -87,25 +87,22 @@ const BOOT_STATE_LIST_TIMEOUT_MS = 3e4;
|
|
|
87
87
|
function iosSimulatorFailureAdvice(exec = getExecutor()) {
|
|
88
88
|
return hostMemoryPressureAdvice(readHostMemoryPressure(exec)) ?? "The simulator may be unresponsive. Check Activity Monitor for memory pressure and free host memory before retrying.";
|
|
89
89
|
}
|
|
90
|
-
function bootstatusTimeout(
|
|
91
|
-
const e = /* @__PURE__ */ new Error(
|
|
90
|
+
function bootstatusTimeout(command) {
|
|
91
|
+
const e = /* @__PURE__ */ new Error(`${command} timed out`);
|
|
92
92
|
e.code = "ETIMEDOUT";
|
|
93
93
|
return e;
|
|
94
94
|
}
|
|
95
|
-
async function
|
|
95
|
+
async function awaitBootCommand(args, attemptMs, onLine) {
|
|
96
|
+
const command = `xcrun ${args.join(" ")}`;
|
|
96
97
|
const lines = [];
|
|
97
98
|
const reader = createLineReader((raw) => {
|
|
98
99
|
const line = stripAnsi(raw).trim();
|
|
99
100
|
if (!line) return;
|
|
101
|
+
onLine(line);
|
|
100
102
|
lines.push(line);
|
|
101
103
|
if (lines.length > 10) lines.shift();
|
|
102
104
|
});
|
|
103
|
-
const child = getExecutor().spawn("xcrun", [
|
|
104
|
-
"simctl",
|
|
105
|
-
"bootstatus",
|
|
106
|
-
udid,
|
|
107
|
-
"-b"
|
|
108
|
-
], { stdio: [
|
|
105
|
+
const child = getExecutor().spawn("xcrun", args, { stdio: [
|
|
109
106
|
"ignore",
|
|
110
107
|
"pipe",
|
|
111
108
|
"pipe"
|
|
@@ -132,48 +129,89 @@ async function awaitBootstatus(udid, attemptMs) {
|
|
|
132
129
|
child.stdout?.destroy?.();
|
|
133
130
|
child.stderr?.destroy?.();
|
|
134
131
|
child.unref();
|
|
135
|
-
throw new Error(`Timed out waiting for
|
|
132
|
+
throw new Error(`Timed out waiting for ${command}; its process could not be confirmed stopped. Inspect pid ${child.pid ?? "unknown"} before retrying.`);
|
|
136
133
|
}
|
|
137
|
-
throw bootstatusTimeout(
|
|
134
|
+
throw bootstatusTimeout(command);
|
|
138
135
|
}
|
|
139
136
|
if (result.error) throw result.error;
|
|
140
137
|
if (result.code === 0) return;
|
|
141
138
|
const detail = lines.length ? `: ${lines.join(" | ")}` : "";
|
|
142
|
-
throw new Error(
|
|
139
|
+
throw new Error(`${command} failed with exit code ${result.code ?? "unknown"}${detail}`);
|
|
143
140
|
}
|
|
144
|
-
async function bootIosSim(udid, { timeoutMs = IOS_BOOT_TIMEOUT_MS, attemptMs = BOOTSTATUS_ATTEMPT_MS } = {}) {
|
|
141
|
+
async function bootIosSim(udid, { timeoutMs = IOS_BOOT_TIMEOUT_MS, attemptMs = BOOTSTATUS_ATTEMPT_MS, label = udid, out = () => {} } = {}) {
|
|
145
142
|
const exec = getExecutor();
|
|
146
|
-
const
|
|
143
|
+
const started = Date.now();
|
|
144
|
+
const deadline = started + timeoutMs;
|
|
145
|
+
let worst = null;
|
|
146
|
+
let unknown = 0;
|
|
147
|
+
let sampledAt = started;
|
|
148
|
+
let longestGapMs = 0;
|
|
149
|
+
let phase = "requesting boot";
|
|
150
|
+
const ranks = {
|
|
151
|
+
normal: 0,
|
|
152
|
+
warning: 1,
|
|
153
|
+
critical: 2
|
|
154
|
+
};
|
|
155
|
+
const sample = () => {
|
|
156
|
+
const now = Date.now();
|
|
157
|
+
longestGapMs = Math.max(longestGapMs, now - sampledAt);
|
|
158
|
+
sampledAt = now;
|
|
159
|
+
const pressure = readHostMemoryPressure(exec);
|
|
160
|
+
if (pressure === null) unknown++;
|
|
161
|
+
else if (worst === null || ranks[pressure] > ranks[worst]) worst = pressure;
|
|
162
|
+
return pressure;
|
|
163
|
+
};
|
|
164
|
+
const recovery = "Stop unused slots in workspaces you own with `stim stop --slot <name>`, reduce concurrent builds, and retry after pressure falls. Ask before closing other agents' devices or apps. Repeated reboots under unchanged pressure may stall again.";
|
|
165
|
+
const coverage = () => longestGapMs > 3e4 ? ` Observations were delayed: longest gap ${Math.round(longestGapMs / 1e3)}s. Pressure during gaps is unobserved; blocking CLI work can also delay progress and timeout handling.` : "";
|
|
166
|
+
const report = () => {
|
|
167
|
+
const pressure = sample();
|
|
168
|
+
out(`Simulator ${label} is still booting after ${Math.round((Date.now() - started) / 1e3)}s. Last boot output: ${phase}. Memory pressure: ${pressure ?? "unknown"}; highest observed: ${worst ?? "unknown"}. Boot deadline: ${Math.round(timeoutMs / 1e3)}s.${coverage()}${pressure === "warning" || pressure === "critical" ? ` Boot may be delayed or stalled. ${recovery}` : ""}`);
|
|
169
|
+
};
|
|
170
|
+
const onLine = (line) => {
|
|
171
|
+
phase = line.slice(0, 240);
|
|
172
|
+
};
|
|
173
|
+
sample();
|
|
174
|
+
const monitor = setInterval(report, 15e3);
|
|
175
|
+
monitor.unref();
|
|
147
176
|
try {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
});
|
|
156
|
-
} catch (e) {
|
|
157
|
-
if (!String(e?.message || e).includes("Booted")) throw e;
|
|
158
|
-
}
|
|
159
|
-
for (;;) {
|
|
160
|
-
const remaining = deadline - Date.now();
|
|
161
|
-
if (remaining > 0) try {
|
|
162
|
-
await awaitBootstatus(udid, Math.min(Math.max(remaining, BOOTSTATUS_ATTEMPT_FLOOR_MS), attemptMs));
|
|
163
|
-
break;
|
|
177
|
+
try {
|
|
178
|
+
await awaitBootCommand([
|
|
179
|
+
"simctl",
|
|
180
|
+
"boot",
|
|
181
|
+
udid
|
|
182
|
+
], Math.max(1, deadline - Date.now()), onLine);
|
|
183
|
+
phase = "waiting for boot completion";
|
|
164
184
|
} catch (e) {
|
|
165
|
-
if (e?.
|
|
185
|
+
if (!String(e?.message || e).includes("Booted")) throw e;
|
|
166
186
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
187
|
+
for (;;) {
|
|
188
|
+
const remaining = deadline - Date.now();
|
|
189
|
+
if (remaining > 0) try {
|
|
190
|
+
await awaitBootCommand([
|
|
191
|
+
"simctl",
|
|
192
|
+
"bootstatus",
|
|
193
|
+
udid,
|
|
194
|
+
"-b"
|
|
195
|
+
], Math.min(Math.max(remaining, BOOTSTATUS_ATTEMPT_FLOOR_MS), attemptMs), onLine);
|
|
196
|
+
break;
|
|
197
|
+
} catch (e) {
|
|
198
|
+
if (e?.code !== "ETIMEDOUT") throw e;
|
|
199
|
+
}
|
|
200
|
+
let state;
|
|
201
|
+
try {
|
|
202
|
+
state = listAllIosSims({ timeoutMs: BOOT_STATE_LIST_TIMEOUT_MS }).find((s) => s.udid === udid)?.state ?? null;
|
|
203
|
+
} catch {
|
|
204
|
+
state = void 0;
|
|
205
|
+
}
|
|
206
|
+
const waited = Math.round((timeoutMs - Math.max(0, deadline - Date.now())) / 1e3);
|
|
207
|
+
if (state !== "Booting" && state !== "Booted" && state !== void 0) throw new Error(`Simulator ${udid} reports "${state ?? "missing"}" after ${waited}s of boot wait.`);
|
|
208
|
+
if (deadline - Date.now() <= 0) throw new Error(`Simulator ${udid} did not finish booting within ${Math.round(timeoutMs / 1e3)}s.`);
|
|
172
209
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
210
|
+
} catch (error) {
|
|
211
|
+
sample();
|
|
212
|
+
throw new Error(`${error?.message || error} Last boot output: ${phase}. Highest observed memory pressure: ${worst ?? "unknown"}; unavailable samples: ${unknown}.${coverage()} ${recovery} These observations do not establish an OOM crash.`, { cause: error });
|
|
213
|
+
} finally {
|
|
214
|
+
clearInterval(monitor);
|
|
177
215
|
}
|
|
178
216
|
exec.runFileQuiet("open", ["-a", "Simulator"], {
|
|
179
217
|
timeoutMs: 5e3,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as getExecutor } from "./exec-CyylIdq9.mjs";
|
|
2
|
-
import { r as INSTALL_ERROR } from "./app-install-
|
|
2
|
+
import { r as INSTALL_ERROR } from "./app-install-DIp9riNx.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,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { V as validateDeviceSlot, g as loadConfig, h as isPathPrefix } from "./config-CcefMY2Q.mjs";
|
|
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";
|
|
4
5
|
import { _ as launchErrorPreview } from "./command-output-8wHMEde0.mjs";
|
|
5
|
-
import { i as captureWorkspaceCrashes, t as errorDiagnostics } from "./error-diagnostics-
|
|
6
|
-
import { a as followLogs, i as fileSizes, n as buildCriteria, o as
|
|
6
|
+
import { i as captureWorkspaceCrashes, t as errorDiagnostics } from "./error-diagnostics-DwhNBFDU.mjs";
|
|
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
|
+
import { realpathSync } from "node:fs";
|
|
9
|
+
import { relative, sep } from "node:path";
|
|
7
10
|
import chalk from "chalk";
|
|
8
11
|
//#region src/commands/logs.ts
|
|
9
12
|
const LEVEL_WIDTH = 5;
|
|
@@ -60,6 +63,28 @@ function validateLevel(level) {
|
|
|
60
63
|
return { error: `Invalid --level value ${JSON.stringify(level)}. Use one of: ${LEVELS.join(", ")}.` };
|
|
61
64
|
}
|
|
62
65
|
const ERRORS_PRINT_CAP = 20;
|
|
66
|
+
function nearestRegisteredLogsProject(root, candidates) {
|
|
67
|
+
return candidates.filter((candidate) => candidate !== root && isPathPrefix(root, candidate)).toSorted((a, b) => {
|
|
68
|
+
const depth = (path) => relative(root, path).split(sep).length;
|
|
69
|
+
return depth(a) - depth(b) || a.length - b.length || a.localeCompare(b);
|
|
70
|
+
})[0] ?? null;
|
|
71
|
+
}
|
|
72
|
+
function registeredDescendantWithLogs(root) {
|
|
73
|
+
const candidates = Object.keys(loadConfig()?.projects ?? {}).flatMap((path) => {
|
|
74
|
+
try {
|
|
75
|
+
const canonical = realpathSync(path);
|
|
76
|
+
return logFiles(workspaceLogsDir(canonical)).length > 0 ? [canonical] : [];
|
|
77
|
+
} catch {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
return nearestRegisteredLogsProject(root, [...new Set(candidates)]);
|
|
82
|
+
}
|
|
83
|
+
function requireLogsWorkspace(root, dir) {
|
|
84
|
+
if (logFiles(dir).length > 0) return;
|
|
85
|
+
const descendant = registeredDescendantWithLogs(root);
|
|
86
|
+
fail(`STIM_NO_PROJECT: No Stim workspace has run in ${root}.${descendant ? ` The nearest registered app with logs is ${descendant}; run this command from there.` : " Run `stim start`, `stim ios`, or `stim android` in the app first."}`);
|
|
87
|
+
}
|
|
63
88
|
const LEVEL_COLOURS = {
|
|
64
89
|
debug: chalk.dim,
|
|
65
90
|
info: chalk.reset,
|
|
@@ -68,7 +93,7 @@ const LEVEL_COLOURS = {
|
|
|
68
93
|
fatal: chalk.bgRed
|
|
69
94
|
};
|
|
70
95
|
function logsCommand(program) {
|
|
71
|
-
program.command("logs").description("Query this workspace's merged NDJSON log timeline (bundler, client, device, build). Prints and exits; nothing matching is a successful, empty result. Use --follow to stream.").option("--source <s...>", "Only these sources: metro, client, device, build, or all").option("--level <l>", `Minimum level: ${LEVELS.join(", ")}`).option("--since <d>", "Only records newer than this, e.g. 30s, 5m, 2h").option("--grep <re>", "Only records whose message matches this regular expression").option("--tail <n>", "Only the last n matching records").option("--errors", "Errors and fatals since the last marker, from metro, client and build, plus confirmed native app-crash reports. Add --source device or --source all for general device errors.").option("--follow", "Keep streaming new records until interrupted").option("--json", "Emit the raw records, one per line (valid NDJSON; zero matches is zero bytes, exit 0)").action(async (opts) => {
|
|
96
|
+
program.command("logs").description("Query this workspace's merged NDJSON log timeline (bundler, client, device, build). Prints and exits; an existing timeline with nothing matching is a successful, empty result. Use --follow to stream.").option("--slot <name>", "Only records attributed to this device slot", validateDeviceSlot).option("--source <s...>", "Only these sources: metro, client, device, build, or all").option("--level <l>", `Minimum level: ${LEVELS.join(", ")}`).option("--since <d>", "Only records newer than this, e.g. 30s, 5m, 2h").option("--grep <re>", "Only records whose message matches this regular expression").option("--tail <n>", "Only the last n matching records").option("--errors", "Errors and fatals since the last marker, from metro, client and build, plus confirmed native app-crash reports. Add --source device or --source all for general device errors.").option("--follow", "Keep streaming new records until interrupted").option("--json", "Emit the raw records, one per line (valid NDJSON; zero matches is zero bytes, exit 0)").action(async (opts) => {
|
|
72
97
|
const root = findProjectRoot(process.cwd());
|
|
73
98
|
if (!root) {
|
|
74
99
|
console.error(chalk.red("Not in a React Native project (no package.json found)."));
|
|
@@ -101,7 +126,9 @@ function logsCommand(program) {
|
|
|
101
126
|
const compiled = compileGrep(opts.grep);
|
|
102
127
|
if (compiled.error) fail(compiled.error);
|
|
103
128
|
}
|
|
129
|
+
requireLogsWorkspace(root, dir);
|
|
104
130
|
const query = {
|
|
131
|
+
slot: opts.slot,
|
|
105
132
|
dir,
|
|
106
133
|
sources,
|
|
107
134
|
minLevel,
|
|
@@ -148,6 +175,7 @@ function logsCommand(program) {
|
|
|
148
175
|
return;
|
|
149
176
|
}
|
|
150
177
|
const criteria = buildCriteria({
|
|
178
|
+
slot: opts.slot,
|
|
151
179
|
sources,
|
|
152
180
|
minLevel,
|
|
153
181
|
since: opts.since,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as parseNdjsonLine, i as levelRank, o as parseNdjsonText } from "./ndjson-
|
|
1
|
+
import { a as parseNdjsonLine, i as levelRank, o as parseNdjsonText } from "./ndjson-BZwI1NBv.mjs";
|
|
2
2
|
import { closeSync, openSync, readFileSync, readSync, readdirSync, statSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { StringDecoder } from "string_decoder";
|
|
@@ -47,6 +47,7 @@ function markerWindow(records) {
|
|
|
47
47
|
}
|
|
48
48
|
function recordMatches(record, criteria = {}) {
|
|
49
49
|
if (!record) return false;
|
|
50
|
+
if (criteria.slot !== void 0 && (record.slot ?? "default") !== criteria.slot) return false;
|
|
50
51
|
const { sources, minLevel, grep, sinceTs, errorsOnly, markerTs, bundleMarkerTs } = criteria;
|
|
51
52
|
if (sources && sources.length > 0 && !sources.includes(record.src) && !(criteria.includeNativeCrashes && record.src === "device" && record.event === "native_crash")) return false;
|
|
52
53
|
if (minLevel && levelRank(record.level) < levelRank(minLevel)) return false;
|
|
@@ -71,8 +72,9 @@ function recordMatches(record, criteria = {}) {
|
|
|
71
72
|
}
|
|
72
73
|
return true;
|
|
73
74
|
}
|
|
74
|
-
function buildCriteria({ sources, minLevel, since, grep, errorsOnly, markerTs, bundleMarkerTs, now } = {}) {
|
|
75
|
+
function buildCriteria({ slot, sources, minLevel, since, grep, errorsOnly, markerTs, bundleMarkerTs, now } = {}) {
|
|
75
76
|
const criteria = {
|
|
77
|
+
...slot === void 0 ? {} : { slot },
|
|
76
78
|
errorsOnly: Boolean(errorsOnly),
|
|
77
79
|
includeNativeCrashes: Boolean(errorsOnly && !sources?.length)
|
|
78
80
|
};
|
|
@@ -160,14 +162,25 @@ function includeBareErrorContext(all, matched, launchTs, sinceTs) {
|
|
|
160
162
|
}
|
|
161
163
|
return sortByTs([...matched, ...renderedContext]);
|
|
162
164
|
}
|
|
163
|
-
function
|
|
164
|
-
const
|
|
165
|
+
function launchMarkersBySlot(records) {
|
|
166
|
+
const markers = /* @__PURE__ */ new Map();
|
|
167
|
+
for (const record of records) {
|
|
168
|
+
if (record.marker !== true || record.src === "metro") continue;
|
|
169
|
+
const ts = tsOf(record);
|
|
170
|
+
const slot = record.slot ?? "default";
|
|
171
|
+
if (ts !== null && ts > (markers.get(slot) ?? -Infinity)) markers.set(slot, ts);
|
|
172
|
+
}
|
|
173
|
+
return markers;
|
|
174
|
+
}
|
|
175
|
+
function queryLogs({ slot, dir, sources, minLevel, since, grep, tail, errorsOnly, errorContext, now } = {}) {
|
|
176
|
+
const all = readLogRecords(dir).filter((record) => slot === void 0 || (record.slot ?? "default") === slot);
|
|
165
177
|
if (all.length === 0) return [];
|
|
166
178
|
const { launchTs, bundleTs } = errorsOnly ? markerWindow(all) : {
|
|
167
179
|
launchTs: null,
|
|
168
180
|
bundleTs: null
|
|
169
181
|
};
|
|
170
182
|
const criteria = buildCriteria({
|
|
183
|
+
slot,
|
|
171
184
|
sources,
|
|
172
185
|
minLevel,
|
|
173
186
|
since,
|
|
@@ -177,7 +190,11 @@ function queryLogs({ dir, sources, minLevel, since, grep, tail, errorsOnly, erro
|
|
|
177
190
|
markerTs: launchTs === null ? void 0 : launchTs,
|
|
178
191
|
bundleMarkerTs: bundleTs === null ? void 0 : bundleTs
|
|
179
192
|
});
|
|
180
|
-
|
|
193
|
+
const slotMarkers = launchMarkersBySlot(all);
|
|
194
|
+
let matched = all.filter((record) => recordMatches(record, {
|
|
195
|
+
...criteria,
|
|
196
|
+
markerTs: record.src === "metro" || record.src === "client" ? criteria.markerTs : slotMarkers.get(record.slot ?? "default")
|
|
197
|
+
}));
|
|
181
198
|
if (typeof tail === "number" && tail >= 0 && matched.length > tail) matched = matched.slice(matched.length - tail);
|
|
182
199
|
return errorContext ? includeBareErrorContext(all, attachExpoErrorContext(all, matched), launchTs, criteria.sinceTs) : matched;
|
|
183
200
|
}
|
|
@@ -270,4 +287,4 @@ function sortByTs(records) {
|
|
|
270
287
|
});
|
|
271
288
|
}
|
|
272
289
|
//#endregion
|
|
273
|
-
export { followLogs as a,
|
|
290
|
+
export { followLogs as a, queryLogs as c, fileSizes as i, readLogRecords as l, buildCriteria as n, logFiles as o, compileGrep as r, parseSince as s, attachExpoErrorContext as t };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as getExecutor } from "./exec-CyylIdq9.mjs";
|
|
2
|
-
import { E as inspectProcessIdentity, M as isOnMountedVolume, N as listMountedVolumes, g as loadConfig, n as allMetroPorts, r as claimMetroPort, v as removeProject } from "./config-
|
|
3
|
-
import { p as readWorkspaceState } from "./state-
|
|
2
|
+
import { E as inspectProcessIdentity, M as isOnMountedVolume, N as listMountedVolumes, g as loadConfig, n as allMetroPorts, r as claimMetroPort, v as removeProject } from "./config-CcefMY2Q.mjs";
|
|
3
|
+
import { p as readWorkspaceState } from "./state-B3XA2wp2.mjs";
|
|
4
4
|
import { existsSync, readlinkSync, realpathSync } from "fs";
|
|
5
5
|
import { sep } from "path";
|
|
6
6
|
import { request } from "http";
|
|
@@ -49,7 +49,7 @@ function formatNdjsonLine(record) {
|
|
|
49
49
|
return null;
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
function createNdjsonWriter(file, { truncate = false } = {}) {
|
|
52
|
+
function createNdjsonWriter(file, { truncate = false, fields = {} } = {}) {
|
|
53
53
|
let fd = null;
|
|
54
54
|
let freshFile = truncate;
|
|
55
55
|
let written = 0;
|
|
@@ -66,7 +66,10 @@ function createNdjsonWriter(file, { truncate = false } = {}) {
|
|
|
66
66
|
dropped += 1;
|
|
67
67
|
return false;
|
|
68
68
|
}
|
|
69
|
-
const line = formatNdjsonLine(
|
|
69
|
+
const line = formatNdjsonLine({
|
|
70
|
+
...stamp(record),
|
|
71
|
+
...fields
|
|
72
|
+
});
|
|
70
73
|
if (line === null) {
|
|
71
74
|
dropped += 1;
|
|
72
75
|
lastError = /* @__PURE__ */ new TypeError("record could not be serialized to JSON");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { D as sameProcessRecord, E as inspectProcessIdentity } from "./config-
|
|
2
|
-
import { _ as writeWorkspaceState, m as withWorkspaceStateLock, p as readWorkspaceState } from "./state-
|
|
3
|
-
import { r as pidExists } from "./metro-
|
|
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";
|
|
4
4
|
import { realpathSync } from "node:fs";
|
|
5
5
|
//#region src/collector/state.ts
|
|
6
6
|
function collectorRoot(root) {
|
|
@@ -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-
|
|
2
|
+
import { E as inspectProcessIdentity, T as captureProcessToken, u as getConfigDir } from "./config-CcefMY2Q.mjs";
|
|
3
3
|
import { t as createLineReader } from "./process-output-C7IlIzd7.mjs";
|
|
4
|
-
import { r as pidExists } from "./metro-
|
|
5
|
-
import { t as withWorkspaceProcessLock } from "./workspace-process-lock-
|
|
4
|
+
import { r as pidExists } from "./metro-CORqXyTL.mjs";
|
|
5
|
+
import { t as withWorkspaceProcessLock } from "./workspace-process-lock-IBj1Towr.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";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { E as inspectProcessIdentity, w as captureProcessIdentity } from "./config-
|
|
1
|
+
import { E as inspectProcessIdentity, w as captureProcessIdentity } from "./config-CcefMY2Q.mjs";
|
|
2
2
|
import { d as quotedPath } from "./command-output-8wHMEde0.mjs";
|
|
3
3
|
import { lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
@@ -6,12 +6,6 @@ import { dirname, join } from "node:path";
|
|
|
6
6
|
//#region src/ownership-claim.ts
|
|
7
7
|
const CLAIM_REFUSED = "STIM_CLAIM_REFUSED";
|
|
8
8
|
const CLAIM_UNAVAILABLE = "STIM_CLAIM_UNAVAILABLE";
|
|
9
|
-
/**
|
|
10
|
-
* The refusal reason for a claim store whose own path is occupied by a file. It names a filesystem
|
|
11
|
-
* state that predates any claim, so a caller that only reads can tell it apart from an unresolvable
|
|
12
|
-
* claim record and fall back to what it did before claims existed.
|
|
13
|
-
*/
|
|
14
|
-
const CLAIM_PATH_NOT_A_DIRECTORY = "the claim path is a file, not a claim directory";
|
|
15
9
|
const EXCLUSIVE_DIR = "exclusive";
|
|
16
10
|
const SHARED_DIR = "shared";
|
|
17
11
|
const CLAIM_SUFFIX = ".claim";
|
|
@@ -23,9 +17,9 @@ var ClaimRefusedError = class extends Error {
|
|
|
23
17
|
claimPath;
|
|
24
18
|
reason;
|
|
25
19
|
removeCommand;
|
|
26
|
-
constructor({ claimPath, root, reason, label }) {
|
|
27
|
-
const removeCommand = claimRemoveCommand(claimPath.startsWith(root) ? claimPath : root);
|
|
28
|
-
super(`Stim cannot tell whether the ${label} claim at ${claimPath} is still held: ${reason}. It will not remove a claim it cannot prove is dead, and it will not wait on one either. If nothing is using it, remove the claim and run the command again:\n ${removeCommand}`);
|
|
20
|
+
constructor({ claimPath, root, reason, label, blockingFile = false }) {
|
|
21
|
+
const removeCommand = blockingFile ? `mv -i ${quotedPath(claimPath)} ${quotedPath(`${claimPath}.stim-backup`)}` : claimRemoveCommand(claimPath.startsWith(root) ? claimPath : root);
|
|
22
|
+
super(blockingFile ? `The ${label} claim store is blocked by a non-directory path at ${claimPath}. Inspect it before moving it aside; its contents may be unrelated to Stim. Preserve it with the command below (an existing backup prompts before overwriting), then run the command again:\n ${removeCommand}` : `Stim cannot tell whether the ${label} claim at ${claimPath} is still held: ${reason}. It will not remove a claim it cannot prove is dead, and it will not wait on one either. If nothing is using it, remove the claim and run the command again:\n ${removeCommand}`);
|
|
29
23
|
this.claimPath = claimPath;
|
|
30
24
|
this.reason = reason;
|
|
31
25
|
this.removeCommand = removeCommand;
|
|
@@ -34,9 +28,11 @@ var ClaimRefusedError = class extends Error {
|
|
|
34
28
|
var ClaimUnavailableError = class extends Error {
|
|
35
29
|
code = CLAIM_UNAVAILABLE;
|
|
36
30
|
reason;
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
remedy;
|
|
32
|
+
constructor(reason, { claimPath } = {}) {
|
|
33
|
+
super(claimPath ? `Stim could not write the ownership claim at ${claimPath}: ${reason}.` : `Stim could not record a process identity, so it cannot take an ownership claim: ${reason}.`);
|
|
39
34
|
this.reason = reason;
|
|
35
|
+
this.remedy = claimPath ? `Restore write access to the existing claim store at ${quotedPath(claimPath)} (check parent permissions, symlink targets, mount access and sandbox rules)` : "Reinstall Stim so the unique-pid native module for this platform is present";
|
|
40
36
|
}
|
|
41
37
|
};
|
|
42
38
|
/**
|
|
@@ -53,22 +49,18 @@ function isClaimRefusal(err) {
|
|
|
53
49
|
function isClaimUnavailable(err) {
|
|
54
50
|
return err?.code === CLAIM_UNAVAILABLE;
|
|
55
51
|
}
|
|
56
|
-
/**
|
|
57
|
-
* The refusal a command reports for either error the claim primitive raises, and null for anything else.
|
|
58
|
-
* Both are refusals: a claim Stim cannot resolve and a process identity it cannot record are the two
|
|
59
|
-
* states in which it holds no claim, and running the operation a claim serializes without one gives a
|
|
60
|
-
* competing run no protection at all.
|
|
61
|
-
*/
|
|
52
|
+
/** Convert an unresolved claim, unavailable identity or inaccessible warm claim store into command recovery guidance. */
|
|
62
53
|
function claimFailure(err, retryCommand) {
|
|
54
|
+
const retry = retryCommand === null ? "retry the same Stim command" : `run \`${retryCommand}\` again`;
|
|
63
55
|
if (isClaimRefusal(err)) return {
|
|
64
56
|
code: CLAIM_REFUSED,
|
|
65
57
|
message: err.message,
|
|
66
|
-
remedy: `Run \`${err.removeCommand}\`, then
|
|
58
|
+
remedy: `Run \`${err.removeCommand}\`, then ${retry}.`
|
|
67
59
|
};
|
|
68
60
|
if (isClaimUnavailable(err)) return {
|
|
69
61
|
code: CLAIM_UNAVAILABLE,
|
|
70
62
|
message: err.message,
|
|
71
|
-
remedy:
|
|
63
|
+
remedy: `${err.remedy}, then ${retry}.`
|
|
72
64
|
};
|
|
73
65
|
return null;
|
|
74
66
|
}
|
|
@@ -173,6 +165,29 @@ function refuse(root, claimPath, label, reason) {
|
|
|
173
165
|
reason
|
|
174
166
|
});
|
|
175
167
|
}
|
|
168
|
+
function refuseNonDirectory(root, path, label) {
|
|
169
|
+
let blocking = null;
|
|
170
|
+
for (let level = path;; level = dirname(level)) {
|
|
171
|
+
try {
|
|
172
|
+
if (!statSync(level).isDirectory()) {
|
|
173
|
+
blocking = level;
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
} catch (error) {
|
|
177
|
+
const code = error?.code;
|
|
178
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") break;
|
|
179
|
+
}
|
|
180
|
+
if (dirname(level) === level) break;
|
|
181
|
+
}
|
|
182
|
+
if (blocking === null) throw new ClaimUnavailableError("the non-directory path could not be identified; the claim store may have changed", { claimPath: root });
|
|
183
|
+
throw new ClaimRefusedError({
|
|
184
|
+
root,
|
|
185
|
+
claimPath: blocking,
|
|
186
|
+
label,
|
|
187
|
+
reason: "the claim path is a file, not a claim directory",
|
|
188
|
+
blockingFile: true
|
|
189
|
+
});
|
|
190
|
+
}
|
|
176
191
|
function names(dir) {
|
|
177
192
|
try {
|
|
178
193
|
return readdirSync(dir).toSorted();
|
|
@@ -404,7 +419,7 @@ function publishExclusive(root, payload, claimId, label) {
|
|
|
404
419
|
});
|
|
405
420
|
const code = err?.code;
|
|
406
421
|
if (contended(err)) return null;
|
|
407
|
-
if (code === "ENOTDIR")
|
|
422
|
+
if (code === "ENOTDIR") refuseNonDirectory(root, target, label);
|
|
408
423
|
throw err;
|
|
409
424
|
}
|
|
410
425
|
}
|
|
@@ -438,7 +453,7 @@ function tryAcquireClaim({ root, mode, details = {}, label = "ownership" }) {
|
|
|
438
453
|
createClaimDir(root);
|
|
439
454
|
} catch (err) {
|
|
440
455
|
const code = err?.code;
|
|
441
|
-
if (code === "EEXIST" || code === "ENOTDIR")
|
|
456
|
+
if (code === "EEXIST" || code === "ENOTDIR") refuseNonDirectory(root, root, label);
|
|
442
457
|
if (contended(err)) continue;
|
|
443
458
|
throw err;
|
|
444
459
|
}
|
|
@@ -566,4 +581,4 @@ function writeChild(handle, body) {
|
|
|
566
581
|
}
|
|
567
582
|
}
|
|
568
583
|
//#endregion
|
|
569
|
-
export { clearClaimChild as a,
|
|
584
|
+
export { clearClaimChild as a, markClaimChildPending as c, releaseClaim as d, setClaimChild as f, claimRemoveCommand as i, processGroupAlive as l, tryAcquireClaim as m, ClaimUnavailableError as n, clearFreeClaimSet as o, settleClaim as p, claimFailure as r, isClaimRefusal as s, ClaimRefusedError as t, readClaimSet as u };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { c as findEnclosingWorktreeRoot, f as getProject } from "./config-
|
|
2
|
-
import { g as repoRoot } from "./worktree-
|
|
1
|
+
import { c as findEnclosingWorktreeRoot, f as getProject } from "./config-CcefMY2Q.mjs";
|
|
2
|
+
import { g as repoRoot } from "./worktree-Eq6TLH8N.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";
|