agent-react-devtools 0.0.0 → 0.1.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/CHANGELOG.md +34 -0
- package/dist/cli.js +584 -0
- package/dist/cli.js.map +1 -0
- package/dist/daemon.js +1091 -0
- package/dist/daemon.js.map +1 -0
- package/package.json +35 -1
- package/src/__tests__/cli-parser.test.ts +76 -0
- package/src/__tests__/component-tree.test.ts +229 -0
- package/src/__tests__/formatters.test.ts +189 -0
- package/src/__tests__/profiler.test.ts +264 -0
- package/src/cli.ts +315 -0
- package/src/component-tree.ts +495 -0
- package/src/daemon-client.ts +144 -0
- package/src/daemon.ts +275 -0
- package/src/devtools-bridge.ts +391 -0
- package/src/formatters.ts +270 -0
- package/src/profiler.ts +356 -0
- package/src/types.ts +126 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +17 -0
- package/vitest.config.ts +7 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# agent-react-devtools
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- d1e02f9: **Daemon, DevTools bridge, and component tree**
|
|
8
|
+
|
|
9
|
+
**Daemon** — Persistent background process with IPC server (Unix socket) that manages connections and dispatches commands.
|
|
10
|
+
|
|
11
|
+
**DevTools Bridge** — WebSocket server implementing the React DevTools "Wall" protocol. Connects to running React apps via `react-devtools-core`.
|
|
12
|
+
|
|
13
|
+
**Component Tree** — Parse and inspect the full React component hierarchy:
|
|
14
|
+
|
|
15
|
+
- View component tree with types, keys, and parent/child relationships
|
|
16
|
+
- Inspect props, state, and hooks of any component
|
|
17
|
+
- Search components by display name (fuzzy or exact match)
|
|
18
|
+
- Count components by type
|
|
19
|
+
|
|
20
|
+
**CLI** — Command-line interface with commands: `start`, `stop`, `status`, `tree`, `find`, `count`, `get component`.
|
|
21
|
+
|
|
22
|
+
**Formatting** — Token-efficient output designed for LLM consumption.
|
|
23
|
+
|
|
24
|
+
- 626a21a: **Profiler**
|
|
25
|
+
|
|
26
|
+
Start and stop profiling sessions to capture render performance data from connected React apps.
|
|
27
|
+
|
|
28
|
+
- **Render reports** — Per-component render duration and count
|
|
29
|
+
- **Slowest components** — Ranked by self render time
|
|
30
|
+
- **Most re-rendered** — Ranked by render count
|
|
31
|
+
- **Commit timeline** — Chronological view of React commits with durations
|
|
32
|
+
- **Commit details** — Per-component breakdown for a specific commit, sorted by self time
|
|
33
|
+
|
|
34
|
+
CLI commands: `profile start`, `profile stop`, `profile report`, `profile slow`, `profile rerenders`, `profile timeline`, `profile commit`.
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/daemon-client.ts
|
|
4
|
+
import net from "net";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { spawn } from "child_process";
|
|
8
|
+
var DEFAULT_STATE_DIR = path.join(
|
|
9
|
+
process.env.HOME || process.env.USERPROFILE || "/tmp",
|
|
10
|
+
".agent-react-devtools"
|
|
11
|
+
);
|
|
12
|
+
var stateDir = DEFAULT_STATE_DIR;
|
|
13
|
+
function setStateDir(dir) {
|
|
14
|
+
stateDir = dir;
|
|
15
|
+
}
|
|
16
|
+
function getDaemonInfoPath() {
|
|
17
|
+
return path.join(stateDir, "daemon.json");
|
|
18
|
+
}
|
|
19
|
+
function getSocketPath() {
|
|
20
|
+
return path.join(stateDir, "daemon.sock");
|
|
21
|
+
}
|
|
22
|
+
function readDaemonInfo() {
|
|
23
|
+
try {
|
|
24
|
+
const raw = fs.readFileSync(getDaemonInfoPath(), "utf-8");
|
|
25
|
+
return JSON.parse(raw);
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function isDaemonAlive(info) {
|
|
31
|
+
try {
|
|
32
|
+
process.kill(info.pid, 0);
|
|
33
|
+
return true;
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function ensureDaemon(port) {
|
|
39
|
+
const info = readDaemonInfo();
|
|
40
|
+
if (info && isDaemonAlive(info)) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
fs.unlinkSync(getDaemonInfoPath());
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
fs.unlinkSync(getSocketPath());
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
const daemonScript = path.join(
|
|
52
|
+
path.dirname(new URL(import.meta.url).pathname),
|
|
53
|
+
"daemon.js"
|
|
54
|
+
);
|
|
55
|
+
const args = [];
|
|
56
|
+
if (port) args.push(`--port=${port}`);
|
|
57
|
+
if (stateDir !== DEFAULT_STATE_DIR) args.push(`--state-dir=${stateDir}`);
|
|
58
|
+
const child = spawn(process.execPath, [daemonScript, ...args], {
|
|
59
|
+
detached: true,
|
|
60
|
+
stdio: "ignore"
|
|
61
|
+
});
|
|
62
|
+
child.unref();
|
|
63
|
+
const deadline = Date.now() + 5e3;
|
|
64
|
+
while (Date.now() < deadline) {
|
|
65
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
66
|
+
try {
|
|
67
|
+
await sendCommand({ type: "ping" });
|
|
68
|
+
return;
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
throw new Error("Daemon failed to start within 5 seconds");
|
|
73
|
+
}
|
|
74
|
+
function stopDaemon() {
|
|
75
|
+
const info = readDaemonInfo();
|
|
76
|
+
if (!info) return false;
|
|
77
|
+
try {
|
|
78
|
+
process.kill(info.pid, "SIGTERM");
|
|
79
|
+
try {
|
|
80
|
+
fs.unlinkSync(getDaemonInfoPath());
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
return true;
|
|
84
|
+
} catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function sendCommand(cmd) {
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
const socketPath = getSocketPath();
|
|
91
|
+
const conn = net.createConnection(socketPath, () => {
|
|
92
|
+
conn.write(JSON.stringify(cmd) + "\n");
|
|
93
|
+
});
|
|
94
|
+
let buffer = "";
|
|
95
|
+
conn.on("data", (chunk) => {
|
|
96
|
+
buffer += chunk.toString();
|
|
97
|
+
const newlineIdx = buffer.indexOf("\n");
|
|
98
|
+
if (newlineIdx !== -1) {
|
|
99
|
+
const line = buffer.slice(0, newlineIdx);
|
|
100
|
+
conn.end();
|
|
101
|
+
try {
|
|
102
|
+
resolve(JSON.parse(line));
|
|
103
|
+
} catch {
|
|
104
|
+
reject(new Error("Invalid response from daemon"));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
conn.on("error", (err) => {
|
|
109
|
+
reject(new Error(`Cannot connect to daemon: ${err.message}`));
|
|
110
|
+
});
|
|
111
|
+
conn.setTimeout(3e4, () => {
|
|
112
|
+
conn.destroy();
|
|
113
|
+
reject(new Error("Command timed out"));
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/formatters.ts
|
|
119
|
+
var TYPE_ABBREV = {
|
|
120
|
+
function: "fn",
|
|
121
|
+
class: "cls",
|
|
122
|
+
host: "host",
|
|
123
|
+
memo: "memo",
|
|
124
|
+
forwardRef: "fRef",
|
|
125
|
+
profiler: "prof",
|
|
126
|
+
suspense: "susp",
|
|
127
|
+
context: "ctx",
|
|
128
|
+
other: "?"
|
|
129
|
+
};
|
|
130
|
+
function typeTag(type) {
|
|
131
|
+
return TYPE_ABBREV[type] || type;
|
|
132
|
+
}
|
|
133
|
+
var PIPE = "\u2502 ";
|
|
134
|
+
var TEE = "\u251C\u2500 ";
|
|
135
|
+
var ELBOW = "\u2514\u2500 ";
|
|
136
|
+
var SPACE = " ";
|
|
137
|
+
function formatTree(nodes) {
|
|
138
|
+
if (nodes.length === 0) return "No components (is a React app connected?)";
|
|
139
|
+
const childrenMap = /* @__PURE__ */ new Map();
|
|
140
|
+
for (const node of nodes) {
|
|
141
|
+
const parentId = node.parentId;
|
|
142
|
+
let siblings = childrenMap.get(parentId);
|
|
143
|
+
if (!siblings) {
|
|
144
|
+
siblings = [];
|
|
145
|
+
childrenMap.set(parentId, siblings);
|
|
146
|
+
}
|
|
147
|
+
siblings.push(node);
|
|
148
|
+
}
|
|
149
|
+
const lines = [];
|
|
150
|
+
function walk(nodeId, prefix, isLast, isRoot) {
|
|
151
|
+
const node = nodes.find((n) => n.id === nodeId);
|
|
152
|
+
if (!node) return;
|
|
153
|
+
const connector = isRoot ? "" : isLast ? ELBOW : TEE;
|
|
154
|
+
let line = `${node.label} [${typeTag(node.type)}] "${node.displayName}"`;
|
|
155
|
+
if (node.key) line += ` key="${node.key}"`;
|
|
156
|
+
lines.push(`${prefix}${connector}${line}`);
|
|
157
|
+
const children = childrenMap.get(node.id) || [];
|
|
158
|
+
const childPrefix = isRoot ? "" : prefix + (isLast ? SPACE : PIPE);
|
|
159
|
+
for (let i = 0; i < children.length; i++) {
|
|
160
|
+
walk(children[i].id, childPrefix, i === children.length - 1, false);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const roots = childrenMap.get(null) || [];
|
|
164
|
+
for (let i = 0; i < roots.length; i++) {
|
|
165
|
+
walk(roots[i].id, "", i === roots.length - 1, true);
|
|
166
|
+
}
|
|
167
|
+
return lines.join("\n");
|
|
168
|
+
}
|
|
169
|
+
function formatComponent(element, label) {
|
|
170
|
+
const lines = [];
|
|
171
|
+
const ref = label || `#${element.id}`;
|
|
172
|
+
let header = `${ref} [${typeTag(element.type)}] "${element.displayName}"`;
|
|
173
|
+
if (element.key) header += ` key="${element.key}"`;
|
|
174
|
+
lines.push(header);
|
|
175
|
+
if (element.props && Object.keys(element.props).length > 0) {
|
|
176
|
+
lines.push("props:");
|
|
177
|
+
for (const [key, value] of Object.entries(element.props)) {
|
|
178
|
+
lines.push(` ${key}: ${formatCompactValue(value) ?? "undefined"}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (element.state && Object.keys(element.state).length > 0) {
|
|
182
|
+
lines.push("state:");
|
|
183
|
+
for (const [key, value] of Object.entries(element.state)) {
|
|
184
|
+
lines.push(` ${key}: ${formatCompactValue(value) ?? "undefined"}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (element.hooks && element.hooks.length > 0) {
|
|
188
|
+
lines.push("hooks:");
|
|
189
|
+
for (const h of element.hooks) {
|
|
190
|
+
const val = formatCompactValue(h.value);
|
|
191
|
+
lines.push(val !== void 0 ? ` ${h.name}: ${val}` : ` ${h.name}`);
|
|
192
|
+
if (h.subHooks && h.subHooks.length > 0) {
|
|
193
|
+
for (const sh of h.subHooks) {
|
|
194
|
+
const sval = formatCompactValue(sh.value);
|
|
195
|
+
lines.push(sval !== void 0 ? ` ${sh.name}: ${sval}` : ` ${sh.name}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return lines.join("\n");
|
|
201
|
+
}
|
|
202
|
+
function formatSearchResults(results) {
|
|
203
|
+
if (results.length === 0) return "No components found";
|
|
204
|
+
return results.map((n) => {
|
|
205
|
+
let line = `${n.label} [${typeTag(n.type)}] "${n.displayName}"`;
|
|
206
|
+
if (n.key) line += ` key="${n.key}"`;
|
|
207
|
+
return line;
|
|
208
|
+
}).join("\n");
|
|
209
|
+
}
|
|
210
|
+
function formatCount(counts) {
|
|
211
|
+
const total = Object.values(counts).reduce((a, b) => a + b, 0);
|
|
212
|
+
const parts = Object.entries(counts).sort((a, b) => b[1] - a[1]).map(([type, count]) => `${typeTag(type)}:${count}`).join(" ");
|
|
213
|
+
return `${total} components (${parts})`;
|
|
214
|
+
}
|
|
215
|
+
function formatStatus(status) {
|
|
216
|
+
const lines = [];
|
|
217
|
+
lines.push(`Daemon: running (port ${status.port})`);
|
|
218
|
+
lines.push(
|
|
219
|
+
`Apps: ${status.connectedApps} connected, ${status.componentCount} components`
|
|
220
|
+
);
|
|
221
|
+
if (status.profilingActive) {
|
|
222
|
+
lines.push("Profiling: active");
|
|
223
|
+
}
|
|
224
|
+
const upSec = Math.round(status.uptime / 1e3);
|
|
225
|
+
lines.push(`Uptime: ${upSec}s`);
|
|
226
|
+
return lines.join("\n");
|
|
227
|
+
}
|
|
228
|
+
function formatProfileSummary(summary) {
|
|
229
|
+
const lines = [];
|
|
230
|
+
const durSec = (summary.duration / 1e3).toFixed(1);
|
|
231
|
+
lines.push(
|
|
232
|
+
`Profile "${summary.name}" (${durSec}s, ${summary.commitCount} commits)`
|
|
233
|
+
);
|
|
234
|
+
if (summary.componentRenderCounts.length > 0) {
|
|
235
|
+
lines.push("");
|
|
236
|
+
lines.push("Top renders:");
|
|
237
|
+
for (const c of summary.componentRenderCounts.slice(0, 10)) {
|
|
238
|
+
const name = c.displayName || `#${c.id}`;
|
|
239
|
+
lines.push(` ${name} ${c.count} renders`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return lines.join("\n");
|
|
243
|
+
}
|
|
244
|
+
function formatProfileReport(report, label) {
|
|
245
|
+
const lines = [];
|
|
246
|
+
const ref = label || `#${report.id}`;
|
|
247
|
+
lines.push(`${ref} "${report.displayName}"`);
|
|
248
|
+
lines.push(
|
|
249
|
+
`renders:${report.renderCount} avg:${report.avgDuration.toFixed(1)}ms max:${report.maxDuration.toFixed(1)}ms total:${report.totalDuration.toFixed(1)}ms`
|
|
250
|
+
);
|
|
251
|
+
if (report.causes.length > 0) {
|
|
252
|
+
lines.push(`causes: ${report.causes.join(", ")}`);
|
|
253
|
+
}
|
|
254
|
+
return lines.join("\n");
|
|
255
|
+
}
|
|
256
|
+
function formatSlowest(reports) {
|
|
257
|
+
if (reports.length === 0) return "No profiling data";
|
|
258
|
+
const lines = ["Slowest (by avg render time):"];
|
|
259
|
+
for (const r of reports) {
|
|
260
|
+
const cause = r.causes[0] || "?";
|
|
261
|
+
lines.push(
|
|
262
|
+
` ${pad(r.displayName, 20)} avg:${r.avgDuration.toFixed(1)}ms max:${r.maxDuration.toFixed(1)}ms renders:${r.renderCount} cause:${cause}`
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
return lines.join("\n");
|
|
266
|
+
}
|
|
267
|
+
function formatRerenders(reports) {
|
|
268
|
+
if (reports.length === 0) return "No profiling data";
|
|
269
|
+
const lines = ["Most re-renders:"];
|
|
270
|
+
for (const r of reports) {
|
|
271
|
+
const cause = r.causes[0] || "?";
|
|
272
|
+
lines.push(
|
|
273
|
+
` ${pad(r.displayName, 20)} ${r.renderCount} renders \u2014 ${cause}`
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
return lines.join("\n");
|
|
277
|
+
}
|
|
278
|
+
function formatTimeline(entries) {
|
|
279
|
+
if (entries.length === 0) return "No profiling data";
|
|
280
|
+
const lines = ["Commit timeline:"];
|
|
281
|
+
for (const e of entries) {
|
|
282
|
+
lines.push(
|
|
283
|
+
` #${e.index} ${e.duration.toFixed(1)}ms ${e.componentCount} components`
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
return lines.join("\n");
|
|
287
|
+
}
|
|
288
|
+
function formatCommitDetail(detail) {
|
|
289
|
+
const lines = [];
|
|
290
|
+
lines.push(`Commit #${detail.index} ${detail.duration.toFixed(1)}ms ${detail.totalComponents} components`);
|
|
291
|
+
lines.push("");
|
|
292
|
+
for (const c of detail.components) {
|
|
293
|
+
const causes = c.causes.length > 0 ? c.causes.join(", ") : "?";
|
|
294
|
+
lines.push(` ${pad(c.displayName, 24)} self:${c.selfDuration.toFixed(1)}ms total:${c.actualDuration.toFixed(1)}ms ${causes}`);
|
|
295
|
+
}
|
|
296
|
+
const hidden = detail.totalComponents - detail.components.length;
|
|
297
|
+
if (hidden > 0) {
|
|
298
|
+
lines.push(` ... ${hidden} more (use --limit to show more)`);
|
|
299
|
+
}
|
|
300
|
+
return lines.join("\n");
|
|
301
|
+
}
|
|
302
|
+
function formatCompactValue(val) {
|
|
303
|
+
if (val === void 0) return void 0;
|
|
304
|
+
if (val === null) return "null";
|
|
305
|
+
if (typeof val === "function") return "\u0192";
|
|
306
|
+
if (typeof val === "string") return `"${val}"`;
|
|
307
|
+
if (typeof val === "number" || typeof val === "boolean") return String(val);
|
|
308
|
+
try {
|
|
309
|
+
const s = JSON.stringify(val, replacer, 0);
|
|
310
|
+
if (s && s.length > 60) return s.slice(0, 57) + "...";
|
|
311
|
+
return s || String(val);
|
|
312
|
+
} catch {
|
|
313
|
+
return String(val);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function replacer(_key, value) {
|
|
317
|
+
if (typeof value === "function") return "\u0192";
|
|
318
|
+
return value;
|
|
319
|
+
}
|
|
320
|
+
function pad(s, len) {
|
|
321
|
+
return s.length >= len ? s : s + " ".repeat(len - s.length);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// src/cli.ts
|
|
325
|
+
function usage() {
|
|
326
|
+
return `Usage: devtools <command> [options]
|
|
327
|
+
|
|
328
|
+
Daemon:
|
|
329
|
+
start [--port 8097] Start daemon
|
|
330
|
+
stop Stop daemon
|
|
331
|
+
status Show daemon status
|
|
332
|
+
|
|
333
|
+
Components:
|
|
334
|
+
get tree [--depth N] Component hierarchy
|
|
335
|
+
get component <@c1 | id> Props, state, hooks
|
|
336
|
+
find <name> [--exact] Search by display name
|
|
337
|
+
count Component count by type
|
|
338
|
+
|
|
339
|
+
Profiling:
|
|
340
|
+
profile start [name] Start profiling session
|
|
341
|
+
profile stop Stop profiling, collect data
|
|
342
|
+
profile report <@c1 | id> Render report for component
|
|
343
|
+
profile slow [--limit N] Slowest components (by avg)
|
|
344
|
+
profile rerenders [--limit N] Most re-rendered components
|
|
345
|
+
profile timeline [--limit N] Commit timeline
|
|
346
|
+
profile commit <N | #N> [--limit N] Detail for specific commit`;
|
|
347
|
+
}
|
|
348
|
+
function parseArgs(argv) {
|
|
349
|
+
const command = [];
|
|
350
|
+
const flags = {};
|
|
351
|
+
for (let i = 0; i < argv.length; i++) {
|
|
352
|
+
const arg = argv[i];
|
|
353
|
+
if (arg.startsWith("--")) {
|
|
354
|
+
const key = arg.slice(2);
|
|
355
|
+
const eqIdx = key.indexOf("=");
|
|
356
|
+
if (eqIdx !== -1) {
|
|
357
|
+
flags[key.slice(0, eqIdx)] = key.slice(eqIdx + 1);
|
|
358
|
+
} else {
|
|
359
|
+
const next = argv[i + 1];
|
|
360
|
+
if (next && !next.startsWith("--")) {
|
|
361
|
+
flags[key] = next;
|
|
362
|
+
i++;
|
|
363
|
+
} else {
|
|
364
|
+
flags[key] = true;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
} else {
|
|
368
|
+
command.push(arg);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return { command, flags };
|
|
372
|
+
}
|
|
373
|
+
async function main() {
|
|
374
|
+
const { command, flags } = parseArgs(process.argv.slice(2));
|
|
375
|
+
if (command.length === 0 || flags["help"]) {
|
|
376
|
+
console.log(usage());
|
|
377
|
+
process.exit(0);
|
|
378
|
+
}
|
|
379
|
+
if (typeof flags["state-dir"] === "string") {
|
|
380
|
+
setStateDir(flags["state-dir"]);
|
|
381
|
+
}
|
|
382
|
+
const cmd0 = command[0];
|
|
383
|
+
const cmd1 = command[1];
|
|
384
|
+
try {
|
|
385
|
+
if (cmd0 === "start") {
|
|
386
|
+
const port = flags["port"] ? parseInt(flags["port"], 10) : void 0;
|
|
387
|
+
await ensureDaemon(port);
|
|
388
|
+
const resp = await sendCommand({ type: "status" });
|
|
389
|
+
if (resp.ok) {
|
|
390
|
+
console.log(formatStatus(resp.data));
|
|
391
|
+
}
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (cmd0 === "stop") {
|
|
395
|
+
const stopped = stopDaemon();
|
|
396
|
+
console.log(stopped ? "Daemon stopped" : "Daemon is not running");
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (cmd0 === "status") {
|
|
400
|
+
const info = readDaemonInfo();
|
|
401
|
+
if (!info) {
|
|
402
|
+
console.log("Daemon is not running");
|
|
403
|
+
process.exit(1);
|
|
404
|
+
}
|
|
405
|
+
try {
|
|
406
|
+
const resp = await sendCommand({ type: "status" });
|
|
407
|
+
if (resp.ok) {
|
|
408
|
+
console.log(formatStatus(resp.data));
|
|
409
|
+
} else {
|
|
410
|
+
console.error(resp.error);
|
|
411
|
+
process.exit(1);
|
|
412
|
+
}
|
|
413
|
+
} catch {
|
|
414
|
+
console.log("Daemon is not running (stale info)");
|
|
415
|
+
process.exit(1);
|
|
416
|
+
}
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
await ensureDaemon();
|
|
420
|
+
if (cmd0 === "get" && cmd1 === "tree") {
|
|
421
|
+
const depth = flags["depth"] ? parseInt(flags["depth"], 10) : void 0;
|
|
422
|
+
const ipcCmd = { type: "get-tree", depth };
|
|
423
|
+
const resp = await sendCommand(ipcCmd);
|
|
424
|
+
if (resp.ok) {
|
|
425
|
+
console.log(formatTree(resp.data));
|
|
426
|
+
} else {
|
|
427
|
+
console.error(resp.error);
|
|
428
|
+
process.exit(1);
|
|
429
|
+
}
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
if (cmd0 === "get" && cmd1 === "component") {
|
|
433
|
+
const raw = command[2];
|
|
434
|
+
if (!raw) {
|
|
435
|
+
console.error("Usage: devtools get component <@c1 | id>");
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
const id = raw.startsWith("@") ? raw : parseInt(raw, 10);
|
|
439
|
+
if (typeof id === "number" && isNaN(id)) {
|
|
440
|
+
console.error("Usage: devtools get component <@c1 | id>");
|
|
441
|
+
process.exit(1);
|
|
442
|
+
}
|
|
443
|
+
const resp = await sendCommand({ type: "get-component", id });
|
|
444
|
+
if (resp.ok) {
|
|
445
|
+
console.log(formatComponent(resp.data, resp.label));
|
|
446
|
+
} else {
|
|
447
|
+
console.error(resp.error);
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (cmd0 === "find") {
|
|
453
|
+
const name = command[1];
|
|
454
|
+
if (!name) {
|
|
455
|
+
console.error("Usage: devtools find <name> [--exact]");
|
|
456
|
+
process.exit(1);
|
|
457
|
+
}
|
|
458
|
+
const exact = flags["exact"] === true;
|
|
459
|
+
const resp = await sendCommand({ type: "find", name, exact });
|
|
460
|
+
if (resp.ok) {
|
|
461
|
+
console.log(formatSearchResults(resp.data));
|
|
462
|
+
} else {
|
|
463
|
+
console.error(resp.error);
|
|
464
|
+
process.exit(1);
|
|
465
|
+
}
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
if (cmd0 === "count") {
|
|
469
|
+
const resp = await sendCommand({ type: "count" });
|
|
470
|
+
if (resp.ok) {
|
|
471
|
+
console.log(formatCount(resp.data));
|
|
472
|
+
} else {
|
|
473
|
+
console.error(resp.error);
|
|
474
|
+
process.exit(1);
|
|
475
|
+
}
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
if (cmd0 === "profile" && cmd1 === "start") {
|
|
479
|
+
const name = command[2];
|
|
480
|
+
const resp = await sendCommand({ type: "profile-start", name });
|
|
481
|
+
if (resp.ok) {
|
|
482
|
+
console.log(resp.data);
|
|
483
|
+
} else {
|
|
484
|
+
console.error(resp.error);
|
|
485
|
+
process.exit(1);
|
|
486
|
+
}
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
if (cmd0 === "profile" && cmd1 === "stop") {
|
|
490
|
+
const resp = await sendCommand({ type: "profile-stop" });
|
|
491
|
+
if (resp.ok) {
|
|
492
|
+
console.log(formatProfileSummary(resp.data));
|
|
493
|
+
} else {
|
|
494
|
+
console.error(resp.error);
|
|
495
|
+
process.exit(1);
|
|
496
|
+
}
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (cmd0 === "profile" && cmd1 === "report") {
|
|
500
|
+
const raw = command[2];
|
|
501
|
+
if (!raw) {
|
|
502
|
+
console.error("Usage: devtools profile report <@c1 | id>");
|
|
503
|
+
process.exit(1);
|
|
504
|
+
}
|
|
505
|
+
const componentId = raw.startsWith("@") ? raw : parseInt(raw, 10);
|
|
506
|
+
if (typeof componentId === "number" && isNaN(componentId)) {
|
|
507
|
+
console.error("Usage: devtools profile report <@c1 | id>");
|
|
508
|
+
process.exit(1);
|
|
509
|
+
}
|
|
510
|
+
const resp = await sendCommand({ type: "profile-report", componentId });
|
|
511
|
+
if (resp.ok) {
|
|
512
|
+
console.log(formatProfileReport(resp.data, resp.label));
|
|
513
|
+
} else {
|
|
514
|
+
console.error(resp.error);
|
|
515
|
+
process.exit(1);
|
|
516
|
+
}
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
if (cmd0 === "profile" && cmd1 === "slow") {
|
|
520
|
+
const limit = flags["limit"] ? parseInt(flags["limit"], 10) : void 0;
|
|
521
|
+
const resp = await sendCommand({ type: "profile-slow", limit });
|
|
522
|
+
if (resp.ok) {
|
|
523
|
+
console.log(formatSlowest(resp.data));
|
|
524
|
+
} else {
|
|
525
|
+
console.error(resp.error);
|
|
526
|
+
process.exit(1);
|
|
527
|
+
}
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
if (cmd0 === "profile" && cmd1 === "rerenders") {
|
|
531
|
+
const limit = flags["limit"] ? parseInt(flags["limit"], 10) : void 0;
|
|
532
|
+
const resp = await sendCommand({ type: "profile-rerenders", limit });
|
|
533
|
+
if (resp.ok) {
|
|
534
|
+
console.log(formatRerenders(resp.data));
|
|
535
|
+
} else {
|
|
536
|
+
console.error(resp.error);
|
|
537
|
+
process.exit(1);
|
|
538
|
+
}
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
if (cmd0 === "profile" && cmd1 === "commit") {
|
|
542
|
+
const raw = command[2];
|
|
543
|
+
if (!raw) {
|
|
544
|
+
console.error("Usage: devtools profile commit <N | #N>");
|
|
545
|
+
process.exit(1);
|
|
546
|
+
}
|
|
547
|
+
const index = parseInt(raw.replace(/^#/, ""), 10);
|
|
548
|
+
if (isNaN(index)) {
|
|
549
|
+
console.error("Usage: devtools profile commit <N | #N>");
|
|
550
|
+
process.exit(1);
|
|
551
|
+
}
|
|
552
|
+
const limit = flags["limit"] ? parseInt(flags["limit"], 10) : void 0;
|
|
553
|
+
const resp = await sendCommand({ type: "profile-commit", index, limit });
|
|
554
|
+
if (resp.ok) {
|
|
555
|
+
console.log(formatCommitDetail(resp.data));
|
|
556
|
+
} else {
|
|
557
|
+
console.error(resp.error);
|
|
558
|
+
process.exit(1);
|
|
559
|
+
}
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
if (cmd0 === "profile" && cmd1 === "timeline") {
|
|
563
|
+
const limit = flags["limit"] ? parseInt(flags["limit"], 10) : void 0;
|
|
564
|
+
const resp = await sendCommand({ type: "profile-timeline", limit });
|
|
565
|
+
if (resp.ok) {
|
|
566
|
+
console.log(formatTimeline(resp.data));
|
|
567
|
+
} else {
|
|
568
|
+
console.error(resp.error);
|
|
569
|
+
process.exit(1);
|
|
570
|
+
}
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
console.error(`Unknown command: ${command.join(" ")}`);
|
|
574
|
+
console.log(usage());
|
|
575
|
+
process.exit(1);
|
|
576
|
+
} catch (err) {
|
|
577
|
+
console.error(
|
|
578
|
+
err instanceof Error ? err.message : String(err)
|
|
579
|
+
);
|
|
580
|
+
process.exit(1);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
main();
|
|
584
|
+
//# sourceMappingURL=cli.js.map
|