@reactgraph/cli 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +319 -0
- package/bun.lock +527 -0
- package/dist/cli/components/IndexProgress.d.ts +18 -0
- package/dist/cli/components/IndexProgress.d.ts.map +1 -0
- package/dist/cli/components/IndexProgress.js +26 -0
- package/dist/cli/components/IndexProgress.js.map +1 -0
- package/dist/cli/components/InitResult.d.ts +7 -0
- package/dist/cli/components/InitResult.d.ts.map +1 -0
- package/dist/cli/components/InitResult.js +6 -0
- package/dist/cli/components/InitResult.js.map +1 -0
- package/dist/cli/index-cmd.d.ts +7 -0
- package/dist/cli/index-cmd.d.ts.map +1 -0
- package/dist/cli/index-cmd.js +28 -0
- package/dist/cli/index-cmd.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +81 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +8 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +77 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/serve.d.ts +2 -0
- package/dist/cli/serve.d.ts.map +1 -0
- package/dist/cli/serve.js +28 -0
- package/dist/cli/serve.js.map +1 -0
- package/dist/cli/unused.d.ts +2 -0
- package/dist/cli/unused.d.ts.map +1 -0
- package/dist/cli/unused.js +56 -0
- package/dist/cli/unused.js.map +1 -0
- package/dist/graph/graph.d.ts +30 -0
- package/dist/graph/graph.d.ts.map +1 -0
- package/dist/graph/graph.js +166 -0
- package/dist/graph/graph.js.map +1 -0
- package/dist/graph/index.d.ts +5 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +5 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/schema.d.ts +33 -0
- package/dist/graph/schema.d.ts.map +1 -0
- package/dist/graph/schema.js +3 -0
- package/dist/graph/schema.js.map +1 -0
- package/dist/graph/serialize.d.ts +7 -0
- package/dist/graph/serialize.d.ts.map +1 -0
- package/dist/graph/serialize.js +39 -0
- package/dist/graph/serialize.js.map +1 -0
- package/dist/graph/traverse.d.ts +14 -0
- package/dist/graph/traverse.d.ts.map +1 -0
- package/dist/graph/traverse.js +50 -0
- package/dist/graph/traverse.js.map +1 -0
- package/dist/mcp/formatter.d.ts +26 -0
- package/dist/mcp/formatter.d.ts.map +1 -0
- package/dist/mcp/formatter.js +691 -0
- package/dist/mcp/formatter.js.map +1 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +45 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +9 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +136 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/output/ai-context.d.ts +7 -0
- package/dist/output/ai-context.d.ts.map +1 -0
- package/dist/output/ai-context.js +26 -0
- package/dist/output/ai-context.js.map +1 -0
- package/dist/parser/extractors/api-calls.d.ts +15 -0
- package/dist/parser/extractors/api-calls.d.ts.map +1 -0
- package/dist/parser/extractors/api-calls.js +168 -0
- package/dist/parser/extractors/api-calls.js.map +1 -0
- package/dist/parser/extractors/components.d.ts +5 -0
- package/dist/parser/extractors/components.d.ts.map +1 -0
- package/dist/parser/extractors/components.js +236 -0
- package/dist/parser/extractors/components.js.map +1 -0
- package/dist/parser/extractors/context.d.ts +14 -0
- package/dist/parser/extractors/context.d.ts.map +1 -0
- package/dist/parser/extractors/context.js +196 -0
- package/dist/parser/extractors/context.js.map +1 -0
- package/dist/parser/extractors/effects.d.ts +14 -0
- package/dist/parser/extractors/effects.d.ts.map +1 -0
- package/dist/parser/extractors/effects.js +175 -0
- package/dist/parser/extractors/effects.js.map +1 -0
- package/dist/parser/extractors/hooks.d.ts +5 -0
- package/dist/parser/extractors/hooks.d.ts.map +1 -0
- package/dist/parser/extractors/hooks.js +242 -0
- package/dist/parser/extractors/hooks.js.map +1 -0
- package/dist/parser/extractors/imports.d.ts +6 -0
- package/dist/parser/extractors/imports.d.ts.map +1 -0
- package/dist/parser/extractors/imports.js +148 -0
- package/dist/parser/extractors/imports.js.map +1 -0
- package/dist/parser/extractors/index.d.ts +12 -0
- package/dist/parser/extractors/index.d.ts.map +1 -0
- package/dist/parser/extractors/index.js +11 -0
- package/dist/parser/extractors/index.js.map +1 -0
- package/dist/parser/extractors/jsx-tree.d.ts +5 -0
- package/dist/parser/extractors/jsx-tree.d.ts.map +1 -0
- package/dist/parser/extractors/jsx-tree.js +226 -0
- package/dist/parser/extractors/jsx-tree.js.map +1 -0
- package/dist/parser/extractors/routes.d.ts +13 -0
- package/dist/parser/extractors/routes.d.ts.map +1 -0
- package/dist/parser/extractors/routes.js +275 -0
- package/dist/parser/extractors/routes.js.map +1 -0
- package/dist/parser/extractors/state.d.ts +14 -0
- package/dist/parser/extractors/state.d.ts.map +1 -0
- package/dist/parser/extractors/state.js +368 -0
- package/dist/parser/extractors/state.js.map +1 -0
- package/dist/parser/extractors/types.d.ts +22 -0
- package/dist/parser/extractors/types.d.ts.map +1 -0
- package/dist/parser/extractors/types.js +51 -0
- package/dist/parser/extractors/types.js.map +1 -0
- package/dist/parser/indexer.d.ts +14 -0
- package/dist/parser/indexer.d.ts.map +1 -0
- package/dist/parser/indexer.js +167 -0
- package/dist/parser/indexer.js.map +1 -0
- package/dist/parser/pipeline.d.ts +16 -0
- package/dist/parser/pipeline.d.ts.map +1 -0
- package/dist/parser/pipeline.js +63 -0
- package/dist/parser/pipeline.js.map +1 -0
- package/dist/parser/setup.d.ts +4 -0
- package/dist/parser/setup.d.ts.map +1 -0
- package/dist/parser/setup.js +29 -0
- package/dist/parser/setup.js.map +1 -0
- package/dist/parser/walker.d.ts +6 -0
- package/dist/parser/walker.d.ts.map +1 -0
- package/dist/parser/walker.js +45 -0
- package/dist/parser/walker.js.map +1 -0
- package/dist/watcher.d.ts +12 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +72 -0
- package/dist/watcher.js.map +1 -0
- package/package.json +51 -0
- package/src/cli/components/IndexProgress.tsx +79 -0
- package/src/cli/components/InitResult.tsx +28 -0
- package/src/cli/index-cmd.ts +41 -0
- package/src/cli/index.ts +92 -0
- package/src/cli/init.ts +97 -0
- package/src/cli/serve.ts +29 -0
- package/src/cli/unused.ts +88 -0
- package/src/graph/graph.ts +179 -0
- package/src/graph/index.ts +4 -0
- package/src/graph/schema.ts +68 -0
- package/src/graph/serialize.ts +40 -0
- package/src/graph/traverse.ts +66 -0
- package/src/mcp/formatter.ts +757 -0
- package/src/mcp/server.ts +59 -0
- package/src/mcp/tools.ts +154 -0
- package/src/output/ai-context.ts +29 -0
- package/src/parser/extractors/api-calls.ts +192 -0
- package/src/parser/extractors/components.ts +273 -0
- package/src/parser/extractors/context.ts +216 -0
- package/src/parser/extractors/effects.ts +205 -0
- package/src/parser/extractors/hooks.ts +268 -0
- package/src/parser/extractors/imports.ts +192 -0
- package/src/parser/extractors/index.ts +11 -0
- package/src/parser/extractors/jsx-tree.ts +271 -0
- package/src/parser/extractors/routes.ts +331 -0
- package/src/parser/extractors/state.ts +392 -0
- package/src/parser/extractors/types.ts +71 -0
- package/src/parser/indexer.ts +197 -0
- package/src/parser/pipeline.ts +89 -0
- package/src/parser/setup.ts +33 -0
- package/src/parser/walker.ts +61 -0
- package/src/watcher.ts +91 -0
- package/templates/CLAUDE.md +7 -0
- package/tests/extractors.test.ts +164 -0
- package/tests/fixtures/basic/src/App.tsx +12 -0
- package/tests/fixtures/basic/src/components/Dashboard.tsx +24 -0
- package/tests/fixtures/basic/src/components/MetricsCard.tsx +15 -0
- package/tests/fixtures/basic/src/components/Sidebar.tsx +20 -0
- package/tests/fixtures/basic/src/contexts/ThemeContext.tsx +16 -0
- package/tests/fixtures/basic/src/hooks/useAuth.ts +25 -0
- package/tests/fixtures/basic/src/stores/authStore.ts +15 -0
- package/tests/fixtures/basic/src/utils.ts +7 -0
- package/tests/graph.test.ts +91 -0
- package/tests/phase2.test.ts +309 -0
- package/tests/smoke.test.ts +77 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IndexProgress.d.ts","sourceRoot":"","sources":["../../../src/cli/components/IndexProgress.tsx"],"names":[],"mappings":"AAgBA,UAAU,kBAAkB;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,CAAC,EAAE;QACN,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC;AAED,wBAAgB,aAAa,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,kBAAkB,2CA8CzG"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
5
|
+
function useSpinner() {
|
|
6
|
+
const [frame, setFrame] = useState(0);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
const timer = setInterval(() => {
|
|
9
|
+
setFrame(f => (f + 1) % SPINNER_FRAMES.length);
|
|
10
|
+
}, 80);
|
|
11
|
+
return () => clearInterval(timer);
|
|
12
|
+
}, []);
|
|
13
|
+
return SPINNER_FRAMES[frame];
|
|
14
|
+
}
|
|
15
|
+
export function IndexProgress({ currentFile, current, total, done, stats, graphStats }) {
|
|
16
|
+
const spinner = useSpinner();
|
|
17
|
+
if (done && stats && graphStats) {
|
|
18
|
+
return (_jsxs(Box, { flexDirection: "column", paddingTop: 1, children: [_jsx(Text, { color: "green", bold: true, children: "\u2713 Indexing complete" }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { dimColor: true, children: ["Files: ", stats.totalFiles, " found, ", stats.parsedFiles, " parsed, ", stats.skippedFiles, " cached"] }), _jsxs(Text, { dimColor: true, children: ["Time: ", (stats.durationMs / 1000).toFixed(2), "s"] }), _jsxs(Text, { dimColor: true, children: ["Nodes: ", stats.nodes] }), _jsxs(Text, { dimColor: true, children: ["Edges: ", stats.edges] })] }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", marginTop: 1, children: [graphStats.Component !== undefined && _jsxs(Text, { children: [" Components: ", graphStats.Component] }), graphStats.Hook !== undefined && _jsxs(Text, { children: [" Hooks: ", graphStats.Hook] }), graphStats.Module !== undefined && _jsxs(Text, { children: [" Modules: ", graphStats.Module] })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "cyan", children: "\u2192 .reactgraph/ai-context.md generated" }) })] }));
|
|
19
|
+
}
|
|
20
|
+
const pct = total > 0 ? Math.round((current / total) * 100) : 0;
|
|
21
|
+
const barWidth = 30;
|
|
22
|
+
const filled = Math.round((pct / 100) * barWidth);
|
|
23
|
+
const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled);
|
|
24
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: spinner }), _jsx(Text, { children: " Indexing... " }), _jsxs(Text, { dimColor: true, children: [current, "/", total] })] }), _jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: "cyan", children: bar }), _jsxs(Text, { children: [" ", pct, "%"] })] }), _jsx(Box, { paddingLeft: 2, children: _jsx(Text, { dimColor: true, children: currentFile }) })] }));
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=IndexProgress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IndexProgress.js","sourceRoot":"","sources":["../../../src/cli/components/IndexProgress.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { InitResult as InitResultType } from '../init.js';
|
|
2
|
+
interface InitResultProps {
|
|
3
|
+
result: InitResultType;
|
|
4
|
+
}
|
|
5
|
+
export declare function InitResultView({ result }: InitResultProps): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=InitResult.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InitResult.d.ts","sourceRoot":"","sources":["../../../src/cli/components/InitResult.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,IAAI,cAAc,EAAE,MAAM,YAAY,CAAC;AAE/D,UAAU,eAAe;IACvB,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,EAAE,eAAe,2CAmBzD"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
export function InitResultView({ result }) {
|
|
4
|
+
return (_jsxs(Box, { flexDirection: "column", paddingTop: 1, children: [_jsx(Text, { color: "green", bold: true, children: "\u2713 ReactGraph initialized" }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { children: ["Framework: ", _jsx(Text, { color: "cyan", children: result.framework })] }), _jsxs(Text, { children: ["Source dirs: ", _jsx(Text, { color: "cyan", children: result.srcDirs.join(', ') })] }), _jsxs(Text, { children: ["Config: ", _jsx(Text, { dimColor: true, children: ".reactgraph/config.json" })] }), result.gitignoreUpdated && (_jsx(Text, { dimColor: true, children: "Added .reactgraph/ to .gitignore" }))] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "yellow", children: "Next steps:" }), _jsxs(Text, { children: [" 1. Run ", _jsx(Text, { color: "cyan", bold: true, children: "reactgraph index" }), " to build the graph"] }), _jsxs(Text, { children: [" 2. Add to CLAUDE.md: ", _jsx(Text, { dimColor: true, children: "\"Read .reactgraph/ai-context.md at session start\"" })] })] })] }));
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=InitResult.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InitResult.js","sourceRoot":"","sources":["../../../src/cli/components/InitResult.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAOhC,MAAM,UAAU,cAAc,CAAC,EAAE,MAAM,EAAmB;IACxD,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,UAAU,EAAE,CAAC,aACvC,KAAC,IAAI,IAAC,KAAK,EAAC,OAAO,EAAC,IAAI,oDAAgC,EACxD,MAAC,GAAG,IAAC,WAAW,EAAE,CAAC,EAAE,aAAa,EAAC,QAAQ,EAAC,SAAS,EAAE,CAAC,aACtD,MAAC,IAAI,8BAAY,KAAC,IAAI,IAAC,KAAK,EAAC,MAAM,YAAE,MAAM,CAAC,SAAS,GAAQ,IAAO,EACpE,MAAC,IAAI,gCAAc,KAAC,IAAI,IAAC,KAAK,EAAC,MAAM,YAAE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAQ,IAAO,EAC/E,MAAC,IAAI,2BAAS,KAAC,IAAI,IAAC,QAAQ,8CAA+B,IAAO,EACjE,MAAM,CAAC,gBAAgB,IAAI,CAC1B,KAAC,IAAI,IAAC,QAAQ,uDAAwC,CACvD,IACG,EACN,MAAC,GAAG,IAAC,SAAS,EAAE,CAAC,EAAE,aAAa,EAAC,QAAQ,aACvC,KAAC,IAAI,IAAC,KAAK,EAAC,QAAQ,4BAAmB,EACvC,MAAC,IAAI,4BAAU,KAAC,IAAI,IAAC,KAAK,EAAC,MAAM,EAAC,IAAI,uCAAwB,2BAA0B,EACxF,MAAC,IAAI,0CAAwB,KAAC,IAAI,IAAC,QAAQ,0EAAyD,IAAO,IACvG,IACF,CACP,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type IndexStats } from '../parser/indexer.js';
|
|
2
|
+
export interface IndexResult {
|
|
3
|
+
stats: IndexStats;
|
|
4
|
+
graphStats: Record<string, number>;
|
|
5
|
+
}
|
|
6
|
+
export declare function runIndex(projectDir: string, onProgress?: (file: string, current: number, total: number) => void): Promise<IndexResult>;
|
|
7
|
+
//# sourceMappingURL=index-cmd.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-cmd.d.ts","sourceRoot":"","sources":["../../src/cli/index-cmd.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGrE,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,UAAU,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,wBAAsB,QAAQ,CAC5B,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAClE,OAAO,CAAC,WAAW,CAAC,CActB"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { indexProject } from '../parser/indexer.js';
|
|
4
|
+
import { generateAIContext } from '../output/ai-context.js';
|
|
5
|
+
export async function runIndex(projectDir, onProgress) {
|
|
6
|
+
// Load config
|
|
7
|
+
const config = loadConfig(projectDir);
|
|
8
|
+
// Run indexer
|
|
9
|
+
const { graph, stats } = await indexProject(projectDir, config.exclude, onProgress);
|
|
10
|
+
// Always generate ai-context.md
|
|
11
|
+
await generateAIContext(graph, projectDir);
|
|
12
|
+
return {
|
|
13
|
+
stats,
|
|
14
|
+
graphStats: graph.stats(),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function loadConfig(projectDir) {
|
|
18
|
+
const configPath = join(projectDir, '.reactgraph', 'config.json');
|
|
19
|
+
if (existsSync(configPath)) {
|
|
20
|
+
return JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
srcDirs: ['src'],
|
|
24
|
+
framework: 'unknown',
|
|
25
|
+
exclude: ['**/*.test.*', '**/*.spec.*'],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=index-cmd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-cmd.js","sourceRoot":"","sources":["../../src/cli/index-cmd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,YAAY,EAAmB,MAAM,sBAAsB,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAO5D,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,UAAkB,EAClB,UAAmE;IAEnE,cAAc;IACd,MAAM,MAAM,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IAEtC,cAAc;IACd,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,MAAM,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAEpF,gCAAgC;IAChC,MAAM,iBAAiB,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;IAE3C,OAAO;QACL,KAAK;QACL,UAAU,EAAE,KAAK,CAAC,KAAK,EAAE;KAC1B,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,UAAkB;IACpC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,EAAE,aAAa,CAAC,CAAC;IAClE,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;IACvD,CAAC;IACD,OAAO;QACL,OAAO,EAAE,CAAC,KAAK,CAAC;QAChB,SAAS,EAAE,SAAS;QACpB,OAAO,EAAE,CAAC,aAAa,EAAE,aAAa,CAAC;KACxC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { render } from 'ink';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { runInit } from './init.js';
|
|
6
|
+
import { runIndex } from './index-cmd.js';
|
|
7
|
+
import { runServe } from './serve.js';
|
|
8
|
+
import { runUnused } from './unused.js';
|
|
9
|
+
import { InitResultView } from './components/InitResult.js';
|
|
10
|
+
import { IndexProgress } from './components/IndexProgress.js';
|
|
11
|
+
const program = new Command();
|
|
12
|
+
program
|
|
13
|
+
.name('reactgraph')
|
|
14
|
+
.description('Pre-computed React project graph for AI-assisted development')
|
|
15
|
+
.version('0.1.0');
|
|
16
|
+
program
|
|
17
|
+
.command('init')
|
|
18
|
+
.description('Initialize ReactGraph in the current project')
|
|
19
|
+
.action(() => {
|
|
20
|
+
const projectDir = process.cwd();
|
|
21
|
+
const result = runInit(projectDir);
|
|
22
|
+
const { unmount } = render(React.createElement(InitResultView, { result }));
|
|
23
|
+
setTimeout(() => unmount(), 100);
|
|
24
|
+
});
|
|
25
|
+
program
|
|
26
|
+
.command('index')
|
|
27
|
+
.description('Build the project graph')
|
|
28
|
+
.action(async () => {
|
|
29
|
+
const projectDir = process.cwd();
|
|
30
|
+
let currentFile = '';
|
|
31
|
+
let current = 0;
|
|
32
|
+
let total = 0;
|
|
33
|
+
let done = false;
|
|
34
|
+
let stats = null;
|
|
35
|
+
let graphStats = null;
|
|
36
|
+
const App = () => React.createElement(IndexProgress, {
|
|
37
|
+
currentFile,
|
|
38
|
+
current,
|
|
39
|
+
total,
|
|
40
|
+
done,
|
|
41
|
+
stats,
|
|
42
|
+
graphStats,
|
|
43
|
+
});
|
|
44
|
+
const { rerender, unmount } = render(React.createElement(App));
|
|
45
|
+
const onProgress = (file, cur, tot) => {
|
|
46
|
+
currentFile = file;
|
|
47
|
+
current = cur;
|
|
48
|
+
total = tot;
|
|
49
|
+
rerender(React.createElement(App));
|
|
50
|
+
};
|
|
51
|
+
try {
|
|
52
|
+
const result = await runIndex(projectDir, onProgress);
|
|
53
|
+
done = true;
|
|
54
|
+
stats = result.stats;
|
|
55
|
+
graphStats = result.graphStats;
|
|
56
|
+
rerender(React.createElement(App));
|
|
57
|
+
setTimeout(() => unmount(), 200);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
unmount();
|
|
61
|
+
console.error('Index failed:', err);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
program
|
|
66
|
+
.command('serve')
|
|
67
|
+
.description('Start the MCP server')
|
|
68
|
+
.option('--watch', 'Watch for file changes and update graph incrementally')
|
|
69
|
+
.action(async (opts) => {
|
|
70
|
+
const projectDir = process.cwd();
|
|
71
|
+
await runServe(projectDir, opts.watch ?? false);
|
|
72
|
+
});
|
|
73
|
+
program
|
|
74
|
+
.command('unused')
|
|
75
|
+
.description('Find orphan components, unused hooks, and dead exports')
|
|
76
|
+
.action(async () => {
|
|
77
|
+
const projectDir = process.cwd();
|
|
78
|
+
await runUnused(projectDir);
|
|
79
|
+
});
|
|
80
|
+
program.parse();
|
|
81
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED,wBAAgB,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,CAyBtD"}
|
package/dist/cli/init.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
export function runInit(projectDir) {
|
|
4
|
+
const framework = detectFramework(projectDir);
|
|
5
|
+
const srcDirs = detectSrcDirs(projectDir);
|
|
6
|
+
// Create .reactgraph/ directory
|
|
7
|
+
const rgDir = join(projectDir, '.reactgraph');
|
|
8
|
+
mkdirSync(rgDir, { recursive: true });
|
|
9
|
+
// Write config
|
|
10
|
+
const config = {
|
|
11
|
+
srcDirs,
|
|
12
|
+
framework,
|
|
13
|
+
exclude: ['**/*.test.*', '**/*.spec.*', '**/*.stories.*'],
|
|
14
|
+
};
|
|
15
|
+
writeFileSync(join(rgDir, 'config.json'), JSON.stringify(config, null, 2));
|
|
16
|
+
// Update .gitignore
|
|
17
|
+
const gitignoreUpdated = ensureGitignore(projectDir);
|
|
18
|
+
return {
|
|
19
|
+
framework,
|
|
20
|
+
srcDirs,
|
|
21
|
+
configCreated: true,
|
|
22
|
+
gitignoreUpdated,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function detectFramework(projectDir) {
|
|
26
|
+
// Next.js
|
|
27
|
+
if (existsSync(join(projectDir, 'next.config.js')) ||
|
|
28
|
+
existsSync(join(projectDir, 'next.config.mjs')) ||
|
|
29
|
+
existsSync(join(projectDir, 'next.config.ts'))) {
|
|
30
|
+
return 'next';
|
|
31
|
+
}
|
|
32
|
+
// Vite
|
|
33
|
+
if (existsSync(join(projectDir, 'vite.config.ts')) ||
|
|
34
|
+
existsSync(join(projectDir, 'vite.config.js'))) {
|
|
35
|
+
return 'vite';
|
|
36
|
+
}
|
|
37
|
+
// Remix
|
|
38
|
+
if (existsSync(join(projectDir, 'remix.config.js'))) {
|
|
39
|
+
return 'remix';
|
|
40
|
+
}
|
|
41
|
+
// CRA (react-scripts in package.json)
|
|
42
|
+
try {
|
|
43
|
+
const pkg = JSON.parse(readFileSync(join(projectDir, 'package.json'), 'utf-8'));
|
|
44
|
+
if (pkg.dependencies?.['react-scripts'] || pkg.devDependencies?.['react-scripts']) {
|
|
45
|
+
return 'cra';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// no package.json
|
|
50
|
+
}
|
|
51
|
+
return 'unknown';
|
|
52
|
+
}
|
|
53
|
+
function detectSrcDirs(projectDir) {
|
|
54
|
+
const candidates = ['src', 'app', 'pages', 'lib'];
|
|
55
|
+
const found = candidates.filter(d => existsSync(join(projectDir, d)));
|
|
56
|
+
return found.length > 0 ? found : ['.'];
|
|
57
|
+
}
|
|
58
|
+
function ensureGitignore(projectDir) {
|
|
59
|
+
const gitignorePath = join(projectDir, '.gitignore');
|
|
60
|
+
const entry = '.reactgraph/';
|
|
61
|
+
try {
|
|
62
|
+
if (existsSync(gitignorePath)) {
|
|
63
|
+
const content = readFileSync(gitignorePath, 'utf-8');
|
|
64
|
+
if (content.includes(entry))
|
|
65
|
+
return false;
|
|
66
|
+
appendFileSync(gitignorePath, `\n# ReactGraph cache\n${entry}\n`);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
writeFileSync(gitignorePath, `# ReactGraph cache\n${entry}\n`);
|
|
70
|
+
}
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/cli/serve.ts"],"names":[],"mappings":"AAIA,wBAAsB,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,GAAE,OAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAwB5F"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { loadGraph } from '../graph/serialize.js';
|
|
2
|
+
import { startServer } from '../mcp/server.js';
|
|
3
|
+
import { startWatcher } from '../watcher.js';
|
|
4
|
+
export async function runServe(projectDir, watchMode = false) {
|
|
5
|
+
if (watchMode) {
|
|
6
|
+
const graph = await loadGraph(projectDir);
|
|
7
|
+
if (!graph) {
|
|
8
|
+
console.error('No graph found. Run "reactgraph index" first.');
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
startWatcher({
|
|
12
|
+
projectDir,
|
|
13
|
+
graph,
|
|
14
|
+
onUpdate: (file, stats) => {
|
|
15
|
+
process.stderr.write(`[reactgraph] Updated: ${file} (${stats.nodes} nodes, ${stats.edges} edges)\n`);
|
|
16
|
+
},
|
|
17
|
+
onError: (file, err) => {
|
|
18
|
+
process.stderr.write(`[reactgraph] Error parsing ${file}: ${err.message}\n`);
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
// Start MCP server with the live graph
|
|
22
|
+
await startServer(projectDir, graph);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
await startServer(projectDir);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=serve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.js","sourceRoot":"","sources":["../../src/cli/serve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,UAAkB,EAAE,YAAqB,KAAK;IAC3E,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,UAAU,CAAC,CAAC;QAC1C,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;YAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,YAAY,CAAC;YACX,UAAU;YACV,KAAK;YACL,QAAQ,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;gBACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,IAAI,KAAK,KAAK,CAAC,KAAK,WAAW,KAAK,CAAC,KAAK,WAAW,CAAC,CAAC;YACvG,CAAC;YACD,OAAO,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;gBACrB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8BAA8B,IAAI,KAAK,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;YAC/E,CAAC;SACF,CAAC,CAAC;QAEH,uCAAuC;QACvC,MAAM,WAAW,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IACvC,CAAC;SAAM,CAAC;QACN,MAAM,WAAW,CAAC,UAAU,CAAC,CAAC;IAChC,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unused.d.ts","sourceRoot":"","sources":["../../src/cli/unused.ts"],"names":[],"mappings":"AAOA,wBAAsB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoDjE"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { render } from 'ink';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import { loadGraph } from '../graph/serialize.js';
|
|
5
|
+
export async function runUnused(projectDir) {
|
|
6
|
+
const graph = await loadGraph(projectDir);
|
|
7
|
+
if (!graph) {
|
|
8
|
+
console.error('No graph found. Run "reactgraph index" first.');
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
const orphanComponents = findOrphanComponents(graph);
|
|
12
|
+
const unusedHooks = findUnusedHooks(graph);
|
|
13
|
+
const unusedExports = findUnusedExports(graph);
|
|
14
|
+
const App = () => React.createElement(Box, { flexDirection: 'column', paddingTop: 1 }, React.createElement(Text, { bold: true }, 'ReactGraph — Unused Analysis'), React.createElement(Text, null, ''),
|
|
15
|
+
// Orphan components
|
|
16
|
+
orphanComponents.length > 0
|
|
17
|
+
? React.createElement(Box, { flexDirection: 'column' }, React.createElement(Text, { color: 'yellow' }, `Orphan components (${orphanComponents.length}) — rendered by nothing:`), ...orphanComponents.map(c => React.createElement(Text, { key: c.id, dimColor: true }, ` ${c.name} [${c.file}:${c.line}]`)))
|
|
18
|
+
: React.createElement(Text, { color: 'green' }, '✓ No orphan components'), React.createElement(Text, null, ''),
|
|
19
|
+
// Unused hooks
|
|
20
|
+
unusedHooks.length > 0
|
|
21
|
+
? React.createElement(Box, { flexDirection: 'column' }, React.createElement(Text, { color: 'yellow' }, `Unused hooks (${unusedHooks.length}) — never called:`), ...unusedHooks.map(h => React.createElement(Text, { key: h.id, dimColor: true }, ` ${h.name} [${h.file}:${h.line}]`)))
|
|
22
|
+
: React.createElement(Text, { color: 'green' }, '✓ No unused hooks'), React.createElement(Text, null, ''),
|
|
23
|
+
// Unused exports
|
|
24
|
+
unusedExports.length > 0
|
|
25
|
+
? React.createElement(Box, { flexDirection: 'column' }, React.createElement(Text, { color: 'yellow' }, `Unused exports (${unusedExports.length}) — never imported:`), ...unusedExports.map(e => React.createElement(Text, { key: e.id, dimColor: true }, ` ${e.name} [${e.file}:${e.line}]`)))
|
|
26
|
+
: React.createElement(Text, { color: 'green' }, '✓ No unused exports'));
|
|
27
|
+
const { unmount } = render(React.createElement(App));
|
|
28
|
+
setTimeout(() => unmount(), 100);
|
|
29
|
+
}
|
|
30
|
+
function findOrphanComponents(graph) {
|
|
31
|
+
return graph.getNodesByKind('Component').filter(c => {
|
|
32
|
+
// A component is orphan if nothing renders it and it's not a route target
|
|
33
|
+
const renderedBy = graph.getEdgesTo(c.id, ['renders']);
|
|
34
|
+
return renderedBy.length === 0;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function findUnusedHooks(graph) {
|
|
38
|
+
return graph.getNodesByKind('Hook').filter(h => {
|
|
39
|
+
const usedBy = graph.getEdgesTo(h.id, ['uses_hook']);
|
|
40
|
+
return usedBy.length === 0;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function findUnusedExports(graph) {
|
|
44
|
+
// Find exported nodes that are never imported by anything
|
|
45
|
+
const allNodes = graph.getAllNodes();
|
|
46
|
+
return allNodes.filter(n => {
|
|
47
|
+
if (n.exportType === 'none')
|
|
48
|
+
return false;
|
|
49
|
+
if (n.kind === 'Module')
|
|
50
|
+
return false; // Modules themselves aren't "used"
|
|
51
|
+
// Check if any edge points to this node
|
|
52
|
+
const incoming = graph.getEdgesTo(n.id);
|
|
53
|
+
return incoming.length === 0;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=unused.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unused.js","sourceRoot":"","sources":["../../src/cli/unused.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { GraphNode, GraphEdge, NodeKind, EdgeKind } from './schema.js';
|
|
2
|
+
export declare class ReactGraph {
|
|
3
|
+
private nodes;
|
|
4
|
+
private edges;
|
|
5
|
+
private byKind;
|
|
6
|
+
private byFile;
|
|
7
|
+
private byName;
|
|
8
|
+
private edgesBySource;
|
|
9
|
+
private edgesByTarget;
|
|
10
|
+
private edgesByKind;
|
|
11
|
+
addNode(node: GraphNode): void;
|
|
12
|
+
addEdge(edge: GraphEdge): void;
|
|
13
|
+
getNode(id: string): GraphNode | undefined;
|
|
14
|
+
getAllNodes(): GraphNode[];
|
|
15
|
+
getAllEdges(): GraphEdge[];
|
|
16
|
+
getNodesByKind(kind: NodeKind): GraphNode[];
|
|
17
|
+
getNodesByFile(file: string): GraphNode[];
|
|
18
|
+
getNodesByName(name: string): GraphNode[];
|
|
19
|
+
getEdgesFrom(nodeId: string, kinds?: EdgeKind[]): GraphEdge[];
|
|
20
|
+
getEdgesTo(nodeId: string, kinds?: EdgeKind[]): GraphEdge[];
|
|
21
|
+
getEdgesByKind(kind: EdgeKind): GraphEdge[];
|
|
22
|
+
removeNodesByFile(file: string): void;
|
|
23
|
+
clear(): void;
|
|
24
|
+
/** Find a node by name with fuzzy matching: exact → case-insensitive → partial */
|
|
25
|
+
findNode(name: string): GraphNode | undefined;
|
|
26
|
+
/** Count of in+out edges for a node */
|
|
27
|
+
connectivity(nodeId: string): number;
|
|
28
|
+
stats(): Record<string, number>;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../src/graph/graph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5E,qBAAa,UAAU;IACrB,OAAO,CAAC,KAAK,CAAgC;IAC7C,OAAO,CAAC,KAAK,CAAmB;IAGhC,OAAO,CAAC,MAAM,CAAoC;IAClD,OAAO,CAAC,MAAM,CAAkC;IAChD,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,aAAa,CAAkC;IACvD,OAAO,CAAC,aAAa,CAAkC;IACvD,OAAO,CAAC,WAAW,CAAoC;IAEvD,OAAO,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAgB9B,OAAO,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAa9B,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAI1C,WAAW,IAAI,SAAS,EAAE;IAI1B,WAAW,IAAI,SAAS,EAAE;IAI1B,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,EAAE;IAM3C,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE;IAMzC,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE;IAMzC,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,QAAQ,EAAE,GAAG,SAAS,EAAE;IAM7D,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,QAAQ,EAAE,GAAG,SAAS,EAAE;IAM3D,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,EAAE;IAI3C,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAyCrC,KAAK,IAAI,IAAI;IAWb,kFAAkF;IAClF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAmB7C,uCAAuC;IACvC,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAKpC,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;CAWhC"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
export class ReactGraph {
|
|
2
|
+
nodes = new Map();
|
|
3
|
+
edges = [];
|
|
4
|
+
// Secondary indexes
|
|
5
|
+
byKind = new Map();
|
|
6
|
+
byFile = new Map();
|
|
7
|
+
byName = new Map();
|
|
8
|
+
edgesBySource = new Map();
|
|
9
|
+
edgesByTarget = new Map();
|
|
10
|
+
edgesByKind = new Map();
|
|
11
|
+
addNode(node) {
|
|
12
|
+
this.nodes.set(node.id, node);
|
|
13
|
+
// Index by kind
|
|
14
|
+
if (!this.byKind.has(node.kind))
|
|
15
|
+
this.byKind.set(node.kind, new Set());
|
|
16
|
+
this.byKind.get(node.kind).add(node.id);
|
|
17
|
+
// Index by file
|
|
18
|
+
if (!this.byFile.has(node.file))
|
|
19
|
+
this.byFile.set(node.file, new Set());
|
|
20
|
+
this.byFile.get(node.file).add(node.id);
|
|
21
|
+
// Index by name
|
|
22
|
+
if (!this.byName.has(node.name))
|
|
23
|
+
this.byName.set(node.name, []);
|
|
24
|
+
this.byName.get(node.name).push(node.id);
|
|
25
|
+
}
|
|
26
|
+
addEdge(edge) {
|
|
27
|
+
this.edges.push(edge);
|
|
28
|
+
if (!this.edgesBySource.has(edge.source))
|
|
29
|
+
this.edgesBySource.set(edge.source, []);
|
|
30
|
+
this.edgesBySource.get(edge.source).push(edge);
|
|
31
|
+
if (!this.edgesByTarget.has(edge.target))
|
|
32
|
+
this.edgesByTarget.set(edge.target, []);
|
|
33
|
+
this.edgesByTarget.get(edge.target).push(edge);
|
|
34
|
+
if (!this.edgesByKind.has(edge.kind))
|
|
35
|
+
this.edgesByKind.set(edge.kind, []);
|
|
36
|
+
this.edgesByKind.get(edge.kind).push(edge);
|
|
37
|
+
}
|
|
38
|
+
getNode(id) {
|
|
39
|
+
return this.nodes.get(id);
|
|
40
|
+
}
|
|
41
|
+
getAllNodes() {
|
|
42
|
+
return Array.from(this.nodes.values());
|
|
43
|
+
}
|
|
44
|
+
getAllEdges() {
|
|
45
|
+
return this.edges;
|
|
46
|
+
}
|
|
47
|
+
getNodesByKind(kind) {
|
|
48
|
+
const ids = this.byKind.get(kind);
|
|
49
|
+
if (!ids)
|
|
50
|
+
return [];
|
|
51
|
+
return Array.from(ids).map(id => this.nodes.get(id));
|
|
52
|
+
}
|
|
53
|
+
getNodesByFile(file) {
|
|
54
|
+
const ids = this.byFile.get(file);
|
|
55
|
+
if (!ids)
|
|
56
|
+
return [];
|
|
57
|
+
return Array.from(ids).map(id => this.nodes.get(id));
|
|
58
|
+
}
|
|
59
|
+
getNodesByName(name) {
|
|
60
|
+
const ids = this.byName.get(name);
|
|
61
|
+
if (!ids)
|
|
62
|
+
return [];
|
|
63
|
+
return ids.map(id => this.nodes.get(id));
|
|
64
|
+
}
|
|
65
|
+
getEdgesFrom(nodeId, kinds) {
|
|
66
|
+
const edges = this.edgesBySource.get(nodeId) ?? [];
|
|
67
|
+
if (!kinds)
|
|
68
|
+
return edges;
|
|
69
|
+
return edges.filter(e => kinds.includes(e.kind));
|
|
70
|
+
}
|
|
71
|
+
getEdgesTo(nodeId, kinds) {
|
|
72
|
+
const edges = this.edgesByTarget.get(nodeId) ?? [];
|
|
73
|
+
if (!kinds)
|
|
74
|
+
return edges;
|
|
75
|
+
return edges.filter(e => kinds.includes(e.kind));
|
|
76
|
+
}
|
|
77
|
+
getEdgesByKind(kind) {
|
|
78
|
+
return this.edgesByKind.get(kind) ?? [];
|
|
79
|
+
}
|
|
80
|
+
removeNodesByFile(file) {
|
|
81
|
+
const ids = this.byFile.get(file);
|
|
82
|
+
if (!ids)
|
|
83
|
+
return;
|
|
84
|
+
// Remove edges referencing these nodes
|
|
85
|
+
const idSet = new Set(ids);
|
|
86
|
+
this.edges = this.edges.filter(e => !idSet.has(e.source) && !idSet.has(e.target));
|
|
87
|
+
// Rebuild edge indexes
|
|
88
|
+
this.edgesBySource.clear();
|
|
89
|
+
this.edgesByTarget.clear();
|
|
90
|
+
this.edgesByKind.clear();
|
|
91
|
+
for (const edge of this.edges) {
|
|
92
|
+
if (!this.edgesBySource.has(edge.source))
|
|
93
|
+
this.edgesBySource.set(edge.source, []);
|
|
94
|
+
this.edgesBySource.get(edge.source).push(edge);
|
|
95
|
+
if (!this.edgesByTarget.has(edge.target))
|
|
96
|
+
this.edgesByTarget.set(edge.target, []);
|
|
97
|
+
this.edgesByTarget.get(edge.target).push(edge);
|
|
98
|
+
if (!this.edgesByKind.has(edge.kind))
|
|
99
|
+
this.edgesByKind.set(edge.kind, []);
|
|
100
|
+
this.edgesByKind.get(edge.kind).push(edge);
|
|
101
|
+
}
|
|
102
|
+
// Remove nodes
|
|
103
|
+
for (const id of ids) {
|
|
104
|
+
const node = this.nodes.get(id);
|
|
105
|
+
this.nodes.delete(id);
|
|
106
|
+
// Clean kind index
|
|
107
|
+
this.byKind.get(node.kind)?.delete(id);
|
|
108
|
+
// Clean name index
|
|
109
|
+
const nameIds = this.byName.get(node.name);
|
|
110
|
+
if (nameIds) {
|
|
111
|
+
const idx = nameIds.indexOf(id);
|
|
112
|
+
if (idx >= 0)
|
|
113
|
+
nameIds.splice(idx, 1);
|
|
114
|
+
if (nameIds.length === 0)
|
|
115
|
+
this.byName.delete(node.name);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
this.byFile.delete(file);
|
|
119
|
+
}
|
|
120
|
+
clear() {
|
|
121
|
+
this.nodes.clear();
|
|
122
|
+
this.edges = [];
|
|
123
|
+
this.byKind.clear();
|
|
124
|
+
this.byFile.clear();
|
|
125
|
+
this.byName.clear();
|
|
126
|
+
this.edgesBySource.clear();
|
|
127
|
+
this.edgesByTarget.clear();
|
|
128
|
+
this.edgesByKind.clear();
|
|
129
|
+
}
|
|
130
|
+
/** Find a node by name with fuzzy matching: exact → case-insensitive → partial */
|
|
131
|
+
findNode(name) {
|
|
132
|
+
// Exact match
|
|
133
|
+
const exact = this.byName.get(name);
|
|
134
|
+
if (exact?.length)
|
|
135
|
+
return this.nodes.get(exact[0]);
|
|
136
|
+
// Case-insensitive
|
|
137
|
+
const lower = name.toLowerCase();
|
|
138
|
+
for (const [key, ids] of this.byName) {
|
|
139
|
+
if (key.toLowerCase() === lower && ids.length)
|
|
140
|
+
return this.nodes.get(ids[0]);
|
|
141
|
+
}
|
|
142
|
+
// Partial match
|
|
143
|
+
for (const [key, ids] of this.byName) {
|
|
144
|
+
if (key.toLowerCase().includes(lower) && ids.length)
|
|
145
|
+
return this.nodes.get(ids[0]);
|
|
146
|
+
}
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
/** Count of in+out edges for a node */
|
|
150
|
+
connectivity(nodeId) {
|
|
151
|
+
return (this.edgesBySource.get(nodeId)?.length ?? 0) +
|
|
152
|
+
(this.edgesByTarget.get(nodeId)?.length ?? 0);
|
|
153
|
+
}
|
|
154
|
+
stats() {
|
|
155
|
+
const s = {
|
|
156
|
+
totalNodes: this.nodes.size,
|
|
157
|
+
totalEdges: this.edges.length,
|
|
158
|
+
files: this.byFile.size,
|
|
159
|
+
};
|
|
160
|
+
for (const [kind, ids] of this.byKind) {
|
|
161
|
+
s[kind] = ids.size;
|
|
162
|
+
}
|
|
163
|
+
return s;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=graph.js.map
|