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/src/cli.ts
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ensureDaemon,
|
|
3
|
+
sendCommand,
|
|
4
|
+
stopDaemon,
|
|
5
|
+
readDaemonInfo,
|
|
6
|
+
setStateDir,
|
|
7
|
+
} from './daemon-client.js';
|
|
8
|
+
import {
|
|
9
|
+
formatTree,
|
|
10
|
+
formatComponent,
|
|
11
|
+
formatSearchResults,
|
|
12
|
+
formatCount,
|
|
13
|
+
formatStatus,
|
|
14
|
+
formatProfileSummary,
|
|
15
|
+
formatProfileReport,
|
|
16
|
+
formatSlowest,
|
|
17
|
+
formatRerenders,
|
|
18
|
+
formatTimeline,
|
|
19
|
+
formatCommitDetail,
|
|
20
|
+
} from './formatters.js';
|
|
21
|
+
import type { IpcCommand } from './types.js';
|
|
22
|
+
|
|
23
|
+
function usage(): string {
|
|
24
|
+
return `Usage: devtools <command> [options]
|
|
25
|
+
|
|
26
|
+
Daemon:
|
|
27
|
+
start [--port 8097] Start daemon
|
|
28
|
+
stop Stop daemon
|
|
29
|
+
status Show daemon status
|
|
30
|
+
|
|
31
|
+
Components:
|
|
32
|
+
get tree [--depth N] Component hierarchy
|
|
33
|
+
get component <@c1 | id> Props, state, hooks
|
|
34
|
+
find <name> [--exact] Search by display name
|
|
35
|
+
count Component count by type
|
|
36
|
+
|
|
37
|
+
Profiling:
|
|
38
|
+
profile start [name] Start profiling session
|
|
39
|
+
profile stop Stop profiling, collect data
|
|
40
|
+
profile report <@c1 | id> Render report for component
|
|
41
|
+
profile slow [--limit N] Slowest components (by avg)
|
|
42
|
+
profile rerenders [--limit N] Most re-rendered components
|
|
43
|
+
profile timeline [--limit N] Commit timeline
|
|
44
|
+
profile commit <N | #N> [--limit N] Detail for specific commit`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function parseArgs(argv: string[]): {
|
|
48
|
+
command: string[];
|
|
49
|
+
flags: Record<string, string | boolean>;
|
|
50
|
+
} {
|
|
51
|
+
const command: string[] = [];
|
|
52
|
+
const flags: Record<string, string | boolean> = {};
|
|
53
|
+
|
|
54
|
+
for (let i = 0; i < argv.length; i++) {
|
|
55
|
+
const arg = argv[i];
|
|
56
|
+
if (arg.startsWith('--')) {
|
|
57
|
+
const key = arg.slice(2);
|
|
58
|
+
const eqIdx = key.indexOf('=');
|
|
59
|
+
if (eqIdx !== -1) {
|
|
60
|
+
flags[key.slice(0, eqIdx)] = key.slice(eqIdx + 1);
|
|
61
|
+
} else {
|
|
62
|
+
// Check if next arg is a value
|
|
63
|
+
const next = argv[i + 1];
|
|
64
|
+
if (next && !next.startsWith('--')) {
|
|
65
|
+
flags[key] = next;
|
|
66
|
+
i++;
|
|
67
|
+
} else {
|
|
68
|
+
flags[key] = true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
command.push(arg);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return { command, flags };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function main(): Promise<void> {
|
|
79
|
+
const { command, flags } = parseArgs(process.argv.slice(2));
|
|
80
|
+
|
|
81
|
+
if (command.length === 0 || flags['help']) {
|
|
82
|
+
console.log(usage());
|
|
83
|
+
process.exit(0);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Configure custom state directory (for test isolation)
|
|
87
|
+
if (typeof flags['state-dir'] === 'string') {
|
|
88
|
+
setStateDir(flags['state-dir']);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const cmd0 = command[0];
|
|
92
|
+
const cmd1 = command[1];
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
// ── Daemon management ──
|
|
96
|
+
if (cmd0 === 'start') {
|
|
97
|
+
const port = flags['port'] ? parseInt(flags['port'] as string, 10) : undefined;
|
|
98
|
+
await ensureDaemon(port);
|
|
99
|
+
const resp = await sendCommand({ type: 'status' });
|
|
100
|
+
if (resp.ok) {
|
|
101
|
+
console.log(formatStatus(resp.data as any));
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (cmd0 === 'stop') {
|
|
107
|
+
const stopped = stopDaemon();
|
|
108
|
+
console.log(stopped ? 'Daemon stopped' : 'Daemon is not running');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (cmd0 === 'status') {
|
|
113
|
+
const info = readDaemonInfo();
|
|
114
|
+
if (!info) {
|
|
115
|
+
console.log('Daemon is not running');
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
const resp = await sendCommand({ type: 'status' });
|
|
120
|
+
if (resp.ok) {
|
|
121
|
+
console.log(formatStatus(resp.data as any));
|
|
122
|
+
} else {
|
|
123
|
+
console.error(resp.error);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
console.log('Daemon is not running (stale info)');
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ── All other commands require the daemon ──
|
|
134
|
+
await ensureDaemon();
|
|
135
|
+
|
|
136
|
+
// ── Component inspection ──
|
|
137
|
+
if (cmd0 === 'get' && cmd1 === 'tree') {
|
|
138
|
+
const depth = flags['depth']
|
|
139
|
+
? parseInt(flags['depth'] as string, 10)
|
|
140
|
+
: undefined;
|
|
141
|
+
const ipcCmd: IpcCommand = { type: 'get-tree', depth };
|
|
142
|
+
const resp = await sendCommand(ipcCmd);
|
|
143
|
+
if (resp.ok) {
|
|
144
|
+
console.log(formatTree(resp.data as any));
|
|
145
|
+
} else {
|
|
146
|
+
console.error(resp.error);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (cmd0 === 'get' && cmd1 === 'component') {
|
|
153
|
+
const raw = command[2];
|
|
154
|
+
if (!raw) {
|
|
155
|
+
console.error('Usage: devtools get component <@c1 | id>');
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
const id: number | string = raw.startsWith('@') ? raw : parseInt(raw, 10);
|
|
159
|
+
if (typeof id === 'number' && isNaN(id)) {
|
|
160
|
+
console.error('Usage: devtools get component <@c1 | id>');
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
const resp = await sendCommand({ type: 'get-component', id });
|
|
164
|
+
if (resp.ok) {
|
|
165
|
+
console.log(formatComponent(resp.data as any, resp.label));
|
|
166
|
+
} else {
|
|
167
|
+
console.error(resp.error);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (cmd0 === 'find') {
|
|
174
|
+
const name = command[1];
|
|
175
|
+
if (!name) {
|
|
176
|
+
console.error('Usage: devtools find <name> [--exact]');
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
const exact = flags['exact'] === true;
|
|
180
|
+
const resp = await sendCommand({ type: 'find', name, exact });
|
|
181
|
+
if (resp.ok) {
|
|
182
|
+
console.log(formatSearchResults(resp.data as any));
|
|
183
|
+
} else {
|
|
184
|
+
console.error(resp.error);
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (cmd0 === 'count') {
|
|
191
|
+
const resp = await sendCommand({ type: 'count' });
|
|
192
|
+
if (resp.ok) {
|
|
193
|
+
console.log(formatCount(resp.data as any));
|
|
194
|
+
} else {
|
|
195
|
+
console.error(resp.error);
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ── Profiling ──
|
|
202
|
+
if (cmd0 === 'profile' && cmd1 === 'start') {
|
|
203
|
+
const name = command[2];
|
|
204
|
+
const resp = await sendCommand({ type: 'profile-start', name });
|
|
205
|
+
if (resp.ok) {
|
|
206
|
+
console.log(resp.data);
|
|
207
|
+
} else {
|
|
208
|
+
console.error(resp.error);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (cmd0 === 'profile' && cmd1 === 'stop') {
|
|
215
|
+
const resp = await sendCommand({ type: 'profile-stop' });
|
|
216
|
+
if (resp.ok) {
|
|
217
|
+
console.log(formatProfileSummary(resp.data as any));
|
|
218
|
+
} else {
|
|
219
|
+
console.error(resp.error);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (cmd0 === 'profile' && cmd1 === 'report') {
|
|
226
|
+
const raw = command[2];
|
|
227
|
+
if (!raw) {
|
|
228
|
+
console.error('Usage: devtools profile report <@c1 | id>');
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
const componentId: number | string = raw.startsWith('@') ? raw : parseInt(raw, 10);
|
|
232
|
+
if (typeof componentId === 'number' && isNaN(componentId)) {
|
|
233
|
+
console.error('Usage: devtools profile report <@c1 | id>');
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
const resp = await sendCommand({ type: 'profile-report', componentId });
|
|
237
|
+
if (resp.ok) {
|
|
238
|
+
console.log(formatProfileReport(resp.data as any, resp.label));
|
|
239
|
+
} else {
|
|
240
|
+
console.error(resp.error);
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (cmd0 === 'profile' && cmd1 === 'slow') {
|
|
247
|
+
const limit = flags['limit'] ? parseInt(flags['limit'] as string, 10) : undefined;
|
|
248
|
+
const resp = await sendCommand({ type: 'profile-slow', limit });
|
|
249
|
+
if (resp.ok) {
|
|
250
|
+
console.log(formatSlowest(resp.data as any));
|
|
251
|
+
} else {
|
|
252
|
+
console.error(resp.error);
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (cmd0 === 'profile' && cmd1 === 'rerenders') {
|
|
259
|
+
const limit = flags['limit'] ? parseInt(flags['limit'] as string, 10) : undefined;
|
|
260
|
+
const resp = await sendCommand({ type: 'profile-rerenders', limit });
|
|
261
|
+
if (resp.ok) {
|
|
262
|
+
console.log(formatRerenders(resp.data as any));
|
|
263
|
+
} else {
|
|
264
|
+
console.error(resp.error);
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (cmd0 === 'profile' && cmd1 === 'commit') {
|
|
271
|
+
const raw = command[2];
|
|
272
|
+
if (!raw) {
|
|
273
|
+
console.error('Usage: devtools profile commit <N | #N>');
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
const index = parseInt(raw.replace(/^#/, ''), 10);
|
|
277
|
+
if (isNaN(index)) {
|
|
278
|
+
console.error('Usage: devtools profile commit <N | #N>');
|
|
279
|
+
process.exit(1);
|
|
280
|
+
}
|
|
281
|
+
const limit = flags['limit'] ? parseInt(flags['limit'] as string, 10) : undefined;
|
|
282
|
+
const resp = await sendCommand({ type: 'profile-commit', index, limit });
|
|
283
|
+
if (resp.ok) {
|
|
284
|
+
console.log(formatCommitDetail(resp.data as any));
|
|
285
|
+
} else {
|
|
286
|
+
console.error(resp.error);
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (cmd0 === 'profile' && cmd1 === 'timeline') {
|
|
293
|
+
const limit = flags['limit'] ? parseInt(flags['limit'] as string, 10) : undefined;
|
|
294
|
+
const resp = await sendCommand({ type: 'profile-timeline', limit });
|
|
295
|
+
if (resp.ok) {
|
|
296
|
+
console.log(formatTimeline(resp.data as any));
|
|
297
|
+
} else {
|
|
298
|
+
console.error(resp.error);
|
|
299
|
+
process.exit(1);
|
|
300
|
+
}
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
console.error(`Unknown command: ${command.join(' ')}`);
|
|
305
|
+
console.log(usage());
|
|
306
|
+
process.exit(1);
|
|
307
|
+
} catch (err) {
|
|
308
|
+
console.error(
|
|
309
|
+
err instanceof Error ? err.message : String(err),
|
|
310
|
+
);
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
main();
|