agent-react-devtools 0.1.0 → 0.2.0-canary-20260210204855

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.
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/init.ts
4
+ import { readFileSync, writeFileSync, existsSync } from "fs";
5
+ import { join, dirname } from "path";
6
+ function detectFramework(cwd) {
7
+ const pkgPath = join(cwd, "package.json");
8
+ if (!existsSync(pkgPath)) return "unknown";
9
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
10
+ const allDeps = {
11
+ ...pkg.dependencies,
12
+ ...pkg.devDependencies
13
+ };
14
+ if (allDeps["@vitejs/plugin-react"]) return "vite";
15
+ if (allDeps["next"]) return "nextjs";
16
+ if (allDeps["react-scripts"]) return "cra";
17
+ if (allDeps["react-native"]) return "react-native";
18
+ return "unknown";
19
+ }
20
+ function findFile(cwd, ...candidates) {
21
+ for (const c of candidates) {
22
+ const p = join(cwd, c);
23
+ if (existsSync(p)) return p;
24
+ }
25
+ return null;
26
+ }
27
+ function prependImport(filePath, importLine, dryRun) {
28
+ const content = readFileSync(filePath, "utf-8");
29
+ if (content.includes(importLine) || content.includes("agent-react-devtools")) {
30
+ return null;
31
+ }
32
+ const newContent = importLine + "\n" + content;
33
+ if (!dryRun) {
34
+ writeFileSync(filePath, newContent, "utf-8");
35
+ }
36
+ return filePath;
37
+ }
38
+ function patchViteConfig(cwd, dryRun) {
39
+ const configPath = findFile(
40
+ cwd,
41
+ "vite.config.ts",
42
+ "vite.config.js",
43
+ "vite.config.mts",
44
+ "vite.config.mjs"
45
+ );
46
+ if (!configPath) {
47
+ console.error(" Could not find vite.config.{ts,js}");
48
+ return [];
49
+ }
50
+ const content = readFileSync(configPath, "utf-8");
51
+ if (content.includes("agent-react-devtools")) {
52
+ console.log(" Already configured");
53
+ return [];
54
+ }
55
+ const importLine = "import { reactDevtools } from 'agent-react-devtools/vite';";
56
+ let newContent;
57
+ const lastImportIdx = content.lastIndexOf("\nimport ");
58
+ if (lastImportIdx !== -1) {
59
+ const endOfLine = content.indexOf("\n", lastImportIdx + 1);
60
+ newContent = content.slice(0, endOfLine + 1) + importLine + "\n" + content.slice(endOfLine + 1);
61
+ } else {
62
+ newContent = importLine + "\n" + content;
63
+ }
64
+ const pluginsMatch = newContent.match(/plugins\s*:\s*\[/);
65
+ if (pluginsMatch && pluginsMatch.index != null) {
66
+ const insertPos = pluginsMatch.index + pluginsMatch[0].length;
67
+ newContent = newContent.slice(0, insertPos) + "\n reactDevtools()," + newContent.slice(insertPos);
68
+ } else {
69
+ console.error(" Could not find plugins array in vite config");
70
+ return [];
71
+ }
72
+ if (!dryRun) {
73
+ writeFileSync(configPath, newContent, "utf-8");
74
+ }
75
+ return [configPath];
76
+ }
77
+ function patchNextJs(cwd, dryRun) {
78
+ const pagesEntry = findFile(
79
+ cwd,
80
+ "pages/_app.tsx",
81
+ "pages/_app.jsx",
82
+ "pages/_app.js",
83
+ "src/pages/_app.tsx",
84
+ "src/pages/_app.jsx",
85
+ "src/pages/_app.js"
86
+ );
87
+ if (pagesEntry) {
88
+ const result2 = prependImport(
89
+ pagesEntry,
90
+ "import 'agent-react-devtools/connect';",
91
+ dryRun
92
+ );
93
+ return result2 ? [result2] : [];
94
+ }
95
+ const layoutPath = findFile(
96
+ cwd,
97
+ "app/layout.tsx",
98
+ "app/layout.jsx",
99
+ "app/layout.js",
100
+ "src/app/layout.tsx",
101
+ "src/app/layout.jsx",
102
+ "src/app/layout.js"
103
+ );
104
+ if (!layoutPath) {
105
+ console.error(" Could not find app/layout.tsx or pages/_app.tsx");
106
+ return [];
107
+ }
108
+ const devtoolsPath = join(dirname(layoutPath), "devtools.ts");
109
+ const modified = [];
110
+ if (existsSync(devtoolsPath)) {
111
+ const existing = readFileSync(devtoolsPath, "utf-8");
112
+ if (!existing.includes("agent-react-devtools")) {
113
+ console.error(` ${devtoolsPath} already exists with different content`);
114
+ return [];
115
+ }
116
+ } else {
117
+ const wrapper = "'use client';\nimport 'agent-react-devtools/connect';\n";
118
+ if (!dryRun) {
119
+ writeFileSync(devtoolsPath, wrapper, "utf-8");
120
+ }
121
+ modified.push(devtoolsPath);
122
+ }
123
+ const result = prependImport(layoutPath, "import './devtools';", dryRun);
124
+ if (result) {
125
+ modified.push(result);
126
+ }
127
+ return modified;
128
+ }
129
+ function patchCRA(cwd, dryRun) {
130
+ const entryPath = findFile(
131
+ cwd,
132
+ "src/index.tsx",
133
+ "src/index.jsx",
134
+ "src/index.js"
135
+ );
136
+ if (!entryPath) {
137
+ console.error(" Could not find src/index.tsx");
138
+ return [];
139
+ }
140
+ const result = prependImport(
141
+ entryPath,
142
+ "import 'agent-react-devtools/connect';",
143
+ dryRun
144
+ );
145
+ return result ? [result] : [];
146
+ }
147
+ async function runInit(cwd, dryRun) {
148
+ const framework = detectFramework(cwd);
149
+ console.log(`Detected framework: ${framework}`);
150
+ if (framework === "unknown") {
151
+ console.log("\nCould not detect framework. Manual setup required:");
152
+ console.log(" import 'agent-react-devtools/connect';");
153
+ console.log(" // Must be imported before React loads");
154
+ return;
155
+ }
156
+ if (framework === "react-native") {
157
+ console.log("\nReact Native detected \u2014 no code changes needed!");
158
+ console.log("React Native apps connect to DevTools automatically.\n");
159
+ console.log("Next steps:");
160
+ console.log(" 1. Start the daemon: agent-react-devtools start");
161
+ console.log(" 2. Start your app: npx react-native start");
162
+ console.log("\nFor physical devices:");
163
+ console.log(" adb reverse tcp:8097 tcp:8097");
164
+ console.log("\nFor Expo:");
165
+ console.log(" The connection works automatically with Expo dev client.");
166
+ console.log("\nCustom port:");
167
+ console.log(" Set REACT_DEVTOOLS_PORT=<port> environment variable");
168
+ return;
169
+ }
170
+ let modified = [];
171
+ if (dryRun) {
172
+ console.log("\n[dry-run] Would modify:");
173
+ }
174
+ switch (framework) {
175
+ case "vite":
176
+ modified = patchViteConfig(cwd, dryRun);
177
+ break;
178
+ case "nextjs":
179
+ modified = patchNextJs(cwd, dryRun);
180
+ break;
181
+ case "cra":
182
+ modified = patchCRA(cwd, dryRun);
183
+ break;
184
+ }
185
+ if (modified.length === 0) {
186
+ console.log(" No changes needed (already configured or could not find entry files)");
187
+ return;
188
+ }
189
+ for (const f of modified) {
190
+ console.log(` ${dryRun ? "[dry-run] " : ""}Modified: ${f}`);
191
+ }
192
+ console.log("\nNext steps:");
193
+ console.log(" 1. Install: npm install -D agent-react-devtools react-devtools-core");
194
+ console.log(" 2. Start daemon: agent-react-devtools start");
195
+ console.log(" 3. Start dev server and open your app");
196
+ console.log(" 4. Inspect: agent-react-devtools get tree");
197
+ }
198
+ export {
199
+ detectFramework,
200
+ runInit
201
+ };
202
+ //# sourceMappingURL=init-7IJ2VXT7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/init.ts"],"sourcesContent":["import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\n\ntype Framework = 'vite' | 'nextjs' | 'cra' | 'react-native' | 'unknown';\n\nexport function detectFramework(cwd: string): Framework {\n const pkgPath = join(cwd, 'package.json');\n if (!existsSync(pkgPath)) return 'unknown';\n\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n const allDeps = {\n ...pkg.dependencies,\n ...pkg.devDependencies,\n };\n\n if (allDeps['@vitejs/plugin-react']) return 'vite';\n if (allDeps['next']) return 'nextjs';\n if (allDeps['react-scripts']) return 'cra';\n if (allDeps['react-native']) return 'react-native';\n return 'unknown';\n}\n\nfunction findFile(cwd: string, ...candidates: string[]): string | null {\n for (const c of candidates) {\n const p = join(cwd, c);\n if (existsSync(p)) return p;\n }\n return null;\n}\n\nfunction prependImport(filePath: string, importLine: string, dryRun: boolean): string | null {\n const content = readFileSync(filePath, 'utf-8');\n if (content.includes(importLine) || content.includes('agent-react-devtools')) {\n return null; // already configured\n }\n const newContent = importLine + '\\n' + content;\n if (!dryRun) {\n writeFileSync(filePath, newContent, 'utf-8');\n }\n return filePath;\n}\n\nfunction patchViteConfig(cwd: string, dryRun: boolean): string[] {\n const configPath = findFile(\n cwd,\n 'vite.config.ts',\n 'vite.config.js',\n 'vite.config.mts',\n 'vite.config.mjs',\n );\n if (!configPath) {\n console.error(' Could not find vite.config.{ts,js}');\n return [];\n }\n\n const content = readFileSync(configPath, 'utf-8');\n if (content.includes('agent-react-devtools')) {\n console.log(' Already configured');\n return [];\n }\n\n const importLine = \"import { reactDevtools } from 'agent-react-devtools/vite';\";\n let newContent: string;\n\n // Add import after the last existing import\n const lastImportIdx = content.lastIndexOf('\\nimport ');\n if (lastImportIdx !== -1) {\n const endOfLine = content.indexOf('\\n', lastImportIdx + 1);\n newContent =\n content.slice(0, endOfLine + 1) +\n importLine +\n '\\n' +\n content.slice(endOfLine + 1);\n } else {\n newContent = importLine + '\\n' + content;\n }\n\n // Add reactDevtools() to plugins array\n const pluginsMatch = newContent.match(/plugins\\s*:\\s*\\[/);\n if (pluginsMatch && pluginsMatch.index != null) {\n const insertPos = pluginsMatch.index + pluginsMatch[0].length;\n newContent =\n newContent.slice(0, insertPos) +\n '\\n reactDevtools(),' +\n newContent.slice(insertPos);\n } else {\n console.error(' Could not find plugins array in vite config');\n return [];\n }\n\n if (!dryRun) {\n writeFileSync(configPath, newContent, 'utf-8');\n }\n\n return [configPath];\n}\n\nfunction patchNextJs(cwd: string, dryRun: boolean): string[] {\n // Try Pages Router first — _app is always client-side\n const pagesEntry = findFile(\n cwd,\n 'pages/_app.tsx',\n 'pages/_app.jsx',\n 'pages/_app.js',\n 'src/pages/_app.tsx',\n 'src/pages/_app.jsx',\n 'src/pages/_app.js',\n );\n if (pagesEntry) {\n const result = prependImport(\n pagesEntry,\n \"import 'agent-react-devtools/connect';\",\n dryRun,\n );\n return result ? [result] : [];\n }\n\n // App Router — layout is a Server Component, so we need a 'use client' wrapper\n const layoutPath = findFile(\n cwd,\n 'app/layout.tsx',\n 'app/layout.jsx',\n 'app/layout.js',\n 'src/app/layout.tsx',\n 'src/app/layout.jsx',\n 'src/app/layout.js',\n );\n if (!layoutPath) {\n console.error(' Could not find app/layout.tsx or pages/_app.tsx');\n return [];\n }\n\n const devtoolsPath = join(dirname(layoutPath), 'devtools.ts');\n const modified: string[] = [];\n\n // Create the 'use client' wrapper file\n if (existsSync(devtoolsPath)) {\n const existing = readFileSync(devtoolsPath, 'utf-8');\n if (!existing.includes('agent-react-devtools')) {\n console.error(` ${devtoolsPath} already exists with different content`);\n return [];\n }\n } else {\n const wrapper = \"'use client';\\nimport 'agent-react-devtools/connect';\\n\";\n if (!dryRun) {\n writeFileSync(devtoolsPath, wrapper, 'utf-8');\n }\n modified.push(devtoolsPath);\n }\n\n // Prepend import of the wrapper to the layout\n const result = prependImport(layoutPath, \"import './devtools';\", dryRun);\n if (result) {\n modified.push(result);\n }\n\n return modified;\n}\n\nfunction patchCRA(cwd: string, dryRun: boolean): string[] {\n const entryPath = findFile(\n cwd,\n 'src/index.tsx',\n 'src/index.jsx',\n 'src/index.js',\n );\n if (!entryPath) {\n console.error(' Could not find src/index.tsx');\n return [];\n }\n\n const result = prependImport(\n entryPath,\n \"import 'agent-react-devtools/connect';\",\n dryRun,\n );\n return result ? [result] : [];\n}\n\nexport async function runInit(\n cwd: string,\n dryRun: boolean,\n): Promise<void> {\n const framework = detectFramework(cwd);\n\n console.log(`Detected framework: ${framework}`);\n\n if (framework === 'unknown') {\n console.log('\\nCould not detect framework. Manual setup required:');\n console.log(\" import 'agent-react-devtools/connect';\");\n console.log(' // Must be imported before React loads');\n return;\n }\n\n if (framework === 'react-native') {\n console.log('\\nReact Native detected — no code changes needed!');\n console.log('React Native apps connect to DevTools automatically.\\n');\n console.log('Next steps:');\n console.log(' 1. Start the daemon: agent-react-devtools start');\n console.log(' 2. Start your app: npx react-native start');\n console.log('\\nFor physical devices:');\n console.log(' adb reverse tcp:8097 tcp:8097');\n console.log('\\nFor Expo:');\n console.log(' The connection works automatically with Expo dev client.');\n console.log('\\nCustom port:');\n console.log(' Set REACT_DEVTOOLS_PORT=<port> environment variable');\n return;\n }\n\n let modified: string[] = [];\n\n if (dryRun) {\n console.log('\\n[dry-run] Would modify:');\n }\n\n switch (framework) {\n case 'vite':\n modified = patchViteConfig(cwd, dryRun);\n break;\n case 'nextjs':\n modified = patchNextJs(cwd, dryRun);\n break;\n case 'cra':\n modified = patchCRA(cwd, dryRun);\n break;\n }\n\n if (modified.length === 0) {\n console.log(' No changes needed (already configured or could not find entry files)');\n return;\n }\n\n for (const f of modified) {\n console.log(` ${dryRun ? '[dry-run] ' : ''}Modified: ${f}`);\n }\n\n console.log('\\nNext steps:');\n console.log(' 1. Install: npm install -D agent-react-devtools react-devtools-core');\n console.log(' 2. Start daemon: agent-react-devtools start');\n console.log(' 3. Start dev server and open your app');\n console.log(' 4. Inspect: agent-react-devtools get tree');\n}\n"],"mappings":"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","names":["result"]}
package/dist/vite.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ interface ReactDevtoolsOptions {
4
+ /** WebSocket port the daemon listens on. Default: 8097 */
5
+ port?: number;
6
+ /** WebSocket host. Default: 'localhost' */
7
+ host?: string;
8
+ }
9
+ declare function reactDevtools(options?: ReactDevtoolsOptions): Plugin;
10
+
11
+ export { type ReactDevtoolsOptions, reactDevtools };
package/dist/vite.js ADDED
@@ -0,0 +1,40 @@
1
+ // src/vite-plugin.ts
2
+ function reactDevtools(options) {
3
+ const port = options?.port ?? 8097;
4
+ const host = options?.host ?? "localhost";
5
+ return {
6
+ name: "agent-react-devtools",
7
+ apply: "serve",
8
+ transformIndexHtml: {
9
+ order: "pre",
10
+ handler() {
11
+ const tags = [];
12
+ if (host !== "localhost") {
13
+ tags.push({
14
+ tag: "meta",
15
+ attrs: { name: "agent-react-devtools-host", content: host },
16
+ injectTo: "head-prepend"
17
+ });
18
+ }
19
+ if (port !== 8097) {
20
+ tags.push({
21
+ tag: "meta",
22
+ attrs: { name: "agent-react-devtools-port", content: String(port) },
23
+ injectTo: "head-prepend"
24
+ });
25
+ }
26
+ tags.push({
27
+ tag: "script",
28
+ attrs: { type: "module" },
29
+ children: `import 'agent-react-devtools/connect';`,
30
+ injectTo: "head-prepend"
31
+ });
32
+ return tags;
33
+ }
34
+ }
35
+ };
36
+ }
37
+ export {
38
+ reactDevtools
39
+ };
40
+ //# sourceMappingURL=vite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vite-plugin.ts"],"sourcesContent":["import type { Plugin, HtmlTagDescriptor } from 'vite';\n\nexport interface ReactDevtoolsOptions {\n /** WebSocket port the daemon listens on. Default: 8097 */\n port?: number;\n /** WebSocket host. Default: 'localhost' */\n host?: string;\n}\n\nexport function reactDevtools(options?: ReactDevtoolsOptions): Plugin {\n const port = options?.port ?? 8097;\n const host = options?.host ?? 'localhost';\n\n return {\n name: 'agent-react-devtools',\n apply: 'serve',\n transformIndexHtml: {\n order: 'pre',\n handler() {\n const tags: HtmlTagDescriptor[] = [];\n\n if (host !== 'localhost') {\n tags.push({\n tag: 'meta',\n attrs: { name: 'agent-react-devtools-host', content: host },\n injectTo: 'head-prepend',\n });\n }\n\n if (port !== 8097) {\n tags.push({\n tag: 'meta',\n attrs: { name: 'agent-react-devtools-port', content: String(port) },\n injectTo: 'head-prepend',\n });\n }\n\n tags.push({\n tag: 'script',\n attrs: { type: 'module' },\n children: `import 'agent-react-devtools/connect';`,\n injectTo: 'head-prepend',\n });\n\n return tags;\n },\n },\n };\n}\n"],"mappings":";AASO,SAAS,cAAc,SAAwC;AACpE,QAAM,OAAO,SAAS,QAAQ;AAC9B,QAAM,OAAO,SAAS,QAAQ;AAE9B,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IACP,oBAAoB;AAAA,MAClB,OAAO;AAAA,MACP,UAAU;AACR,cAAM,OAA4B,CAAC;AAEnC,YAAI,SAAS,aAAa;AACxB,eAAK,KAAK;AAAA,YACR,KAAK;AAAA,YACL,OAAO,EAAE,MAAM,6BAA6B,SAAS,KAAK;AAAA,YAC1D,UAAU;AAAA,UACZ,CAAC;AAAA,QACH;AAEA,YAAI,SAAS,MAAM;AACjB,eAAK,KAAK;AAAA,YACR,KAAK;AAAA,YACL,OAAO,EAAE,MAAM,6BAA6B,SAAS,OAAO,IAAI,EAAE;AAAA,YAClE,UAAU;AAAA,UACZ,CAAC;AAAA,QACH;AAEA,aAAK,KAAK;AAAA,UACR,KAAK;AAAA,UACL,OAAO,EAAE,MAAM,SAAS;AAAA,UACxB,UAAU;AAAA,UACV,UAAU;AAAA,QACZ,CAAC;AAED,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
package/package.json CHANGED
@@ -1,18 +1,35 @@
1
1
  {
2
2
  "name": "agent-react-devtools",
3
- "version": "0.1.0",
3
+ "version": "0.2.0-canary-20260210204855",
4
4
  "description": "CLI tool for AI agents to inspect React component trees and profile performance",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "agent-react-devtools": "./dist/cli.js"
8
8
  },
9
+ "exports": {
10
+ ".": "./dist/cli.js",
11
+ "./connect": {
12
+ "types": "./dist/connect.d.ts",
13
+ "import": "./dist/connect.js"
14
+ },
15
+ "./vite": {
16
+ "types": "./dist/vite.d.ts",
17
+ "import": "./dist/vite.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "README.md",
23
+ "CHANGELOG.md"
24
+ ],
9
25
  "scripts": {
10
26
  "build": "tsup",
11
27
  "dev": "tsup --watch",
12
28
  "test": "vitest run",
13
29
  "test:watch": "vitest",
14
30
  "typecheck": "tsc --noEmit",
15
- "lint": "tsc --noEmit"
31
+ "lint": "tsc --noEmit",
32
+ "prepublishOnly": "cp ../../README.md ."
16
33
  },
17
34
  "repository": {
18
35
  "type": "git",
@@ -23,13 +40,27 @@
23
40
  "devDependencies": {
24
41
  "@types/node": "^22.0.0",
25
42
  "@types/ws": "^8.5.0",
43
+ "react-devtools-core": "^6.1.0",
26
44
  "tsup": "^8.0.0",
27
45
  "typescript": "^5.5.0",
46
+ "vite": "^6.0.0",
28
47
  "vitest": "^2.0.0"
29
48
  },
30
49
  "dependencies": {
31
50
  "@modelcontextprotocol/sdk": "^1.12.0",
32
51
  "ws": "^8.18.0",
33
52
  "zod": "^3.24.0"
53
+ },
54
+ "peerDependencies": {
55
+ "react-devtools-core": ">=5.0.0",
56
+ "vite": ">=5.0.0"
57
+ },
58
+ "peerDependenciesMeta": {
59
+ "react-devtools-core": {
60
+ "optional": true
61
+ },
62
+ "vite": {
63
+ "optional": true
64
+ }
34
65
  }
35
66
  }
@@ -1,76 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
-
3
- // Inline the parseArgs function for testing (it's not exported from cli.ts)
4
- function parseArgs(argv: string[]): {
5
- command: string[];
6
- flags: Record<string, string | boolean>;
7
- } {
8
- const command: string[] = [];
9
- const flags: Record<string, string | boolean> = {};
10
-
11
- for (let i = 0; i < argv.length; i++) {
12
- const arg = argv[i];
13
- if (arg.startsWith('--')) {
14
- const key = arg.slice(2);
15
- const eqIdx = key.indexOf('=');
16
- if (eqIdx !== -1) {
17
- flags[key.slice(0, eqIdx)] = key.slice(eqIdx + 1);
18
- } else {
19
- const next = argv[i + 1];
20
- if (next && !next.startsWith('--')) {
21
- flags[key] = next;
22
- i++;
23
- } else {
24
- flags[key] = true;
25
- }
26
- }
27
- } else {
28
- command.push(arg);
29
- }
30
- }
31
- return { command, flags };
32
- }
33
-
34
- describe('CLI argument parser', () => {
35
- it('should parse simple commands', () => {
36
- const { command, flags } = parseArgs(['start']);
37
- expect(command).toEqual(['start']);
38
- expect(flags).toEqual({});
39
- });
40
-
41
- it('should parse commands with subcommands', () => {
42
- const { command } = parseArgs(['get', 'tree']);
43
- expect(command).toEqual(['get', 'tree']);
44
- });
45
-
46
- it('should parse --flag=value', () => {
47
- const { flags } = parseArgs(['start', '--port=8098']);
48
- expect(flags['port']).toBe('8098');
49
- });
50
-
51
- it('should parse --flag value', () => {
52
- const { flags } = parseArgs(['start', '--port', '8098']);
53
- expect(flags['port']).toBe('8098');
54
- });
55
-
56
- it('should parse boolean flags', () => {
57
- const { flags } = parseArgs(['find', 'User', '--exact']);
58
- expect(flags['exact']).toBe(true);
59
- });
60
-
61
- it('should parse mixed args', () => {
62
- const { command, flags } = parseArgs([
63
- 'profile',
64
- 'slow',
65
- '--limit',
66
- '5',
67
- ]);
68
- expect(command).toEqual(['profile', 'slow']);
69
- expect(flags['limit']).toBe('5');
70
- });
71
-
72
- it('should parse component id as positional arg', () => {
73
- const { command } = parseArgs(['get', 'component', '42']);
74
- expect(command).toEqual(['get', 'component', '42']);
75
- });
76
- });
@@ -1,229 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { ComponentTree } from '../component-tree.js';
3
-
4
- /**
5
- * Operations encoding reference (protocol v2):
6
- * [rendererID, rootFiberID, stringTableSize, ...stringTable, ...ops]
7
- *
8
- * String table: for each string, [length, ...charCodes]. String ID 0 = null.
9
- *
10
- * TREE_OPERATION_ADD (1):
11
- * 1, id, elementType, parentId, ownerID, displayNameStringID, keyStringID
12
- *
13
- * Element types (from react-devtools-shared/src/frontend/types.js):
14
- * CLASS=1, FUNCTION=5, FORWARD_REF=6, HOST=7, MEMO=8, PROFILER=10, ROOT=11, SUSPENSE=12
15
- */
16
-
17
- /** Build a string table and return [tableData, stringIdMap] */
18
- function buildStringTable(strings: string[]): [number[], Map<string, number>] {
19
- const idMap = new Map<string, number>();
20
- const data: number[] = [];
21
- for (const s of strings) {
22
- const id = idMap.size + 1; // 0 is reserved for null
23
- if (!idMap.has(s)) {
24
- idMap.set(s, id);
25
- data.push(s.length, ...Array.from(s).map((c) => c.charCodeAt(0)));
26
- }
27
- }
28
- return [data, idMap];
29
- }
30
-
31
- /** Build a complete operations array with string table */
32
- function buildOps(
33
- rendererID: number,
34
- rootID: number,
35
- strings: string[],
36
- opsFn: (strId: (s: string) => number) => number[],
37
- ): number[] {
38
- const [tableData, idMap] = buildStringTable(strings);
39
- const strId = (s: string) => idMap.get(s) || 0;
40
- const ops = opsFn(strId);
41
- return [rendererID, rootID, tableData.length, ...tableData, ...ops];
42
- }
43
-
44
- function addOp(
45
- id: number,
46
- elementType: number,
47
- parentId: number,
48
- displayNameStrId: number,
49
- keyStrId: number = 0,
50
- ): number[] {
51
- return [1, id, elementType, parentId, 0, displayNameStrId, keyStrId];
52
- }
53
-
54
- function removeOp(...ids: number[]): number[] {
55
- return [2, ids.length, ...ids];
56
- }
57
-
58
- function reorderOp(id: number, children: number[]): number[] {
59
- return [3, id, children.length, ...children];
60
- }
61
-
62
- describe('ComponentTree', () => {
63
- let tree: ComponentTree;
64
-
65
- beforeEach(() => {
66
- tree = new ComponentTree();
67
- });
68
-
69
- it('should add nodes from operations', () => {
70
- const ops = buildOps(1, 100, ['App', 'Header', 'Footer'], (s) => [
71
- ...addOp(1, 5, 0, s('App')), // Function component at root
72
- ...addOp(2, 8, 1, s('Header')), // Memo child
73
- ...addOp(3, 5, 1, s('Footer')), // Function child
74
- ]);
75
-
76
- tree.applyOperations(ops);
77
-
78
- expect(tree.getComponentCount()).toBe(3);
79
-
80
- const node1 = tree.getNode(1);
81
- expect(node1).toBeDefined();
82
- expect(node1!.displayName).toBe('App');
83
- expect(node1!.type).toBe('function');
84
- expect(node1!.children).toEqual([2, 3]);
85
-
86
- const node2 = tree.getNode(2);
87
- expect(node2!.displayName).toBe('Header');
88
- expect(node2!.type).toBe('memo');
89
- expect(node2!.parentId).toBe(1);
90
- });
91
-
92
- it('should handle keys', () => {
93
- const ops = buildOps(1, 100, ['List', 'Item', 'item-1', 'item-2'], (s) => [
94
- ...addOp(1, 5, 0, s('List')),
95
- ...addOp(2, 5, 1, s('Item'), s('item-1')),
96
- ...addOp(3, 5, 1, s('Item'), s('item-2')),
97
- ]);
98
-
99
- tree.applyOperations(ops);
100
-
101
- expect(tree.getNode(2)!.key).toBe('item-1');
102
- expect(tree.getNode(3)!.key).toBe('item-2');
103
- });
104
-
105
- it('should remove nodes', () => {
106
- const addOps = buildOps(1, 100, ['App', 'Child'], (s) => [
107
- ...addOp(1, 5, 0, s('App')),
108
- ...addOp(2, 5, 1, s('Child')),
109
- ]);
110
- tree.applyOperations(addOps);
111
- expect(tree.getComponentCount()).toBe(2);
112
-
113
- // Remove ops still need a string table (can be empty)
114
- const rmOps = [1, 100, 0, ...removeOp(2)];
115
- tree.applyOperations(rmOps);
116
- expect(tree.getComponentCount()).toBe(1);
117
- expect(tree.getNode(1)!.children).toEqual([]);
118
- });
119
-
120
- it('should reorder children', () => {
121
- const ops = buildOps(1, 100, ['App', 'A', 'B', 'C'], (s) => [
122
- ...addOp(1, 5, 0, s('App')),
123
- ...addOp(2, 5, 1, s('A')),
124
- ...addOp(3, 5, 1, s('B')),
125
- ...addOp(4, 5, 1, s('C')),
126
- ]);
127
- tree.applyOperations(ops);
128
- expect(tree.getNode(1)!.children).toEqual([2, 3, 4]);
129
-
130
- const reorderOps = [1, 100, 0, ...reorderOp(1, [4, 2, 3])];
131
- tree.applyOperations(reorderOps);
132
- expect(tree.getNode(1)!.children).toEqual([4, 2, 3]);
133
- });
134
-
135
- it('should find by name (partial match)', () => {
136
- const ops = buildOps(1, 100, ['App', 'UserProfile', 'UserCard', 'Footer'], (s) => [
137
- ...addOp(1, 5, 0, s('App')),
138
- ...addOp(2, 5, 1, s('UserProfile')),
139
- ...addOp(3, 5, 1, s('UserCard')),
140
- ...addOp(4, 5, 1, s('Footer')),
141
- ]);
142
- tree.applyOperations(ops);
143
-
144
- const results = tree.findByName('user');
145
- expect(results).toHaveLength(2);
146
- expect(results.map((r) => r.displayName).sort()).toEqual([
147
- 'UserCard',
148
- 'UserProfile',
149
- ]);
150
- });
151
-
152
- it('should find by name (exact match)', () => {
153
- const ops = buildOps(1, 100, ['App', 'UserProfile', 'UserCard'], (s) => [
154
- ...addOp(1, 5, 0, s('App')),
155
- ...addOp(2, 5, 1, s('UserProfile')),
156
- ...addOp(3, 5, 1, s('UserCard')),
157
- ]);
158
- tree.applyOperations(ops);
159
-
160
- const results = tree.findByName('UserProfile', true);
161
- expect(results).toHaveLength(1);
162
- expect(results[0].displayName).toBe('UserProfile');
163
- });
164
-
165
- it('should get count by type', () => {
166
- const ops = buildOps(1, 100, ['App', 'MemoComp', 'FuncComp', 'div'], (s) => [
167
- ...addOp(1, 5, 0, s('App')), // function
168
- ...addOp(2, 8, 1, s('MemoComp')), // memo
169
- ...addOp(3, 5, 1, s('FuncComp')), // function
170
- ...addOp(4, 7, 1, s('div')), // host
171
- ]);
172
- tree.applyOperations(ops);
173
-
174
- const counts = tree.getCountByType();
175
- expect(counts['function']).toBe(2);
176
- expect(counts['memo']).toBe(1);
177
- expect(counts['host']).toBe(1);
178
- });
179
-
180
- it('should get tree with depth limit', () => {
181
- const ops = buildOps(1, 100, ['App', 'Level1', 'Level2', 'Level3'], (s) => [
182
- ...addOp(1, 5, 0, s('App')),
183
- ...addOp(2, 5, 1, s('Level1')),
184
- ...addOp(3, 5, 2, s('Level2')),
185
- ...addOp(4, 5, 3, s('Level3')),
186
- ]);
187
- tree.applyOperations(ops);
188
-
189
- const fullTree = tree.getTree();
190
- expect(fullTree).toHaveLength(4);
191
-
192
- const shallow = tree.getTree(1);
193
- expect(shallow).toHaveLength(2);
194
- expect(shallow.map((n) => n.displayName)).toEqual(['App', 'Level1']);
195
- });
196
-
197
- it('should handle empty operations', () => {
198
- tree.applyOperations([]);
199
- expect(tree.getComponentCount()).toBe(0);
200
-
201
- tree.applyOperations([1]);
202
- expect(tree.getComponentCount()).toBe(0);
203
- });
204
-
205
- it('should handle all element types', () => {
206
- const ops = buildOps(
207
- 1, 100,
208
- ['ClassComp', 'FuncComp', 'ForwardRefComp', 'HostComp', 'MemoComp', 'ProfilerComp', 'SuspenseComp'],
209
- (s) => [
210
- ...addOp(1, 1, 0, s('ClassComp')), // class = 1
211
- ...addOp(2, 5, 1, s('FuncComp')), // function = 5
212
- ...addOp(3, 6, 1, s('ForwardRefComp')), // forwardRef = 6
213
- ...addOp(4, 7, 1, s('HostComp')), // host = 7
214
- ...addOp(5, 8, 1, s('MemoComp')), // memo = 8
215
- ...addOp(6, 10, 1, s('ProfilerComp')), // profiler = 10
216
- ...addOp(7, 12, 1, s('SuspenseComp')), // suspense = 12
217
- ],
218
- );
219
- tree.applyOperations(ops);
220
-
221
- expect(tree.getNode(1)!.type).toBe('class');
222
- expect(tree.getNode(2)!.type).toBe('function');
223
- expect(tree.getNode(3)!.type).toBe('forwardRef');
224
- expect(tree.getNode(4)!.type).toBe('host');
225
- expect(tree.getNode(5)!.type).toBe('memo');
226
- expect(tree.getNode(6)!.type).toBe('profiler');
227
- expect(tree.getNode(7)!.type).toBe('suspense');
228
- });
229
- });