react-dev-panel 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/LICENSE +21 -0
- package/README.md +130 -0
- package/dist/adapters/next.cjs +95 -0
- package/dist/adapters/next.cjs.map +1 -0
- package/dist/adapters/next.d.cts +17 -0
- package/dist/adapters/next.d.ts +17 -0
- package/dist/adapters/next.js +65 -0
- package/dist/adapters/next.js.map +1 -0
- package/dist/adapters/server.cjs +72 -0
- package/dist/adapters/server.cjs.map +1 -0
- package/dist/adapters/server.d.cts +24 -0
- package/dist/adapters/server.d.ts +24 -0
- package/dist/adapters/server.js +4 -0
- package/dist/adapters/server.js.map +1 -0
- package/dist/adapters/vite.cjs +301 -0
- package/dist/adapters/vite.cjs.map +1 -0
- package/dist/adapters/vite.d.cts +35 -0
- package/dist/adapters/vite.d.ts +35 -0
- package/dist/adapters/vite.js +31 -0
- package/dist/adapters/vite.js.map +1 -0
- package/dist/chunk-2ZAPVMUL.js +25 -0
- package/dist/chunk-2ZAPVMUL.js.map +1 -0
- package/dist/chunk-MAYMGQIM.js +64 -0
- package/dist/chunk-MAYMGQIM.js.map +1 -0
- package/dist/chunk-XZ4DPO52.js +190 -0
- package/dist/chunk-XZ4DPO52.js.map +1 -0
- package/dist/cli/index.cjs +221 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +35 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/generate-UK65K5BU.js +3 -0
- package/dist/generate-UK65K5BU.js.map +1 -0
- package/dist/index.cjs +1745 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +53 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +1711 -0
- package/dist/index.js.map +1 -0
- package/dist/server-open-B4FK0jQF.d.cts +92 -0
- package/dist/server-open-B4FK0jQF.d.ts +92 -0
- package/package.json +77 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { resolve, sep, relative, join } from 'path';
|
|
2
|
+
import { readFileSync, mkdirSync, writeFileSync, readdirSync, statSync } from 'fs';
|
|
3
|
+
|
|
4
|
+
// src/cli/generate.ts
|
|
5
|
+
var SKIP_DIR = /* @__PURE__ */ new Set(["node_modules", "dist", ".next", ".turbo", "__tests__", "generated", ".git"]);
|
|
6
|
+
var isComponentFile = (f) => f.endsWith(".tsx") && !f.endsWith(".test.tsx") && !f.endsWith(".stories.tsx");
|
|
7
|
+
var isPascal = (name) => typeof name === "string" && /^[A-Z]/.test(name);
|
|
8
|
+
function collectFiles(dir, out) {
|
|
9
|
+
let entries;
|
|
10
|
+
try {
|
|
11
|
+
entries = readdirSync(dir);
|
|
12
|
+
} catch {
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
if (SKIP_DIR.has(entry)) continue;
|
|
17
|
+
const full = join(dir, entry);
|
|
18
|
+
let st;
|
|
19
|
+
try {
|
|
20
|
+
st = statSync(full);
|
|
21
|
+
} catch {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (st.isDirectory()) collectFiles(full, out);
|
|
25
|
+
else if (isComponentFile(entry)) out.push(full);
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
async function generateComponentGraph(options = {}) {
|
|
30
|
+
let ts;
|
|
31
|
+
try {
|
|
32
|
+
ts = (await import('typescript')).default ?? await import('typescript');
|
|
33
|
+
} catch {
|
|
34
|
+
throw new Error(
|
|
35
|
+
"[dev-panel-graph] 'typescript' is required to generate the component graph. Install it: npm i -D typescript"
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
const root = resolve(options.root ?? process.cwd());
|
|
39
|
+
const scanDirs = (options.scan ?? ["src"]).map((d) => resolve(root, d));
|
|
40
|
+
const outFile = resolve(root, options.out ?? ".dev-panel/component-graph.json");
|
|
41
|
+
const toRel = (abs) => relative(root, abs).split(sep).join("/");
|
|
42
|
+
const files = scanDirs.flatMap((d) => collectFiles(d, []));
|
|
43
|
+
const containsJsx = (node) => {
|
|
44
|
+
let found = false;
|
|
45
|
+
const visit = (n) => {
|
|
46
|
+
if (found) return;
|
|
47
|
+
if (ts.isJsxElement(n) || ts.isJsxSelfClosingElement(n) || ts.isJsxFragment(n)) {
|
|
48
|
+
found = true;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
ts.forEachChild(n, visit);
|
|
52
|
+
};
|
|
53
|
+
visit(node);
|
|
54
|
+
return found;
|
|
55
|
+
};
|
|
56
|
+
const jsxRoot = (tag) => {
|
|
57
|
+
if (ts.isIdentifier(tag)) return tag.text;
|
|
58
|
+
if (ts.isPropertyAccessExpression(tag)) {
|
|
59
|
+
let expr = tag.expression;
|
|
60
|
+
while (ts.isPropertyAccessExpression(expr)) expr = expr.expression;
|
|
61
|
+
if (ts.isIdentifier(expr)) return expr.text;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
};
|
|
65
|
+
const renderedIn = (node) => {
|
|
66
|
+
const names = /* @__PURE__ */ new Set();
|
|
67
|
+
const visit = (n) => {
|
|
68
|
+
if (ts.isJsxOpeningElement(n) || ts.isJsxSelfClosingElement(n)) {
|
|
69
|
+
const r = jsxRoot(n.tagName);
|
|
70
|
+
if (r && isPascal(r)) names.add(r);
|
|
71
|
+
}
|
|
72
|
+
ts.forEachChild(n, visit);
|
|
73
|
+
};
|
|
74
|
+
visit(node);
|
|
75
|
+
return names;
|
|
76
|
+
};
|
|
77
|
+
const importedIn = (sf) => {
|
|
78
|
+
const names = /* @__PURE__ */ new Set();
|
|
79
|
+
for (const stmt of sf.statements) {
|
|
80
|
+
if (!ts.isImportDeclaration(stmt) || !stmt.importClause) continue;
|
|
81
|
+
const clause = stmt.importClause;
|
|
82
|
+
if (clause.isTypeOnly) continue;
|
|
83
|
+
if (clause.name && isPascal(clause.name.text)) names.add(clause.name.text);
|
|
84
|
+
const b = clause.namedBindings;
|
|
85
|
+
if (b && ts.isNamedImports(b)) {
|
|
86
|
+
for (const spec of b.elements) {
|
|
87
|
+
if (spec.isTypeOnly) continue;
|
|
88
|
+
if (isPascal(spec.name.text)) names.add(spec.name.text);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return names;
|
|
93
|
+
};
|
|
94
|
+
const hasExport = (node) => !!(ts.canHaveModifiers(node) ? ts.getModifiers(node) : void 0)?.some(
|
|
95
|
+
(m) => m.kind === ts.SyntaxKind.ExportKeyword
|
|
96
|
+
);
|
|
97
|
+
const routeFor = (absPath) => {
|
|
98
|
+
const rel = toRel(absPath);
|
|
99
|
+
const marker = "/app/";
|
|
100
|
+
const idx = rel.indexOf(marker);
|
|
101
|
+
if (idx === -1 || !rel.endsWith("/page.tsx")) return null;
|
|
102
|
+
const inner = rel.slice(idx + marker.length, -"/page.tsx".length);
|
|
103
|
+
const segments = inner.split("/").filter(Boolean).filter((s) => !(s.startsWith("(") && s.endsWith(")"))).map((s) => s.replace(/^\[\.\.\.(.+)\]$/, "*$1").replace(/^\[(.+)\]$/, ":$1"));
|
|
104
|
+
return "/" + segments.join("/");
|
|
105
|
+
};
|
|
106
|
+
const nodesByName = /* @__PURE__ */ new Map();
|
|
107
|
+
const edgeSet = /* @__PURE__ */ new Set();
|
|
108
|
+
const edges = [];
|
|
109
|
+
let duplicateNames = 0;
|
|
110
|
+
const addEdge = (from, to, type) => {
|
|
111
|
+
if (!from || !to || from === to) return;
|
|
112
|
+
const key = `${from}|${to}|${type}`;
|
|
113
|
+
if (edgeSet.has(key)) return;
|
|
114
|
+
edgeSet.add(key);
|
|
115
|
+
edges.push({ from, to, type });
|
|
116
|
+
};
|
|
117
|
+
const addNode = (name, sf, decl, exported) => {
|
|
118
|
+
if (nodesByName.has(name)) {
|
|
119
|
+
duplicateNames += 1;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const pos = sf.getLineAndCharacterOfPosition(decl.getStart(sf));
|
|
123
|
+
nodesByName.set(name, {
|
|
124
|
+
id: name,
|
|
125
|
+
name,
|
|
126
|
+
filePath: toRel(sf.fileName),
|
|
127
|
+
line: pos.line + 1,
|
|
128
|
+
column: pos.character + 1,
|
|
129
|
+
type: "component",
|
|
130
|
+
exported
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
for (const file of files) {
|
|
134
|
+
const text = readFileSync(file, "utf8");
|
|
135
|
+
const sf = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
|
|
136
|
+
const localComponents = [];
|
|
137
|
+
for (const stmt of sf.statements) {
|
|
138
|
+
if (ts.isFunctionDeclaration(stmt) && stmt.name && isPascal(stmt.name.text) && stmt.body) {
|
|
139
|
+
if (containsJsx(stmt.body)) {
|
|
140
|
+
addNode(stmt.name.text, sf, stmt, hasExport(stmt));
|
|
141
|
+
localComponents.push({ name: stmt.name.text, body: stmt.body });
|
|
142
|
+
}
|
|
143
|
+
} else if (ts.isVariableStatement(stmt)) {
|
|
144
|
+
const exported = hasExport(stmt);
|
|
145
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
146
|
+
if (!ts.isIdentifier(decl.name) || !isPascal(decl.name.text) || !decl.initializer) continue;
|
|
147
|
+
const init = decl.initializer;
|
|
148
|
+
if ((ts.isArrowFunction(init) || ts.isFunctionExpression(init)) && containsJsx(init)) {
|
|
149
|
+
addNode(decl.name.text, sf, decl, exported);
|
|
150
|
+
localComponents.push({ name: decl.name.text, body: init });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (localComponents.length === 0) continue;
|
|
156
|
+
const base = file.split(sep).pop().replace(/\.tsx$/, "");
|
|
157
|
+
const primary = localComponents.find(
|
|
158
|
+
(c) => c.name.toLowerCase() === base.replace(/[-_]/g, "").toLowerCase()
|
|
159
|
+
) ?? localComponents[0];
|
|
160
|
+
for (const comp of localComponents) {
|
|
161
|
+
for (const rendered of renderedIn(comp.body)) addEdge(comp.name, rendered, "renders");
|
|
162
|
+
}
|
|
163
|
+
for (const imported of importedIn(sf)) addEdge(primary.name, imported, "imports");
|
|
164
|
+
const route = routeFor(file);
|
|
165
|
+
if (route) {
|
|
166
|
+
const routeId = `route:${route}`;
|
|
167
|
+
if (!nodesByName.has(routeId)) {
|
|
168
|
+
nodesByName.set(routeId, { id: routeId, name: route, filePath: toRel(file), type: "route", route });
|
|
169
|
+
}
|
|
170
|
+
const primaryNode = nodesByName.get(primary.name);
|
|
171
|
+
if (primaryNode && !primaryNode.route) primaryNode.route = route;
|
|
172
|
+
addEdge(routeId, primary.name, "route");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const known = new Set(nodesByName.keys());
|
|
176
|
+
const prunedEdges = edges.filter((e) => known.has(e.from) && known.has(e.to));
|
|
177
|
+
const graph = {
|
|
178
|
+
root,
|
|
179
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
180
|
+
nodes: [...nodesByName.values()],
|
|
181
|
+
edges: prunedEdges
|
|
182
|
+
};
|
|
183
|
+
mkdirSync(resolve(outFile, ".."), { recursive: true });
|
|
184
|
+
writeFileSync(outFile, JSON.stringify(graph, null, 2), "utf8");
|
|
185
|
+
return { graph, outFile, fileCount: files.length, duplicateNames };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export { generateComponentGraph };
|
|
189
|
+
//# sourceMappingURL=chunk-XZ4DPO52.js.map
|
|
190
|
+
//# sourceMappingURL=chunk-XZ4DPO52.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/generate.ts"],"names":[],"mappings":"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","file":"chunk-XZ4DPO52.js","sourcesContent":["/**\n * Static component-graph generator using the TypeScript Compiler API (no ts-morph). Scans .tsx\n * files for React components and emits nodes + edges (renders / imports / route). Shared by the\n * `dev-panel-graph` CLI and the Vite adapter. `typescript` is an optional peer dep — imported\n * dynamically with a friendly error if absent.\n */\nimport { join, sep, resolve, relative } from 'node:path';\nimport { readdirSync, statSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';\n\nimport type { ComponentGraph, ComponentGraphNode, ComponentGraphEdge } from '../core/graph-types';\n\nexport interface GenerateOptions {\n /** Repo root (absolute). Default: process.cwd(). */\n root?: string;\n /** Directories to scan, relative to root. Default: ['src']. */\n scan?: string[];\n /** Output file, relative to root. Default: '.dev-panel/component-graph.json'. */\n out?: string;\n}\n\nexport interface GenerateResult {\n graph: ComponentGraph;\n outFile: string;\n fileCount: number;\n duplicateNames: number;\n}\n\nconst SKIP_DIR = new Set(['node_modules', 'dist', '.next', '.turbo', '__tests__', 'generated', '.git']);\nconst isComponentFile = (f: string) =>\n f.endsWith('.tsx') && !f.endsWith('.test.tsx') && !f.endsWith('.stories.tsx');\nconst isPascal = (name: unknown): name is string => typeof name === 'string' && /^[A-Z]/.test(name);\n\nfunction collectFiles(dir: string, out: string[]): string[] {\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return out;\n }\n for (const entry of entries) {\n if (SKIP_DIR.has(entry)) continue;\n const full = join(dir, entry);\n let st;\n try {\n st = statSync(full);\n } catch {\n continue;\n }\n if (st.isDirectory()) collectFiles(full, out);\n else if (isComponentFile(entry)) out.push(full);\n }\n return out;\n}\n\nexport async function generateComponentGraph(options: GenerateOptions = {}): Promise<GenerateResult> {\n let ts: typeof import('typescript');\n try {\n ts = (await import('typescript')).default ?? (await import('typescript'));\n } catch {\n throw new Error(\n \"[dev-panel-graph] 'typescript' is required to generate the component graph. Install it: npm i -D typescript\",\n );\n }\n\n const root = resolve(options.root ?? process.cwd());\n const scanDirs = (options.scan ?? ['src']).map((d) => resolve(root, d));\n const outFile = resolve(root, options.out ?? '.dev-panel/component-graph.json');\n const toRel = (abs: string) => relative(root, abs).split(sep).join('/');\n\n const files = scanDirs.flatMap((d) => collectFiles(d, []));\n\n const containsJsx = (node: import('typescript').Node): boolean => {\n let found = false;\n const visit = (n: import('typescript').Node) => {\n if (found) return;\n if (ts.isJsxElement(n) || ts.isJsxSelfClosingElement(n) || ts.isJsxFragment(n)) {\n found = true;\n return;\n }\n ts.forEachChild(n, visit);\n };\n visit(node);\n return found;\n };\n\n const jsxRoot = (tag: import('typescript').JsxTagNameExpression): string | null => {\n if (ts.isIdentifier(tag)) return tag.text;\n if (ts.isPropertyAccessExpression(tag)) {\n let expr: import('typescript').Expression = tag.expression;\n while (ts.isPropertyAccessExpression(expr)) expr = expr.expression;\n if (ts.isIdentifier(expr)) return expr.text;\n }\n return null;\n };\n\n const renderedIn = (node: import('typescript').Node): Set<string> => {\n const names = new Set<string>();\n const visit = (n: import('typescript').Node) => {\n if (ts.isJsxOpeningElement(n) || ts.isJsxSelfClosingElement(n)) {\n const r = jsxRoot(n.tagName);\n if (r && isPascal(r)) names.add(r);\n }\n ts.forEachChild(n, visit);\n };\n visit(node);\n return names;\n };\n\n const importedIn = (sf: import('typescript').SourceFile): Set<string> => {\n const names = new Set<string>();\n for (const stmt of sf.statements) {\n if (!ts.isImportDeclaration(stmt) || !stmt.importClause) continue;\n const clause = stmt.importClause;\n if (clause.isTypeOnly) continue;\n if (clause.name && isPascal(clause.name.text)) names.add(clause.name.text);\n const b = clause.namedBindings;\n if (b && ts.isNamedImports(b)) {\n for (const spec of b.elements) {\n if (spec.isTypeOnly) continue;\n if (isPascal(spec.name.text)) names.add(spec.name.text);\n }\n }\n }\n return names;\n };\n\n const hasExport = (node: import('typescript').Node): boolean =>\n !!(ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined)?.some(\n (m) => m.kind === ts.SyntaxKind.ExportKeyword,\n );\n\n const routeFor = (absPath: string): string | null => {\n const rel = toRel(absPath);\n const marker = '/app/';\n const idx = rel.indexOf(marker);\n if (idx === -1 || !rel.endsWith('/page.tsx')) return null;\n const inner = rel.slice(idx + marker.length, -'/page.tsx'.length);\n const segments = inner\n .split('/')\n .filter(Boolean)\n .filter((s) => !(s.startsWith('(') && s.endsWith(')')))\n .map((s) => s.replace(/^\\[\\.\\.\\.(.+)\\]$/, '*$1').replace(/^\\[(.+)\\]$/, ':$1'));\n return '/' + segments.join('/');\n };\n\n const nodesByName = new Map<string, ComponentGraphNode>();\n const edgeSet = new Set<string>();\n const edges: ComponentGraphEdge[] = [];\n let duplicateNames = 0;\n\n const addEdge = (from: string, to: string, type: ComponentGraphEdge['type']) => {\n if (!from || !to || from === to) return;\n const key = `${from}|${to}|${type}`;\n if (edgeSet.has(key)) return;\n edgeSet.add(key);\n edges.push({ from, to, type });\n };\n\n const addNode = (\n name: string,\n sf: import('typescript').SourceFile,\n decl: import('typescript').Node,\n exported: boolean,\n ) => {\n if (nodesByName.has(name)) {\n duplicateNames += 1;\n return;\n }\n const pos = sf.getLineAndCharacterOfPosition(decl.getStart(sf));\n nodesByName.set(name, {\n id: name,\n name,\n filePath: toRel(sf.fileName),\n line: pos.line + 1,\n column: pos.character + 1,\n type: 'component',\n exported,\n });\n };\n\n for (const file of files) {\n const text = readFileSync(file, 'utf8');\n const sf = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);\n const localComponents: Array<{ name: string; body: import('typescript').Node }> = [];\n\n for (const stmt of sf.statements) {\n if (ts.isFunctionDeclaration(stmt) && stmt.name && isPascal(stmt.name.text) && stmt.body) {\n if (containsJsx(stmt.body)) {\n addNode(stmt.name.text, sf, stmt, hasExport(stmt));\n localComponents.push({ name: stmt.name.text, body: stmt.body });\n }\n } else if (ts.isVariableStatement(stmt)) {\n const exported = hasExport(stmt);\n for (const decl of stmt.declarationList.declarations) {\n if (!ts.isIdentifier(decl.name) || !isPascal(decl.name.text) || !decl.initializer) continue;\n const init = decl.initializer;\n if ((ts.isArrowFunction(init) || ts.isFunctionExpression(init)) && containsJsx(init)) {\n addNode(decl.name.text, sf, decl, exported);\n localComponents.push({ name: decl.name.text, body: init });\n }\n }\n }\n }\n\n if (localComponents.length === 0) continue;\n\n const base = file.split(sep).pop()!.replace(/\\.tsx$/, '');\n const primary =\n localComponents.find(\n (c) => c.name.toLowerCase() === base.replace(/[-_]/g, '').toLowerCase(),\n ) ?? localComponents[0];\n\n for (const comp of localComponents) {\n for (const rendered of renderedIn(comp.body)) addEdge(comp.name, rendered, 'renders');\n }\n for (const imported of importedIn(sf)) addEdge(primary.name, imported, 'imports');\n\n const route = routeFor(file);\n if (route) {\n const routeId = `route:${route}`;\n if (!nodesByName.has(routeId)) {\n nodesByName.set(routeId, { id: routeId, name: route, filePath: toRel(file), type: 'route', route });\n }\n const primaryNode = nodesByName.get(primary.name);\n if (primaryNode && !primaryNode.route) primaryNode.route = route;\n addEdge(routeId, primary.name, 'route');\n }\n }\n\n const known = new Set(nodesByName.keys());\n const prunedEdges = edges.filter((e) => known.has(e.from) && known.has(e.to));\n\n const graph: ComponentGraph = {\n root,\n generatedAt: new Date().toISOString(),\n nodes: [...nodesByName.values()],\n edges: prunedEdges,\n };\n\n mkdirSync(resolve(outFile, '..'), { recursive: true });\n writeFileSync(outFile, JSON.stringify(graph, null, 2), 'utf8');\n\n return { graph, outFile, fileCount: files.length, duplicateNames };\n}\n"]}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var path = require('path');
|
|
5
|
+
var fs = require('fs');
|
|
6
|
+
|
|
7
|
+
var SKIP_DIR = /* @__PURE__ */ new Set(["node_modules", "dist", ".next", ".turbo", "__tests__", "generated", ".git"]);
|
|
8
|
+
var isComponentFile = (f) => f.endsWith(".tsx") && !f.endsWith(".test.tsx") && !f.endsWith(".stories.tsx");
|
|
9
|
+
var isPascal = (name) => typeof name === "string" && /^[A-Z]/.test(name);
|
|
10
|
+
function collectFiles(dir, out) {
|
|
11
|
+
let entries;
|
|
12
|
+
try {
|
|
13
|
+
entries = fs.readdirSync(dir);
|
|
14
|
+
} catch {
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
for (const entry of entries) {
|
|
18
|
+
if (SKIP_DIR.has(entry)) continue;
|
|
19
|
+
const full = path.join(dir, entry);
|
|
20
|
+
let st;
|
|
21
|
+
try {
|
|
22
|
+
st = fs.statSync(full);
|
|
23
|
+
} catch {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (st.isDirectory()) collectFiles(full, out);
|
|
27
|
+
else if (isComponentFile(entry)) out.push(full);
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
async function generateComponentGraph(options = {}) {
|
|
32
|
+
let ts;
|
|
33
|
+
try {
|
|
34
|
+
ts = (await import('typescript')).default ?? await import('typescript');
|
|
35
|
+
} catch {
|
|
36
|
+
throw new Error(
|
|
37
|
+
"[dev-panel-graph] 'typescript' is required to generate the component graph. Install it: npm i -D typescript"
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
const root = path.resolve(options.root ?? process.cwd());
|
|
41
|
+
const scanDirs = (options.scan ?? ["src"]).map((d) => path.resolve(root, d));
|
|
42
|
+
const outFile = path.resolve(root, options.out ?? ".dev-panel/component-graph.json");
|
|
43
|
+
const toRel = (abs) => path.relative(root, abs).split(path.sep).join("/");
|
|
44
|
+
const files = scanDirs.flatMap((d) => collectFiles(d, []));
|
|
45
|
+
const containsJsx = (node) => {
|
|
46
|
+
let found = false;
|
|
47
|
+
const visit = (n) => {
|
|
48
|
+
if (found) return;
|
|
49
|
+
if (ts.isJsxElement(n) || ts.isJsxSelfClosingElement(n) || ts.isJsxFragment(n)) {
|
|
50
|
+
found = true;
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
ts.forEachChild(n, visit);
|
|
54
|
+
};
|
|
55
|
+
visit(node);
|
|
56
|
+
return found;
|
|
57
|
+
};
|
|
58
|
+
const jsxRoot = (tag) => {
|
|
59
|
+
if (ts.isIdentifier(tag)) return tag.text;
|
|
60
|
+
if (ts.isPropertyAccessExpression(tag)) {
|
|
61
|
+
let expr = tag.expression;
|
|
62
|
+
while (ts.isPropertyAccessExpression(expr)) expr = expr.expression;
|
|
63
|
+
if (ts.isIdentifier(expr)) return expr.text;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
};
|
|
67
|
+
const renderedIn = (node) => {
|
|
68
|
+
const names = /* @__PURE__ */ new Set();
|
|
69
|
+
const visit = (n) => {
|
|
70
|
+
if (ts.isJsxOpeningElement(n) || ts.isJsxSelfClosingElement(n)) {
|
|
71
|
+
const r = jsxRoot(n.tagName);
|
|
72
|
+
if (r && isPascal(r)) names.add(r);
|
|
73
|
+
}
|
|
74
|
+
ts.forEachChild(n, visit);
|
|
75
|
+
};
|
|
76
|
+
visit(node);
|
|
77
|
+
return names;
|
|
78
|
+
};
|
|
79
|
+
const importedIn = (sf) => {
|
|
80
|
+
const names = /* @__PURE__ */ new Set();
|
|
81
|
+
for (const stmt of sf.statements) {
|
|
82
|
+
if (!ts.isImportDeclaration(stmt) || !stmt.importClause) continue;
|
|
83
|
+
const clause = stmt.importClause;
|
|
84
|
+
if (clause.isTypeOnly) continue;
|
|
85
|
+
if (clause.name && isPascal(clause.name.text)) names.add(clause.name.text);
|
|
86
|
+
const b = clause.namedBindings;
|
|
87
|
+
if (b && ts.isNamedImports(b)) {
|
|
88
|
+
for (const spec of b.elements) {
|
|
89
|
+
if (spec.isTypeOnly) continue;
|
|
90
|
+
if (isPascal(spec.name.text)) names.add(spec.name.text);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return names;
|
|
95
|
+
};
|
|
96
|
+
const hasExport = (node) => !!(ts.canHaveModifiers(node) ? ts.getModifiers(node) : void 0)?.some(
|
|
97
|
+
(m) => m.kind === ts.SyntaxKind.ExportKeyword
|
|
98
|
+
);
|
|
99
|
+
const routeFor = (absPath) => {
|
|
100
|
+
const rel = toRel(absPath);
|
|
101
|
+
const marker = "/app/";
|
|
102
|
+
const idx = rel.indexOf(marker);
|
|
103
|
+
if (idx === -1 || !rel.endsWith("/page.tsx")) return null;
|
|
104
|
+
const inner = rel.slice(idx + marker.length, -"/page.tsx".length);
|
|
105
|
+
const segments = inner.split("/").filter(Boolean).filter((s) => !(s.startsWith("(") && s.endsWith(")"))).map((s) => s.replace(/^\[\.\.\.(.+)\]$/, "*$1").replace(/^\[(.+)\]$/, ":$1"));
|
|
106
|
+
return "/" + segments.join("/");
|
|
107
|
+
};
|
|
108
|
+
const nodesByName = /* @__PURE__ */ new Map();
|
|
109
|
+
const edgeSet = /* @__PURE__ */ new Set();
|
|
110
|
+
const edges = [];
|
|
111
|
+
let duplicateNames = 0;
|
|
112
|
+
const addEdge = (from, to, type) => {
|
|
113
|
+
if (!from || !to || from === to) return;
|
|
114
|
+
const key = `${from}|${to}|${type}`;
|
|
115
|
+
if (edgeSet.has(key)) return;
|
|
116
|
+
edgeSet.add(key);
|
|
117
|
+
edges.push({ from, to, type });
|
|
118
|
+
};
|
|
119
|
+
const addNode = (name, sf, decl, exported) => {
|
|
120
|
+
if (nodesByName.has(name)) {
|
|
121
|
+
duplicateNames += 1;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const pos = sf.getLineAndCharacterOfPosition(decl.getStart(sf));
|
|
125
|
+
nodesByName.set(name, {
|
|
126
|
+
id: name,
|
|
127
|
+
name,
|
|
128
|
+
filePath: toRel(sf.fileName),
|
|
129
|
+
line: pos.line + 1,
|
|
130
|
+
column: pos.character + 1,
|
|
131
|
+
type: "component",
|
|
132
|
+
exported
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
for (const file of files) {
|
|
136
|
+
const text = fs.readFileSync(file, "utf8");
|
|
137
|
+
const sf = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
|
|
138
|
+
const localComponents = [];
|
|
139
|
+
for (const stmt of sf.statements) {
|
|
140
|
+
if (ts.isFunctionDeclaration(stmt) && stmt.name && isPascal(stmt.name.text) && stmt.body) {
|
|
141
|
+
if (containsJsx(stmt.body)) {
|
|
142
|
+
addNode(stmt.name.text, sf, stmt, hasExport(stmt));
|
|
143
|
+
localComponents.push({ name: stmt.name.text, body: stmt.body });
|
|
144
|
+
}
|
|
145
|
+
} else if (ts.isVariableStatement(stmt)) {
|
|
146
|
+
const exported = hasExport(stmt);
|
|
147
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
148
|
+
if (!ts.isIdentifier(decl.name) || !isPascal(decl.name.text) || !decl.initializer) continue;
|
|
149
|
+
const init = decl.initializer;
|
|
150
|
+
if ((ts.isArrowFunction(init) || ts.isFunctionExpression(init)) && containsJsx(init)) {
|
|
151
|
+
addNode(decl.name.text, sf, decl, exported);
|
|
152
|
+
localComponents.push({ name: decl.name.text, body: init });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (localComponents.length === 0) continue;
|
|
158
|
+
const base = file.split(path.sep).pop().replace(/\.tsx$/, "");
|
|
159
|
+
const primary = localComponents.find(
|
|
160
|
+
(c) => c.name.toLowerCase() === base.replace(/[-_]/g, "").toLowerCase()
|
|
161
|
+
) ?? localComponents[0];
|
|
162
|
+
for (const comp of localComponents) {
|
|
163
|
+
for (const rendered of renderedIn(comp.body)) addEdge(comp.name, rendered, "renders");
|
|
164
|
+
}
|
|
165
|
+
for (const imported of importedIn(sf)) addEdge(primary.name, imported, "imports");
|
|
166
|
+
const route = routeFor(file);
|
|
167
|
+
if (route) {
|
|
168
|
+
const routeId = `route:${route}`;
|
|
169
|
+
if (!nodesByName.has(routeId)) {
|
|
170
|
+
nodesByName.set(routeId, { id: routeId, name: route, filePath: toRel(file), type: "route", route });
|
|
171
|
+
}
|
|
172
|
+
const primaryNode = nodesByName.get(primary.name);
|
|
173
|
+
if (primaryNode && !primaryNode.route) primaryNode.route = route;
|
|
174
|
+
addEdge(routeId, primary.name, "route");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const known = new Set(nodesByName.keys());
|
|
178
|
+
const prunedEdges = edges.filter((e) => known.has(e.from) && known.has(e.to));
|
|
179
|
+
const graph = {
|
|
180
|
+
root,
|
|
181
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
182
|
+
nodes: [...nodesByName.values()],
|
|
183
|
+
edges: prunedEdges
|
|
184
|
+
};
|
|
185
|
+
fs.mkdirSync(path.resolve(outFile, ".."), { recursive: true });
|
|
186
|
+
fs.writeFileSync(outFile, JSON.stringify(graph, null, 2), "utf8");
|
|
187
|
+
return { graph, outFile, fileCount: files.length, duplicateNames };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/cli/index.ts
|
|
191
|
+
function parseArgs(argv) {
|
|
192
|
+
const out = {};
|
|
193
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
194
|
+
const arg = argv[i];
|
|
195
|
+
const next = argv[i + 1];
|
|
196
|
+
if (arg === "--root" && next) out.root = next, i += 1;
|
|
197
|
+
else if (arg === "--scan" && next) out.scan = next.split(",").map((s) => s.trim()).filter(Boolean), i += 1;
|
|
198
|
+
else if (arg === "--out" && next) out.out = next, i += 1;
|
|
199
|
+
else if (arg === "--help" || arg === "-h") {
|
|
200
|
+
console.log(
|
|
201
|
+
"Usage: dev-panel-graph [--root <dir>] [--scan <dir,dir>] [--out <file>]\n --root repo root (default: cwd)\n --scan dirs to scan, comma-separated (default: src)\n --out output JSON path (default: .dev-panel/component-graph.json)"
|
|
202
|
+
);
|
|
203
|
+
process.exit(0);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
async function main() {
|
|
209
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
210
|
+
const { graph, outFile, fileCount, duplicateNames } = await generateComponentGraph(opts);
|
|
211
|
+
console.log(
|
|
212
|
+
`[dev-panel-graph] ${graph.nodes.length} nodes, ${graph.edges.length} edges from ${fileCount} files` + (duplicateNames ? ` (${duplicateNames} duplicate names, first-wins)` : "") + `
|
|
213
|
+
[dev-panel-graph] \u2192 ${outFile}`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
main().catch((err) => {
|
|
217
|
+
console.error(err instanceof Error ? err.message : err);
|
|
218
|
+
process.exit(1);
|
|
219
|
+
});
|
|
220
|
+
//# sourceMappingURL=index.cjs.map
|
|
221
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/cli/generate.ts","../../src/cli/index.ts"],"names":["readdirSync","join","statSync","resolve","relative","sep","readFileSync","mkdirSync","writeFileSync"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Static component-graph generator using the TypeScript Compiler API (no ts-morph). Scans .tsx\n * files for React components and emits nodes + edges (renders / imports / route). Shared by the\n * `dev-panel-graph` CLI and the Vite adapter. `typescript` is an optional peer dep — imported\n * dynamically with a friendly error if absent.\n */\nimport { join, sep, resolve, relative } from 'node:path';\nimport { readdirSync, statSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';\n\nimport type { ComponentGraph, ComponentGraphNode, ComponentGraphEdge } from '../core/graph-types';\n\nexport interface GenerateOptions {\n /** Repo root (absolute). Default: process.cwd(). */\n root?: string;\n /** Directories to scan, relative to root. Default: ['src']. */\n scan?: string[];\n /** Output file, relative to root. Default: '.dev-panel/component-graph.json'. */\n out?: string;\n}\n\nexport interface GenerateResult {\n graph: ComponentGraph;\n outFile: string;\n fileCount: number;\n duplicateNames: number;\n}\n\nconst SKIP_DIR = new Set(['node_modules', 'dist', '.next', '.turbo', '__tests__', 'generated', '.git']);\nconst isComponentFile = (f: string) =>\n f.endsWith('.tsx') && !f.endsWith('.test.tsx') && !f.endsWith('.stories.tsx');\nconst isPascal = (name: unknown): name is string => typeof name === 'string' && /^[A-Z]/.test(name);\n\nfunction collectFiles(dir: string, out: string[]): string[] {\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return out;\n }\n for (const entry of entries) {\n if (SKIP_DIR.has(entry)) continue;\n const full = join(dir, entry);\n let st;\n try {\n st = statSync(full);\n } catch {\n continue;\n }\n if (st.isDirectory()) collectFiles(full, out);\n else if (isComponentFile(entry)) out.push(full);\n }\n return out;\n}\n\nexport async function generateComponentGraph(options: GenerateOptions = {}): Promise<GenerateResult> {\n let ts: typeof import('typescript');\n try {\n ts = (await import('typescript')).default ?? (await import('typescript'));\n } catch {\n throw new Error(\n \"[dev-panel-graph] 'typescript' is required to generate the component graph. Install it: npm i -D typescript\",\n );\n }\n\n const root = resolve(options.root ?? process.cwd());\n const scanDirs = (options.scan ?? ['src']).map((d) => resolve(root, d));\n const outFile = resolve(root, options.out ?? '.dev-panel/component-graph.json');\n const toRel = (abs: string) => relative(root, abs).split(sep).join('/');\n\n const files = scanDirs.flatMap((d) => collectFiles(d, []));\n\n const containsJsx = (node: import('typescript').Node): boolean => {\n let found = false;\n const visit = (n: import('typescript').Node) => {\n if (found) return;\n if (ts.isJsxElement(n) || ts.isJsxSelfClosingElement(n) || ts.isJsxFragment(n)) {\n found = true;\n return;\n }\n ts.forEachChild(n, visit);\n };\n visit(node);\n return found;\n };\n\n const jsxRoot = (tag: import('typescript').JsxTagNameExpression): string | null => {\n if (ts.isIdentifier(tag)) return tag.text;\n if (ts.isPropertyAccessExpression(tag)) {\n let expr: import('typescript').Expression = tag.expression;\n while (ts.isPropertyAccessExpression(expr)) expr = expr.expression;\n if (ts.isIdentifier(expr)) return expr.text;\n }\n return null;\n };\n\n const renderedIn = (node: import('typescript').Node): Set<string> => {\n const names = new Set<string>();\n const visit = (n: import('typescript').Node) => {\n if (ts.isJsxOpeningElement(n) || ts.isJsxSelfClosingElement(n)) {\n const r = jsxRoot(n.tagName);\n if (r && isPascal(r)) names.add(r);\n }\n ts.forEachChild(n, visit);\n };\n visit(node);\n return names;\n };\n\n const importedIn = (sf: import('typescript').SourceFile): Set<string> => {\n const names = new Set<string>();\n for (const stmt of sf.statements) {\n if (!ts.isImportDeclaration(stmt) || !stmt.importClause) continue;\n const clause = stmt.importClause;\n if (clause.isTypeOnly) continue;\n if (clause.name && isPascal(clause.name.text)) names.add(clause.name.text);\n const b = clause.namedBindings;\n if (b && ts.isNamedImports(b)) {\n for (const spec of b.elements) {\n if (spec.isTypeOnly) continue;\n if (isPascal(spec.name.text)) names.add(spec.name.text);\n }\n }\n }\n return names;\n };\n\n const hasExport = (node: import('typescript').Node): boolean =>\n !!(ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined)?.some(\n (m) => m.kind === ts.SyntaxKind.ExportKeyword,\n );\n\n const routeFor = (absPath: string): string | null => {\n const rel = toRel(absPath);\n const marker = '/app/';\n const idx = rel.indexOf(marker);\n if (idx === -1 || !rel.endsWith('/page.tsx')) return null;\n const inner = rel.slice(idx + marker.length, -'/page.tsx'.length);\n const segments = inner\n .split('/')\n .filter(Boolean)\n .filter((s) => !(s.startsWith('(') && s.endsWith(')')))\n .map((s) => s.replace(/^\\[\\.\\.\\.(.+)\\]$/, '*$1').replace(/^\\[(.+)\\]$/, ':$1'));\n return '/' + segments.join('/');\n };\n\n const nodesByName = new Map<string, ComponentGraphNode>();\n const edgeSet = new Set<string>();\n const edges: ComponentGraphEdge[] = [];\n let duplicateNames = 0;\n\n const addEdge = (from: string, to: string, type: ComponentGraphEdge['type']) => {\n if (!from || !to || from === to) return;\n const key = `${from}|${to}|${type}`;\n if (edgeSet.has(key)) return;\n edgeSet.add(key);\n edges.push({ from, to, type });\n };\n\n const addNode = (\n name: string,\n sf: import('typescript').SourceFile,\n decl: import('typescript').Node,\n exported: boolean,\n ) => {\n if (nodesByName.has(name)) {\n duplicateNames += 1;\n return;\n }\n const pos = sf.getLineAndCharacterOfPosition(decl.getStart(sf));\n nodesByName.set(name, {\n id: name,\n name,\n filePath: toRel(sf.fileName),\n line: pos.line + 1,\n column: pos.character + 1,\n type: 'component',\n exported,\n });\n };\n\n for (const file of files) {\n const text = readFileSync(file, 'utf8');\n const sf = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);\n const localComponents: Array<{ name: string; body: import('typescript').Node }> = [];\n\n for (const stmt of sf.statements) {\n if (ts.isFunctionDeclaration(stmt) && stmt.name && isPascal(stmt.name.text) && stmt.body) {\n if (containsJsx(stmt.body)) {\n addNode(stmt.name.text, sf, stmt, hasExport(stmt));\n localComponents.push({ name: stmt.name.text, body: stmt.body });\n }\n } else if (ts.isVariableStatement(stmt)) {\n const exported = hasExport(stmt);\n for (const decl of stmt.declarationList.declarations) {\n if (!ts.isIdentifier(decl.name) || !isPascal(decl.name.text) || !decl.initializer) continue;\n const init = decl.initializer;\n if ((ts.isArrowFunction(init) || ts.isFunctionExpression(init)) && containsJsx(init)) {\n addNode(decl.name.text, sf, decl, exported);\n localComponents.push({ name: decl.name.text, body: init });\n }\n }\n }\n }\n\n if (localComponents.length === 0) continue;\n\n const base = file.split(sep).pop()!.replace(/\\.tsx$/, '');\n const primary =\n localComponents.find(\n (c) => c.name.toLowerCase() === base.replace(/[-_]/g, '').toLowerCase(),\n ) ?? localComponents[0];\n\n for (const comp of localComponents) {\n for (const rendered of renderedIn(comp.body)) addEdge(comp.name, rendered, 'renders');\n }\n for (const imported of importedIn(sf)) addEdge(primary.name, imported, 'imports');\n\n const route = routeFor(file);\n if (route) {\n const routeId = `route:${route}`;\n if (!nodesByName.has(routeId)) {\n nodesByName.set(routeId, { id: routeId, name: route, filePath: toRel(file), type: 'route', route });\n }\n const primaryNode = nodesByName.get(primary.name);\n if (primaryNode && !primaryNode.route) primaryNode.route = route;\n addEdge(routeId, primary.name, 'route');\n }\n }\n\n const known = new Set(nodesByName.keys());\n const prunedEdges = edges.filter((e) => known.has(e.from) && known.has(e.to));\n\n const graph: ComponentGraph = {\n root,\n generatedAt: new Date().toISOString(),\n nodes: [...nodesByName.values()],\n edges: prunedEdges,\n };\n\n mkdirSync(resolve(outFile, '..'), { recursive: true });\n writeFileSync(outFile, JSON.stringify(graph, null, 2), 'utf8');\n\n return { graph, outFile, fileCount: files.length, duplicateNames };\n}\n","#!/usr/bin/env node\n/**\n * dev-panel-graph — generate the static component graph for the Component Graph Inspector.\n *\n * npx dev-panel-graph # scan ./src → .dev-panel/component-graph.json\n * npx dev-panel-graph --scan src,packages/ui/src --out .dev-panel/graph.json\n * npx dev-panel-graph --root /path/to/repo\n */\nimport { generateComponentGraph } from './generate';\n\nfunction parseArgs(argv: string[]): { root?: string; scan?: string[]; out?: string } {\n const out: { root?: string; scan?: string[]; out?: string } = {};\n for (let i = 0; i < argv.length; i += 1) {\n const arg = argv[i];\n const next = argv[i + 1];\n if (arg === '--root' && next) (out.root = next), (i += 1);\n else if (arg === '--scan' && next) (out.scan = next.split(',').map((s) => s.trim()).filter(Boolean)), (i += 1);\n else if (arg === '--out' && next) (out.out = next), (i += 1);\n else if (arg === '--help' || arg === '-h') {\n // eslint-disable-next-line no-console\n console.log(\n 'Usage: dev-panel-graph [--root <dir>] [--scan <dir,dir>] [--out <file>]\\n' +\n ' --root repo root (default: cwd)\\n' +\n ' --scan dirs to scan, comma-separated (default: src)\\n' +\n ' --out output JSON path (default: .dev-panel/component-graph.json)',\n );\n process.exit(0);\n }\n }\n return out;\n}\n\nasync function main() {\n const opts = parseArgs(process.argv.slice(2));\n const { graph, outFile, fileCount, duplicateNames } = await generateComponentGraph(opts);\n // eslint-disable-next-line no-console\n console.log(\n `[dev-panel-graph] ${graph.nodes.length} nodes, ${graph.edges.length} edges from ${fileCount} files` +\n (duplicateNames ? ` (${duplicateNames} duplicate names, first-wins)` : '') +\n `\\n[dev-panel-graph] → ${outFile}`,\n );\n}\n\nmain().catch((err) => {\n // eslint-disable-next-line no-console\n console.error(err instanceof Error ? err.message : err);\n process.exit(1);\n});\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { generateComponentGraph } from '../chunk-XZ4DPO52.js';
|
|
3
|
+
|
|
4
|
+
// src/cli/index.ts
|
|
5
|
+
function parseArgs(argv) {
|
|
6
|
+
const out = {};
|
|
7
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
8
|
+
const arg = argv[i];
|
|
9
|
+
const next = argv[i + 1];
|
|
10
|
+
if (arg === "--root" && next) out.root = next, i += 1;
|
|
11
|
+
else if (arg === "--scan" && next) out.scan = next.split(",").map((s) => s.trim()).filter(Boolean), i += 1;
|
|
12
|
+
else if (arg === "--out" && next) out.out = next, i += 1;
|
|
13
|
+
else if (arg === "--help" || arg === "-h") {
|
|
14
|
+
console.log(
|
|
15
|
+
"Usage: dev-panel-graph [--root <dir>] [--scan <dir,dir>] [--out <file>]\n --root repo root (default: cwd)\n --scan dirs to scan, comma-separated (default: src)\n --out output JSON path (default: .dev-panel/component-graph.json)"
|
|
16
|
+
);
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
async function main() {
|
|
23
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
24
|
+
const { graph, outFile, fileCount, duplicateNames } = await generateComponentGraph(opts);
|
|
25
|
+
console.log(
|
|
26
|
+
`[dev-panel-graph] ${graph.nodes.length} nodes, ${graph.edges.length} edges from ${fileCount} files` + (duplicateNames ? ` (${duplicateNames} duplicate names, first-wins)` : "") + `
|
|
27
|
+
[dev-panel-graph] \u2192 ${outFile}`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
main().catch((err) => {
|
|
31
|
+
console.error(err instanceof Error ? err.message : err);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
});
|
|
34
|
+
//# sourceMappingURL=index.js.map
|
|
35
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/cli/index.ts"],"names":[],"mappings":";;;;AAUA,SAAS,UAAU,IAAA,EAAkE;AACnF,EAAA,MAAM,MAAwD,EAAC;AAC/D,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,MAAA,EAAQ,KAAK,CAAA,EAAG;AACvC,IAAA,MAAM,GAAA,GAAM,KAAK,CAAC,CAAA;AAClB,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,CAAA,GAAI,CAAC,CAAA;AACvB,IAAA,IAAI,QAAQ,QAAA,IAAY,IAAA,EAAO,GAAA,CAAI,IAAA,GAAO,MAAQ,CAAA,IAAK,CAAA;AAAA,SAAA,IAC9C,GAAA,KAAQ,YAAY,IAAA,EAAO,IAAI,IAAA,GAAO,IAAA,CAAK,MAAM,GAAG,CAAA,CAAE,IAAI,CAAC,CAAA,KAAM,EAAE,IAAA,EAAM,EAAE,MAAA,CAAO,OAAO,GAAK,CAAA,IAAK,CAAA;AAAA,SAAA,IACnG,QAAQ,OAAA,IAAW,IAAA,EAAO,GAAA,CAAI,GAAA,GAAM,MAAQ,CAAA,IAAK,CAAA;AAAA,SAAA,IACjD,GAAA,KAAQ,QAAA,IAAY,GAAA,KAAQ,IAAA,EAAM;AAEzC,MAAA,OAAA,CAAQ,GAAA;AAAA,QACN;AAAA,OAIF;AACA,MAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,IAChB;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;AAEA,eAAe,IAAA,GAAO;AACpB,EAAA,MAAM,OAAO,SAAA,CAAU,OAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,CAAC,CAAC,CAAA;AAC5C,EAAA,MAAM,EAAE,OAAO,OAAA,EAAS,SAAA,EAAW,gBAAe,GAAI,MAAM,uBAAuB,IAAI,CAAA;AAEvF,EAAA,OAAA,CAAQ,GAAA;AAAA,IACN,CAAA,kBAAA,EAAqB,KAAA,CAAM,KAAA,CAAM,MAAM,WAAW,KAAA,CAAM,KAAA,CAAM,MAAM,CAAA,YAAA,EAAe,SAAS,CAAA,MAAA,CAAA,IACzF,cAAA,GAAiB,CAAA,EAAA,EAAK,cAAc,kCAAkC,EAAA,CAAA,GACvE;AAAA,yBAAA,EAAyB,OAAO,CAAA;AAAA,GACpC;AACF;AAEA,IAAA,EAAK,CAAE,KAAA,CAAM,CAAC,GAAA,KAAQ;AAEpB,EAAA,OAAA,CAAQ,KAAA,CAAM,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,UAAU,GAAG,CAAA;AACtD,EAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAChB,CAAC,CAAA","file":"index.js","sourcesContent":["#!/usr/bin/env node\n/**\n * dev-panel-graph — generate the static component graph for the Component Graph Inspector.\n *\n * npx dev-panel-graph # scan ./src → .dev-panel/component-graph.json\n * npx dev-panel-graph --scan src,packages/ui/src --out .dev-panel/graph.json\n * npx dev-panel-graph --root /path/to/repo\n */\nimport { generateComponentGraph } from './generate';\n\nfunction parseArgs(argv: string[]): { root?: string; scan?: string[]; out?: string } {\n const out: { root?: string; scan?: string[]; out?: string } = {};\n for (let i = 0; i < argv.length; i += 1) {\n const arg = argv[i];\n const next = argv[i + 1];\n if (arg === '--root' && next) (out.root = next), (i += 1);\n else if (arg === '--scan' && next) (out.scan = next.split(',').map((s) => s.trim()).filter(Boolean)), (i += 1);\n else if (arg === '--out' && next) (out.out = next), (i += 1);\n else if (arg === '--help' || arg === '-h') {\n // eslint-disable-next-line no-console\n console.log(\n 'Usage: dev-panel-graph [--root <dir>] [--scan <dir,dir>] [--out <file>]\\n' +\n ' --root repo root (default: cwd)\\n' +\n ' --scan dirs to scan, comma-separated (default: src)\\n' +\n ' --out output JSON path (default: .dev-panel/component-graph.json)',\n );\n process.exit(0);\n }\n }\n return out;\n}\n\nasync function main() {\n const opts = parseArgs(process.argv.slice(2));\n const { graph, outFile, fileCount, duplicateNames } = await generateComponentGraph(opts);\n // eslint-disable-next-line no-console\n console.log(\n `[dev-panel-graph] ${graph.nodes.length} nodes, ${graph.edges.length} edges from ${fileCount} files` +\n (duplicateNames ? ` (${duplicateNames} duplicate names, first-wins)` : '') +\n `\\n[dev-panel-graph] → ${outFile}`,\n );\n}\n\nmain().catch((err) => {\n // eslint-disable-next-line no-console\n console.error(err instanceof Error ? err.message : err);\n process.exit(1);\n});\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"generate-UK65K5BU.js"}
|