projectify-cli 2.0.3 → 2.0.5
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/dist/analyzer/index.js +38 -4
- package/dist/index.js +1 -1
- package/dist/report/htmlGenerator.js +47 -480
- package/dist/report/template.html +516 -0
- package/package.json +5 -4
package/dist/analyzer/index.js
CHANGED
|
@@ -11,23 +11,57 @@ const python_1 = require("./parsers/python");
|
|
|
11
11
|
async function analyzeFiles(filePaths) {
|
|
12
12
|
const analysis = {
|
|
13
13
|
fileCount: filePaths.length,
|
|
14
|
-
files: {}
|
|
14
|
+
files: {},
|
|
15
|
+
dependencies: {}
|
|
15
16
|
};
|
|
16
17
|
for (const filePath of filePaths) {
|
|
17
18
|
const content = await (0, fileUtils_1.readFileSafe)(filePath);
|
|
18
|
-
if (
|
|
19
|
-
continue;
|
|
19
|
+
if (content === null)
|
|
20
|
+
continue; // Skip if read failed
|
|
20
21
|
const ext = path_1.default.extname(filePath);
|
|
22
|
+
const basename = path_1.default.basename(filePath);
|
|
21
23
|
let fileResult = { imports: [], exports: [], functions: [], classes: [] };
|
|
24
|
+
// Dependency Parsing
|
|
25
|
+
if (basename === 'package.json') {
|
|
26
|
+
try {
|
|
27
|
+
const pkg = JSON.parse(content);
|
|
28
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies, ...pkg.peerDependencies };
|
|
29
|
+
Object.assign(analysis.dependencies, deps);
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
console.warn(`⚠️ Failed to parse package.json: ${e}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (basename === 'requirements.txt') {
|
|
36
|
+
const lines = content.split('\n');
|
|
37
|
+
lines.forEach(line => {
|
|
38
|
+
const parts = line.split('==');
|
|
39
|
+
if (parts.length > 0 && parts[0].trim()) {
|
|
40
|
+
analysis.dependencies[parts[0].trim()] = parts[1]?.trim() || 'latest';
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// Code Level Analysis
|
|
22
45
|
if (['.js', '.jsx', '.ts', '.tsx'].includes(ext)) {
|
|
23
46
|
fileResult = (0, javascript_1.parseJS)(content, filePath);
|
|
47
|
+
fileResult.language = 'javascript';
|
|
24
48
|
}
|
|
25
49
|
else if (['.py'].includes(ext)) {
|
|
26
50
|
fileResult = (0, python_1.parsePython)(content, filePath);
|
|
51
|
+
fileResult.language = 'python';
|
|
52
|
+
}
|
|
53
|
+
else if (['.json'].includes(ext)) {
|
|
54
|
+
fileResult.language = 'json';
|
|
55
|
+
}
|
|
56
|
+
else if (['.md', '.txt'].includes(ext)) {
|
|
57
|
+
fileResult.language = 'markdown';
|
|
27
58
|
}
|
|
28
59
|
else {
|
|
29
|
-
|
|
60
|
+
fileResult.language = 'text';
|
|
30
61
|
}
|
|
62
|
+
// Store Content + Meta
|
|
63
|
+
fileResult.content = content;
|
|
64
|
+
fileResult.size = content.length;
|
|
31
65
|
analysis.files[filePath] = fileResult;
|
|
32
66
|
}
|
|
33
67
|
return analysis;
|
package/dist/index.js
CHANGED
|
@@ -18,7 +18,7 @@ const program = new commander_1.Command();
|
|
|
18
18
|
program
|
|
19
19
|
.name('projectify')
|
|
20
20
|
.description('Projectify - Autonomous Code Analysis & Visualization')
|
|
21
|
-
.version('2.0.
|
|
21
|
+
.version('2.0.5');
|
|
22
22
|
program
|
|
23
23
|
.argument('[path]', 'Project path to analyze', '.')
|
|
24
24
|
.option('--no-ai', 'Skip AI analysis')
|
|
@@ -71,14 +71,22 @@ async function generateHtmlReport(projectPath, analysis, graph, outputPath, gitS
|
|
|
71
71
|
});
|
|
72
72
|
// Aggregate functions for the view
|
|
73
73
|
const functionsList = [];
|
|
74
|
+
const relativeFiles = {};
|
|
75
|
+
const absoluteProjectPath = path_1.default.resolve(projectPath);
|
|
74
76
|
Object.entries(analysis.files).forEach(([file, data]) => {
|
|
77
|
+
// Normalize path for frontend (relative + forward slashes)
|
|
78
|
+
let relPath = path_1.default.relative(absoluteProjectPath, file);
|
|
79
|
+
if (path_1.default.sep === '\\') {
|
|
80
|
+
relPath = relPath.replace(/\\/g, '/');
|
|
81
|
+
}
|
|
82
|
+
relativeFiles[relPath] = data;
|
|
75
83
|
data.functions.forEach((fn) => {
|
|
76
84
|
// Handle both old (string) and new (FunctionInfo) formats safely
|
|
77
85
|
if (typeof fn === 'object') {
|
|
78
86
|
functionsList.push({
|
|
79
87
|
name: fn.name,
|
|
80
88
|
line: fn.line,
|
|
81
|
-
file:
|
|
89
|
+
file: relPath, // Use relative path here too
|
|
82
90
|
params: fn.params || [],
|
|
83
91
|
doc: fn.doc || '',
|
|
84
92
|
code: fn.code || ''
|
|
@@ -88,7 +96,7 @@ async function generateHtmlReport(projectPath, analysis, graph, outputPath, gitS
|
|
|
88
96
|
functionsList.push({
|
|
89
97
|
name: fn,
|
|
90
98
|
line: 0,
|
|
91
|
-
file:
|
|
99
|
+
file: relPath,
|
|
92
100
|
params: [],
|
|
93
101
|
doc: '',
|
|
94
102
|
code: ''
|
|
@@ -96,487 +104,46 @@ async function generateHtmlReport(projectPath, analysis, graph, outputPath, gitS
|
|
|
96
104
|
}
|
|
97
105
|
});
|
|
98
106
|
});
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
sans: ['Inter', 'sans-serif'],
|
|
114
|
-
mono: ['JetBrains Mono', 'monospace'],
|
|
115
|
-
},
|
|
116
|
-
colors: {
|
|
117
|
-
background: '#000000',
|
|
118
|
-
surface: '#0a0a0a',
|
|
119
|
-
primary: '#22d3ee', // cyan-400
|
|
120
|
-
secondary: '#818cf8', // indigo-400
|
|
121
|
-
accent: '#f472b6', // pink-400
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
</script>
|
|
127
|
-
<script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
|
|
128
|
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
|
|
129
|
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/prism.min.js"></script>
|
|
130
|
-
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/themes/prism-tomorrow.min.css" rel="stylesheet" />
|
|
131
|
-
|
|
132
|
-
<style>
|
|
133
|
-
body {
|
|
134
|
-
background-color: #000000;
|
|
135
|
-
color: #ecfeff;
|
|
136
|
-
overflow: hidden;
|
|
137
|
-
background-image: radial-gradient(circle at 50% 50%, #111827 0%, #000000 100%);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
.glass {
|
|
141
|
-
background: rgba(10, 10, 10, 0.6);
|
|
142
|
-
backdrop-filter: blur(8px);
|
|
143
|
-
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
.neon-text {
|
|
147
|
-
text-shadow: 0 0 10px rgba(34, 211, 238, 0.5);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
.vis-network { outline: none; }
|
|
151
|
-
|
|
152
|
-
/* Custom Scrollbar */
|
|
153
|
-
::-webkit-scrollbar { width: 4px; }
|
|
154
|
-
::-webkit-scrollbar-track { background: transparent; }
|
|
155
|
-
::-webkit-scrollbar-thumb { background: #334155; border-radius: 2px; }
|
|
156
|
-
|
|
157
|
-
/* Navigation Tabs */
|
|
158
|
-
.nav-tab {
|
|
159
|
-
position: relative;
|
|
160
|
-
color: #94a3b8;
|
|
161
|
-
transition: color 0.2s;
|
|
162
|
-
}
|
|
163
|
-
.nav-tab.active {
|
|
164
|
-
color: #fff;
|
|
165
|
-
font-weight: 600;
|
|
166
|
-
}
|
|
167
|
-
.nav-tab.active::after {
|
|
168
|
-
content: '';
|
|
169
|
-
position: absolute;
|
|
170
|
-
bottom: -18px;
|
|
171
|
-
left: 0;
|
|
172
|
-
right: 0;
|
|
173
|
-
height: 2px;
|
|
174
|
-
background: #a855f7; /* Purple */
|
|
175
|
-
box-shadow: 0 0 10px #a855f7;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/* Snap Button */
|
|
179
|
-
.snap-btn {
|
|
180
|
-
background: rgba(20, 20, 20, 0.8);
|
|
181
|
-
border: 1px solid rgba(255,255,255,0.1);
|
|
182
|
-
backdrop-filter: blur(4px);
|
|
183
|
-
color: #94a3b8;
|
|
184
|
-
border-radius: 16px;
|
|
185
|
-
transition: all 0.2s;
|
|
186
|
-
}
|
|
187
|
-
.snap-btn:hover {
|
|
188
|
-
color: #fff;
|
|
189
|
-
border-color: rgba(255,255,255,0.3);
|
|
190
|
-
box-shadow: 0 0 20px rgba(0,0,0,0.5);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/* Custom Tooltip */
|
|
194
|
-
#custom-tooltip {
|
|
195
|
-
pointer-events: none;
|
|
196
|
-
z-index: 100;
|
|
197
|
-
transition: opacity 0.1s;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/* Code Block Styling */
|
|
201
|
-
pre[class*="language-"] {
|
|
202
|
-
background: #0f172a !important;
|
|
203
|
-
border-radius: 8px;
|
|
204
|
-
border: 1px solid #1e293b;
|
|
205
|
-
margin: 0;
|
|
206
|
-
}
|
|
207
|
-
</style>
|
|
208
|
-
</head>
|
|
209
|
-
<body class="flex flex-col h-screen font-sans selection:bg-primary/30 selection:text-white">
|
|
210
|
-
|
|
211
|
-
<!-- TOP NAVIGATION -->
|
|
212
|
-
<nav class="h-16 border-b border-white/5 flex items-center justify-between px-6 bg-black z-50">
|
|
213
|
-
<!-- Logo & Breadcrumb -->
|
|
214
|
-
<div class="flex items-center gap-4">
|
|
215
|
-
<div class="flex items-center gap-2 text-primary font-mono tracking-wider font-bold text-lg neon-text">
|
|
216
|
-
<span>></span> Projectify
|
|
217
|
-
</div>
|
|
218
|
-
<div class="text-zinc-600 font-light text-sm tracking-widest uppercase flex items-center gap-2">
|
|
219
|
-
<span>//</span>
|
|
220
|
-
<span>KNOWLEDGE GRAPH</span>
|
|
221
|
-
</div>
|
|
222
|
-
<!-- Navigation Arrows (Static) -->
|
|
223
|
-
<div class="flex gap-1 ml-4 text-zinc-700">
|
|
224
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
|
225
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path></svg>
|
|
226
|
-
</div>
|
|
227
|
-
</div>
|
|
228
|
-
|
|
229
|
-
<!-- Center Tabs -->
|
|
230
|
-
<div class="hidden md:flex items-center gap-8 text-sm uppercase tracking-wider">
|
|
231
|
-
<button class="nav-tab hover:text-white" onclick="switchView('network')">GRAPH</button>
|
|
232
|
-
<button class="nav-tab active" onclick="switchView('functions')">FUNCTIONS</button>
|
|
233
|
-
<button class="nav-tab hover:text-white" onclick="switchView('files')">FILES</button>
|
|
234
|
-
</div>
|
|
235
|
-
|
|
236
|
-
<!-- Right Search -->
|
|
237
|
-
<div class="relative group w-64">
|
|
238
|
-
<input type="text" id="search-input"
|
|
239
|
-
class="w-full bg-zinc-900/50 border border-zinc-800 rounded-full px-4 py-1.5 text-xs text-zinc-300 focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/50 transition-all font-mono"
|
|
240
|
-
placeholder="SEARCH NODE..."
|
|
241
|
-
onkeydown="if(event.key === 'Enter') searchNodes()">
|
|
242
|
-
<div class="absolute right-2 top-1.5 text-zinc-600">
|
|
243
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
|
244
|
-
</div>
|
|
245
|
-
</div>
|
|
246
|
-
|
|
247
|
-
<div class="ml-4 p-2 rounded border border-zinc-800 text-zinc-500 hover:text-white hover:border-zinc-600 transition-colors cursor-pointer">
|
|
248
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
|
249
|
-
</div>
|
|
250
|
-
</nav>
|
|
251
|
-
|
|
252
|
-
<!-- CONTENT AREA -->
|
|
253
|
-
<div class="flex-1 relative overflow-hidden">
|
|
254
|
-
|
|
255
|
-
<!-- GRAPH CONTAINER -->
|
|
256
|
-
<div id="network-view" class="view-panel absolute inset-0 z-0 cursor-crosshair"></div>
|
|
257
|
-
|
|
258
|
-
<!-- FUNCTIONS CONTAINER -->
|
|
259
|
-
<div id="functions-view" class="view-panel absolute inset-0 z-20 bg-black hidden flex flex-col p-8 overflow-y-auto">
|
|
260
|
-
<div class="max-w-6xl mx-auto w-full">
|
|
261
|
-
<h2 class="text-2xl font-mono text-primary mb-6 neon-text">Available Functions</h2>
|
|
262
|
-
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" id="functions-grid">
|
|
263
|
-
<!-- Functions injected here -->
|
|
264
|
-
</div>
|
|
265
|
-
</div>
|
|
266
|
-
</div>
|
|
267
|
-
|
|
268
|
-
<!-- FILES CONTAINER (Placeholder) -->
|
|
269
|
-
<div id="files-view" class="view-panel absolute inset-0 z-20 bg-black hidden flex items-center justify-center">
|
|
270
|
-
<div class="text-zinc-600 font-mono">FILES VIEW UNDER CONSTRUCTION</div>
|
|
271
|
-
</div>
|
|
272
|
-
|
|
273
|
-
<!-- CUSTOM TOOLTIP -->
|
|
274
|
-
<div id="custom-tooltip" class="fixed hidden p-4 bg-black/90 border border-primary/50 text-xs font-mono rounded shadow-[0_0_20px_rgba(34,211,238,0.2)]">
|
|
275
|
-
<div id="tooltip-title" class="text-primary font-bold text-sm mb-2"></div>
|
|
276
|
-
<div class="space-y-1 text-zinc-400">
|
|
277
|
-
<div class="flex gap-2"><span class="w-16 text-zinc-600">PATH:</span> <span id="tooltip-path" class="text-white"></span></div>
|
|
278
|
-
<div class="flex gap-2"><span class="w-16 text-zinc-600">FILE:</span> <span id="tooltip-file" class="text-white"></span></div>
|
|
279
|
-
<div class="flex gap-2"><span class="w-16 text-zinc-600">LINE:</span> <span id="tooltip-line" class="text-accent"></span></div>
|
|
280
|
-
</div>
|
|
281
|
-
</div>
|
|
282
|
-
|
|
283
|
-
<!-- DETAILS SIDEBAR (Overlay, conditionally hidden) -->
|
|
284
|
-
<div id="details-panel" class="absolute top-0 right-0 h-full w-[500px] bg-black/95 backdrop-blur-xl border-l border-white/10 transform translate-x-full transition-transform duration-300 z-30 flex flex-col shadow-2xl">
|
|
285
|
-
<div class="p-6 border-b border-white/10 flex justify-between items-center">
|
|
286
|
-
<div>
|
|
287
|
-
<div class="text-[10px] text-primary tracking-widest uppercase mb-1" id="detail-type">Node Details</div>
|
|
288
|
-
<h2 id="detail-title" class="text-lg font-mono font-bold text-white break-all"></h2>
|
|
289
|
-
</div>
|
|
290
|
-
<button onclick="closeDetails()" class="text-zinc-500 hover:text-white">
|
|
291
|
-
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
|
|
292
|
-
</button>
|
|
293
|
-
</div>
|
|
294
|
-
|
|
295
|
-
<div class="p-6 space-y-8 flex-1 overflow-y-auto" id="detail-content">
|
|
296
|
-
<!-- Content injected dynamically -->
|
|
297
|
-
</div>
|
|
298
|
-
</div>
|
|
299
|
-
|
|
300
|
-
<!-- BOTTOM CONTROLS (Floating) -->
|
|
301
|
-
<div class="absolute bottom-8 left-1/2 transform -translate-x-1/2 z-10">
|
|
302
|
-
<button class="snap-btn py-4 px-6 flex flex-col items-center gap-1 group" onclick="resetView()">
|
|
303
|
-
<div class="w-6 h-6 border-2 border-current rounded mb-1 bg-gradient-to-tr from-transparent to-white/10"></div>
|
|
304
|
-
<span class="text-[10px] tracking-widest font-bold font-mono">SNAP</span>
|
|
305
|
-
</button>
|
|
306
|
-
</div>
|
|
307
|
-
|
|
308
|
-
</div>
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
<!-- DATA INJECTION -->
|
|
312
|
-
<script id="nodes-data" type="application/json">
|
|
313
|
-
${safeJSON(nodes)}
|
|
314
|
-
</script>
|
|
315
|
-
<script id="edges-data" type="application/json">
|
|
316
|
-
${safeJSON(edges)}
|
|
317
|
-
</script>
|
|
318
|
-
<script id="functions-data" type="application/json">
|
|
319
|
-
${safeJSON(functionsList)}
|
|
320
|
-
</script>
|
|
321
|
-
|
|
322
|
-
<script>
|
|
323
|
-
// Safe data retrieval
|
|
324
|
-
const nodesData = JSON.parse(document.getElementById('nodes-data').textContent);
|
|
325
|
-
const edgesData = JSON.parse(document.getElementById('edges-data').textContent);
|
|
326
|
-
const functionsData = JSON.parse(document.getElementById('functions-data').textContent);
|
|
327
|
-
|
|
328
|
-
const nodes = new vis.DataSet(nodesData);
|
|
329
|
-
const edges = new vis.DataSet(edgesData);
|
|
330
|
-
|
|
331
|
-
const container = document.getElementById('network-view');
|
|
332
|
-
const data = { nodes: nodes, edges: edges };
|
|
333
|
-
|
|
334
|
-
const options = {
|
|
335
|
-
nodes: {
|
|
336
|
-
borderWidth: 0,
|
|
337
|
-
shadow: true,
|
|
338
|
-
font: { face: 'JetBrains Mono', size: 12, color: '#a5f3fc', strokeWidth: 0, vadjust: -30 },
|
|
339
|
-
title: undefined // Disable default title
|
|
340
|
-
},
|
|
341
|
-
edges: {
|
|
342
|
-
width: 1,
|
|
343
|
-
smooth: { type: 'continuous', roundness: 0.4 },
|
|
344
|
-
color: { inherit: false, color: 'rgba(30, 41, 59, 0.4)', highlight: '#38bdf8' },
|
|
345
|
-
arrows: { to: { enabled: true, scaleFactor: 0.5 } }
|
|
346
|
-
},
|
|
347
|
-
physics: {
|
|
348
|
-
forceAtlas2Based: {
|
|
349
|
-
gravitationalConstant: -50,
|
|
350
|
-
centralGravity: 0.005,
|
|
351
|
-
springLength: 200,
|
|
352
|
-
springConstant: 0.08
|
|
353
|
-
},
|
|
354
|
-
maxVelocity: 50,
|
|
355
|
-
solver: 'forceAtlas2Based',
|
|
356
|
-
timestep: 0.35,
|
|
357
|
-
stabilization: { enabled: true, iterations: 1000 }
|
|
358
|
-
},
|
|
359
|
-
interaction: {
|
|
360
|
-
hover: true,
|
|
361
|
-
tooltipDelay: 100,
|
|
362
|
-
hideEdgesOnDrag: true,
|
|
363
|
-
dragNodes: true,
|
|
364
|
-
zoomView: true
|
|
365
|
-
}
|
|
366
|
-
};
|
|
367
|
-
|
|
368
|
-
const network = new vis.Network(container, data, options);
|
|
369
|
-
|
|
370
|
-
// -- MOUSE INTERACTION FOR TOOLTIP --
|
|
371
|
-
network.on("hoverNode", function (params) {
|
|
372
|
-
const nodeId = params.node;
|
|
373
|
-
const node = nodes.get(nodeId);
|
|
374
|
-
const tooltip = document.getElementById('custom-tooltip');
|
|
375
|
-
|
|
376
|
-
// Populate tooltip
|
|
377
|
-
document.getElementById('tooltip-title').innerText = node.label.startsWith('GET') || node.label.startsWith('POST') ? node.label : 'FUNCTION / MODULE';
|
|
378
|
-
document.getElementById('tooltip-path').innerText = node.label;
|
|
379
|
-
document.getElementById('tooltip-file').innerText = node.id.split('/').pop(); // Simple filename
|
|
380
|
-
document.getElementById('tooltip-line').innerText = '--';
|
|
381
|
-
|
|
382
|
-
// Position tooltip
|
|
383
|
-
const canvasPos = network.canvasToDOM(network.getPositions([nodeId])[nodeId]);
|
|
384
|
-
tooltip.style.left = (canvasPos.x + 20) + 'px';
|
|
385
|
-
tooltip.style.top = (canvasPos.y - 20) + 'px';
|
|
386
|
-
tooltip.style.display = 'block';
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
network.on("blurNode", function (params) {
|
|
390
|
-
document.getElementById('custom-tooltip').style.display = 'none';
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
// -- CLICK INTERACTION --
|
|
394
|
-
network.on("click", function (params) {
|
|
395
|
-
if (params.nodes.length > 0) {
|
|
396
|
-
const nodeId = params.nodes[0];
|
|
397
|
-
const node = nodes.get(nodeId);
|
|
398
|
-
openNodeDetails(node);
|
|
399
|
-
} else {
|
|
400
|
-
closeDetails();
|
|
401
|
-
}
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
function openNodeDetails(node) {
|
|
405
|
-
const container = document.getElementById('detail-content');
|
|
406
|
-
document.getElementById('detail-type').innerText = "NODE DETAILS";
|
|
407
|
-
document.getElementById('detail-title').innerText = node.label;
|
|
408
|
-
|
|
409
|
-
container.innerHTML = \`
|
|
410
|
-
<div class="grid grid-cols-2 gap-4">
|
|
411
|
-
<div class="bg-zinc-900/50 p-4 rounded border border-zinc-800">
|
|
412
|
-
<div class="text-xs text-zinc-500 uppercase tracking-widest mb-1">Impact</div>
|
|
413
|
-
<div class="text-2xl font-mono text-accent">\${node.data.blastRadius}%</div>
|
|
414
|
-
</div>
|
|
415
|
-
<div class="bg-zinc-900/50 p-4 rounded border border-zinc-800">
|
|
416
|
-
<div class="text-xs text-zinc-500 uppercase tracking-widest mb-1">References</div>
|
|
417
|
-
<div class="text-2xl font-mono text-secondary">\${node.data.affectedFiles}</div>
|
|
418
|
-
</div>
|
|
419
|
-
</div>
|
|
420
|
-
|
|
421
|
-
<div>
|
|
422
|
-
<div class="text-xs text-zinc-500 uppercase tracking-widest mb-3 flex items-center gap-2">
|
|
423
|
-
<span class="w-1.5 h-1.5 bg-primary rounded-full animate-pulse"></span>
|
|
424
|
-
AI Analysis
|
|
425
|
-
</div>
|
|
426
|
-
<div class="text-sm text-zinc-400 leading-relaxed font-light">
|
|
427
|
-
High connectivity detected. This module acts as a central hub for data processing. Recommended to decouple dependencies to reduce blast radius.
|
|
428
|
-
</div>
|
|
429
|
-
</div>
|
|
430
|
-
|
|
431
|
-
<div>
|
|
432
|
-
<div class="text-xs text-zinc-500 uppercase tracking-widest mb-3">Location</div>
|
|
433
|
-
<div class="text-sm font-mono text-zinc-300 bg-zinc-900/50 p-2 rounded">\${node.id}</div>
|
|
434
|
-
</div>
|
|
435
|
-
\`;
|
|
436
|
-
|
|
437
|
-
document.getElementById('details-panel').classList.remove('translate-x-full');
|
|
438
|
-
document.getElementById('details-panel').classList.remove('hidden');
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
function openFunctionDetails(fn) {
|
|
442
|
-
const container = document.getElementById('detail-content');
|
|
443
|
-
document.getElementById('detail-type').innerText = "FUNCTION DETAILS";
|
|
444
|
-
document.getElementById('detail-title').innerText = fn.name;
|
|
445
|
-
|
|
446
|
-
const safeCode = fn.code.replace(/</g, '<').replace(/>/g, '>');
|
|
447
|
-
const safeDoc = fn.doc ? fn.doc : 'No documentation available.';
|
|
448
|
-
const paramsList = fn.params.length > 0 ? fn.params.join(', ') : 'None';
|
|
449
|
-
|
|
450
|
-
container.innerHTML = \`
|
|
451
|
-
<div class="space-y-6">
|
|
452
|
-
<!-- Metrics Row -->
|
|
453
|
-
<div class="flex gap-4 border-b border-zinc-800 pb-6">
|
|
454
|
-
<div class="flex-1">
|
|
455
|
-
<div class="text-[10px] text-zinc-500 uppercase tracking-widest mb-1">File</div>
|
|
456
|
-
<div class="text-xs font-mono text-zinc-300 break-all">\${fn.file.split('/').pop()}</div>
|
|
457
|
-
</div>
|
|
458
|
-
<div>
|
|
459
|
-
<div class="text-[10px] text-zinc-500 uppercase tracking-widest mb-1">Line</div>
|
|
460
|
-
<div class="text-xs font-mono text-accent">L\${fn.line}</div>
|
|
461
|
-
</div>
|
|
462
|
-
</div>
|
|
463
|
-
|
|
464
|
-
<!-- Params -->
|
|
465
|
-
<div>
|
|
466
|
-
<div class="text-[10px] text-zinc-500 uppercase tracking-widest mb-2">Parameters</div>
|
|
467
|
-
<div class="flex flex-wrap gap-2">
|
|
468
|
-
\${fn.params.map(p => \`<span class="px-2 py-1 bg-zinc-800 rounded text-xs font-mono text-primary">\${p}</span>\`).join('') || '<span class="text-zinc-600 text-xs italic">None</span>'}
|
|
469
|
-
</div>
|
|
470
|
-
</div>
|
|
471
|
-
|
|
472
|
-
<!-- Description / Doc -->
|
|
473
|
-
<div>
|
|
474
|
-
<div class="text-[10px] text-zinc-500 uppercase tracking-widest mb-2 flex items-center gap-2">
|
|
475
|
-
Description
|
|
476
|
-
</div>
|
|
477
|
-
<div class="text-sm text-zinc-400 font-light leading-relaxed bg-zinc-900/30 p-3 rounded border border-white/5 whitespace-pre-wrap">\${safeDoc}</div>
|
|
478
|
-
</div>
|
|
479
|
-
|
|
480
|
-
<!-- Source Toggle -->
|
|
481
|
-
<div>
|
|
482
|
-
<div class="text-[10px] text-zinc-500 uppercase tracking-widest mb-2">Source Preview</div>
|
|
483
|
-
<pre class="language-javascript text-xs max-h-[300px] overflow-auto"><code class="language-javascript">\${safeCode}</code></pre>
|
|
484
|
-
</div>
|
|
485
|
-
</div>
|
|
486
|
-
\`;
|
|
487
|
-
|
|
488
|
-
// Re-highlight prism
|
|
489
|
-
Prism.highlightAllUnder(container);
|
|
490
|
-
|
|
491
|
-
document.getElementById('details-panel').classList.remove('translate-x-full');
|
|
492
|
-
document.getElementById('details-panel').classList.remove('hidden');
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
function closeDetails() {
|
|
496
|
-
document.getElementById('details-panel').classList.add('translate-x-full');
|
|
497
|
-
network.unselectAll();
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
function resetView() {
|
|
501
|
-
network.fit({ animation: true });
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
function searchNodes() {
|
|
505
|
-
const query = document.getElementById('search-input').value.toLowerCase();
|
|
506
|
-
if (!query) return;
|
|
507
|
-
|
|
508
|
-
const allNodes = nodes.get();
|
|
509
|
-
const found = allNodes.find(n => n.label.toLowerCase().includes(query));
|
|
510
|
-
|
|
511
|
-
if (found) {
|
|
512
|
-
switchView('network'); // Ensure we are on graph view
|
|
513
|
-
network.focus(found.id, {
|
|
514
|
-
scale: 1.5,
|
|
515
|
-
animation: { duration: 1000, easingFunction: 'easeInOutQuad' }
|
|
107
|
+
// --- DEPENDENCY USAGE LOGIC ---
|
|
108
|
+
const depUsageData = {};
|
|
109
|
+
const dependencies = analysis.dependencies || {};
|
|
110
|
+
// Initialize usage arrays
|
|
111
|
+
Object.keys(dependencies).forEach(dep => depUsageData[dep] = []);
|
|
112
|
+
// Scan all files imports to map back to dependencies
|
|
113
|
+
Object.entries(relativeFiles).forEach(([relPath, data]) => {
|
|
114
|
+
if (data.imports && Array.isArray(data.imports)) {
|
|
115
|
+
data.imports.forEach((imp) => {
|
|
116
|
+
// Check against all dependencies (exact match or scoped/subpath)
|
|
117
|
+
// e.g. import 'react' matches dep 'react'
|
|
118
|
+
// e.g. import 'lodash/map' matches dep 'lodash'
|
|
119
|
+
const matchedDep = Object.keys(dependencies).find(depName => {
|
|
120
|
+
return imp === depName || imp.startsWith(depName + '/');
|
|
516
121
|
});
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
function switchView(viewName) {
|
|
523
|
-
// Update tabs
|
|
524
|
-
document.querySelectorAll('.nav-tab').forEach(el => el.classList.remove('active'));
|
|
525
|
-
// Find button by onclick text content
|
|
526
|
-
const btns = document.querySelectorAll('.nav-tab');
|
|
527
|
-
for(let btn of btns) {
|
|
528
|
-
if(btn.getAttribute('onclick').includes(viewName)) {
|
|
529
|
-
btn.classList.add('active');
|
|
530
|
-
break;
|
|
122
|
+
if (matchedDep) {
|
|
123
|
+
if (!depUsageData[matchedDep].includes(relPath)) {
|
|
124
|
+
depUsageData[matchedDep].push(relPath);
|
|
125
|
+
}
|
|
531
126
|
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
// Hide all views
|
|
535
|
-
document.querySelectorAll('.view-panel').forEach(el => el.classList.add('hidden'));
|
|
536
|
-
|
|
537
|
-
if (viewName === 'network') {
|
|
538
|
-
document.getElementById('network-view').classList.remove('hidden');
|
|
539
|
-
} else if (viewName === 'functions') {
|
|
540
|
-
document.getElementById('functions-view').classList.remove('hidden');
|
|
541
|
-
renderFunctions();
|
|
542
|
-
} else if (viewName === 'files') {
|
|
543
|
-
document.getElementById('files-view').classList.remove('hidden');
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
let functionsRendered = false;
|
|
548
|
-
function renderFunctions() {
|
|
549
|
-
if (functionsRendered) return;
|
|
550
|
-
const grid = document.getElementById('functions-grid');
|
|
551
|
-
|
|
552
|
-
// Limit to 200 functions to assume performance if huge repo
|
|
553
|
-
const displayData = functionsData; // .slice(0, 200);
|
|
554
|
-
|
|
555
|
-
displayData.forEach((fn, index) => {
|
|
556
|
-
const card = document.createElement('div');
|
|
557
|
-
card.className = 'bg-zinc-900/50 border border-zinc-800 p-4 rounded hover:border-primary/50 transition-colors group cursor-pointer flex flex-col justify-between h-[100px]';
|
|
558
|
-
card.onclick = () => openFunctionDetails(fn);
|
|
559
|
-
|
|
560
|
-
card.innerHTML = \`
|
|
561
|
-
<div class="flex justify-between items-start">
|
|
562
|
-
<span class="text-primary font-mono font-bold group-hover:text-white transition-colors truncate w-3/4">\${fn.name}</span>
|
|
563
|
-
<span class="text-xs text-zinc-500 font-mono">L\${fn.line}</span>
|
|
564
|
-
</div>
|
|
565
|
-
<div class="mt-2">
|
|
566
|
-
<div class="text-[10px] text-zinc-500 uppercase tracking-wider mb-1">File</div>
|
|
567
|
-
<div class="text-xs text-zinc-400 truncate">\${fn.file.split('/').slice(-2).join('/')}</div>
|
|
568
|
-
</div>
|
|
569
|
-
\`;
|
|
570
|
-
grid.appendChild(card);
|
|
571
127
|
});
|
|
572
|
-
functionsRendered = true;
|
|
573
128
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
129
|
+
});
|
|
130
|
+
// --- TEMPLATE LOADING ---
|
|
131
|
+
const templatePath = path_1.default.join(__dirname, 'template.html');
|
|
132
|
+
let html = '';
|
|
133
|
+
try {
|
|
134
|
+
html = await fs_extra_1.default.readFile(templatePath, 'utf-8');
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
console.error('Error reading HTML template:', e);
|
|
138
|
+
html = '<h1>Error loading template. Ensure dist/report/template.html exists.</h1>';
|
|
139
|
+
}
|
|
140
|
+
// --- DATA INJECTION ---
|
|
141
|
+
const base64JSON = (data) => Buffer.from(JSON.stringify(data)).toString('base64');
|
|
142
|
+
html = html.replace('{{NODES_JSON}}', () => base64JSON(nodes));
|
|
143
|
+
html = html.replace('{{EDGES_JSON}}', () => base64JSON(edges));
|
|
144
|
+
html = html.replace('{{FUNCTIONS_JSON}}', () => base64JSON(functionsList));
|
|
145
|
+
html = html.replace('{{FILES_JSON}}', () => base64JSON(relativeFiles));
|
|
146
|
+
html = html.replace('{{DEPS_JSON}}', () => base64JSON(dependencies));
|
|
147
|
+
html = html.replace('{{DEP_USAGE_JSON}}', () => base64JSON(depUsageData));
|
|
581
148
|
await fs_extra_1.default.writeFile(outputPath, html);
|
|
582
149
|
}
|
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" class="dark">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>Projetify</title>
|
|
8
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
9
|
+
<script>
|
|
10
|
+
tailwind.config = {
|
|
11
|
+
darkMode: 'class',
|
|
12
|
+
theme: {
|
|
13
|
+
extend: {
|
|
14
|
+
fontFamily: {
|
|
15
|
+
sans: ['Inter', 'sans-serif'],
|
|
16
|
+
mono: ['JetBrains Mono', 'monospace'],
|
|
17
|
+
},
|
|
18
|
+
colors: {
|
|
19
|
+
background: '#000000',
|
|
20
|
+
surface: '#0a0a0a',
|
|
21
|
+
primary: '#22d3ee', // cyan-400
|
|
22
|
+
secondary: '#818cf8', // indigo-400
|
|
23
|
+
accent: '#f472b6', // pink-400
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
</script>
|
|
29
|
+
<script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
|
|
30
|
+
<link
|
|
31
|
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap"
|
|
32
|
+
rel="stylesheet">
|
|
33
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/prism.min.js"></script>
|
|
34
|
+
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/themes/prism-tomorrow.min.css" rel="stylesheet" />
|
|
35
|
+
|
|
36
|
+
<style>
|
|
37
|
+
body {
|
|
38
|
+
background-color: #000000;
|
|
39
|
+
color: #ecfeff;
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
background-image: radial-gradient(circle at 50% 50%, #111827 0%, #000000 100%);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.glass {
|
|
45
|
+
background: rgba(10, 10, 10, 0.6);
|
|
46
|
+
backdrop-filter: blur(8px);
|
|
47
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.neon-text {
|
|
51
|
+
text-shadow: 0 0 10px rgba(34, 211, 238, 0.5);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.vis-network {
|
|
55
|
+
outline: none;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Custom Scrollbar */
|
|
59
|
+
::-webkit-scrollbar {
|
|
60
|
+
width: 4px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
::-webkit-scrollbar-track {
|
|
64
|
+
background: transparent;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
::-webkit-scrollbar-thumb {
|
|
68
|
+
background: #334155;
|
|
69
|
+
border-radius: 2px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Navigation Tabs */
|
|
73
|
+
.nav-tab {
|
|
74
|
+
position: relative;
|
|
75
|
+
color: #94a3b8;
|
|
76
|
+
transition: color 0.2s;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.nav-tab.active {
|
|
80
|
+
color: #fff;
|
|
81
|
+
font-weight: 600;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.nav-tab.active::after {
|
|
85
|
+
content: '';
|
|
86
|
+
position: absolute;
|
|
87
|
+
bottom: -18px;
|
|
88
|
+
left: 0;
|
|
89
|
+
right: 0;
|
|
90
|
+
height: 2px;
|
|
91
|
+
background: #a855f7;
|
|
92
|
+
/* Purple */
|
|
93
|
+
box-shadow: 0 0 10px #a855f7;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Snap Button */
|
|
97
|
+
.snap-btn {
|
|
98
|
+
background: rgba(20, 20, 20, 0.8);
|
|
99
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
100
|
+
backdrop-filter: blur(4px);
|
|
101
|
+
color: #94a3b8;
|
|
102
|
+
border-radius: 16px;
|
|
103
|
+
transition: all 0.2s;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.snap-btn:hover {
|
|
107
|
+
color: #fff;
|
|
108
|
+
border-color: rgba(255, 255, 255, 0.3);
|
|
109
|
+
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* Custom Tooltip */
|
|
113
|
+
#custom-tooltip {
|
|
114
|
+
pointer-events: none;
|
|
115
|
+
z-index: 100;
|
|
116
|
+
transition: opacity 0.1s;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* Code Block Styling */
|
|
120
|
+
pre[class*="language-"] {
|
|
121
|
+
background: #0f172a !important;
|
|
122
|
+
border-radius: 8px;
|
|
123
|
+
border: 1px solid #1e293b;
|
|
124
|
+
margin: 0;
|
|
125
|
+
}
|
|
126
|
+
</style>
|
|
127
|
+
</head>
|
|
128
|
+
|
|
129
|
+
<body class="flex flex-col h-screen font-sans selection:bg-primary/30 selection:text-white">
|
|
130
|
+
|
|
131
|
+
<!-- TOP NAVIGATION -->
|
|
132
|
+
<nav class="h-16 border-b border-white/5 flex items-center justify-between px-6 bg-black z-50">
|
|
133
|
+
<!-- Logo & Breadcrumb -->
|
|
134
|
+
<div class="flex items-center gap-4">
|
|
135
|
+
<div class="flex items-center gap-2 text-primary font-mono tracking-wider font-bold text-lg neon-text">
|
|
136
|
+
<span>></span> Projectify
|
|
137
|
+
</div>
|
|
138
|
+
<div class="text-zinc-600 font-light text-sm tracking-widest uppercase flex items-center gap-2">
|
|
139
|
+
<span>//</span>
|
|
140
|
+
<span>KNOWLEDGE GRAPH</span>
|
|
141
|
+
</div>
|
|
142
|
+
<!-- Navigation Arrows (Static) -->
|
|
143
|
+
<div class="flex gap-1 ml-4 text-zinc-700">
|
|
144
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
145
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
146
|
+
d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
|
|
147
|
+
</svg>
|
|
148
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
149
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3">
|
|
150
|
+
</path>
|
|
151
|
+
</svg>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<!-- Center Tabs -->
|
|
156
|
+
<div class="hidden md:flex items-center gap-8 text-sm uppercase tracking-wider">
|
|
157
|
+
<button class="nav-tab hover:text-white" onclick="switchView('network')">GRAPH</button>
|
|
158
|
+
<button class="nav-tab active" onclick="switchView('functions')">FUNCTIONS</button>
|
|
159
|
+
<button class="nav-tab hover:text-white" onclick="switchView('files')">FILES</button>
|
|
160
|
+
<button class="nav-tab hover:text-white" onclick="switchView('dependencies')">DEPENDENCIES</button>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<!-- Right Search -->
|
|
164
|
+
<div class="relative group w-64">
|
|
165
|
+
<input type="text" id="search-input"
|
|
166
|
+
class="w-full bg-zinc-900/50 border border-zinc-800 rounded-full px-4 py-1.5 text-xs text-zinc-300 focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/50 transition-all font-mono"
|
|
167
|
+
placeholder="SEARCH NODE..." onkeydown="if(event.key === 'Enter') searchNodes()">
|
|
168
|
+
<div class="absolute right-2 top-1.5 text-zinc-600">
|
|
169
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
170
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
171
|
+
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
|
172
|
+
</svg>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<div
|
|
177
|
+
class="ml-4 p-2 rounded border border-zinc-800 text-zinc-500 hover:text-white hover:border-zinc-600 transition-colors cursor-pointer">
|
|
178
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
179
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
180
|
+
d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z">
|
|
181
|
+
</path>
|
|
182
|
+
</svg>
|
|
183
|
+
</div>
|
|
184
|
+
</nav>
|
|
185
|
+
|
|
186
|
+
<!-- CONTENT AREA -->
|
|
187
|
+
<div class="flex-1 relative overflow-hidden">
|
|
188
|
+
|
|
189
|
+
<!-- GRAPH CONTAINER -->
|
|
190
|
+
<div id="network-view" class="view-panel absolute inset-0 z-0 cursor-crosshair"></div>
|
|
191
|
+
|
|
192
|
+
<!-- FUNCTIONS CONTAINER -->
|
|
193
|
+
<div id="functions-view"
|
|
194
|
+
class="view-panel absolute inset-0 z-20 bg-black hidden flex flex-col p-8 overflow-y-auto">
|
|
195
|
+
<div class="max-w-6xl mx-auto w-full">
|
|
196
|
+
<h2 class="text-2xl font-mono text-primary mb-6 neon-text">Available Functions</h2>
|
|
197
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" id="functions-grid">
|
|
198
|
+
<!-- Functions injected here -->
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
|
|
203
|
+
<!-- FILES CONTAINER -->
|
|
204
|
+
<div id="files-view" class="view-panel absolute inset-0 z-20 bg-black hidden flex h-full">
|
|
205
|
+
<!-- Sidebar -->
|
|
206
|
+
<div class="w-1/4 h-full border-r border-zinc-800 bg-zinc-900/30 overflow-y-auto p-4 custom-scrollbar">
|
|
207
|
+
<h3 class="text-xs font-mono uppercase tracking-widest text-zinc-500 mb-4">Project Explorer</h3>
|
|
208
|
+
<div id="file-tree" class="font-mono text-sm space-y-1"></div>
|
|
209
|
+
</div>
|
|
210
|
+
<!-- Code Viewer -->
|
|
211
|
+
<div class="flex-1 h-full bg-zinc-950 overflow-auto p-6 relative">
|
|
212
|
+
<div id="file-placeholder"
|
|
213
|
+
class="absolute inset-0 flex items-center justify-center text-zinc-700 font-mono text-sm">
|
|
214
|
+
< SELECT A FILE TO VIEW SOURCE >
|
|
215
|
+
</div>
|
|
216
|
+
<div id="code-container" class="hidden">
|
|
217
|
+
<div class="flex justify-between items-center mb-4 border-b border-zinc-800 pb-2">
|
|
218
|
+
<span id="current-filename" class="text-primary font-mono font-bold"></span>
|
|
219
|
+
<span id="current-lang"
|
|
220
|
+
class="text-[10px] uppercase text-zinc-500 bg-zinc-900 px-2 py-1 rounded"></span>
|
|
221
|
+
</div>
|
|
222
|
+
<pre><code id="code-content" class="language-javascript"></code></pre>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
<!-- DEPENDENCIES CONTAINER -->
|
|
228
|
+
<div id="dependencies-view"
|
|
229
|
+
class="view-panel absolute inset-0 z-20 bg-black hidden flex flex-col p-8 overflow-y-auto">
|
|
230
|
+
<div class="max-w-4xl mx-auto w-full">
|
|
231
|
+
<h2 class="text-2xl font-mono text-accent mb-6 neon-text">Dependencies & Libraries</h2>
|
|
232
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4" id="deps-grid">
|
|
233
|
+
<!-- Deps injected here -->
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
|
|
238
|
+
<!-- CUSTOM TOOLTIP -->
|
|
239
|
+
<div id="custom-tooltip"
|
|
240
|
+
class="fixed hidden p-4 bg-black/90 border border-primary/50 text-xs font-mono rounded shadow-[0_0_20px_rgba(34,211,238,0.2)]">
|
|
241
|
+
<div id="tooltip-title" class="text-primary font-bold text-sm mb-2"></div>
|
|
242
|
+
<div class="space-y-1 text-zinc-400">
|
|
243
|
+
<div class="flex gap-2"><span class="w-16 text-zinc-600">PATH:</span> <span id="tooltip-path"
|
|
244
|
+
class="text-white"></span></div>
|
|
245
|
+
<div class="flex gap-2"><span class="w-16 text-zinc-600">FILE:</span> <span id="tooltip-file"
|
|
246
|
+
class="text-white"></span></div>
|
|
247
|
+
<div class="flex gap-2"><span class="w-16 text-zinc-600">LINE:</span> <span id="tooltip-line"
|
|
248
|
+
class="text-accent"></span></div>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
<!-- DETAILS SIDEBAR (Overlay, conditionally hidden) -->
|
|
253
|
+
<div id="details-panel"
|
|
254
|
+
class="absolute top-0 right-0 h-full w-[500px] bg-black/95 backdrop-blur-xl border-l border-white/10 transform translate-x-full transition-transform duration-300 z-30 flex flex-col shadow-2xl">
|
|
255
|
+
<div class="p-6 border-b border-white/10 flex justify-between items-center">
|
|
256
|
+
<div>
|
|
257
|
+
<div class="text-[10px] text-primary tracking-widest uppercase mb-1" id="detail-type">Node Details
|
|
258
|
+
</div>
|
|
259
|
+
<h2 id="detail-title" class="text-lg font-mono font-bold text-white break-all"></h2>
|
|
260
|
+
</div>
|
|
261
|
+
<button onclick="closeDetails()" class="text-zinc-500 hover:text-white">
|
|
262
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
263
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12">
|
|
264
|
+
</path>
|
|
265
|
+
</svg>
|
|
266
|
+
</button>
|
|
267
|
+
</div>
|
|
268
|
+
|
|
269
|
+
<div class="p-6 space-y-8 flex-1 overflow-y-auto" id="detail-content">
|
|
270
|
+
<!-- Content injected dynamically -->
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
|
|
274
|
+
<!-- BOTTOM CONTROLS (Floating) -->
|
|
275
|
+
<div class="absolute bottom-8 left-1/2 transform -translate-x-1/2 z-10">
|
|
276
|
+
<button class="snap-btn py-4 px-6 flex flex-col items-center gap-1 group" onclick="resetView()">
|
|
277
|
+
<div
|
|
278
|
+
class="w-6 h-6 border-2 border-current rounded mb-1 bg-gradient-to-tr from-transparent to-white/10">
|
|
279
|
+
</div>
|
|
280
|
+
<span class="text-[10px] tracking-widest font-bold font-mono">SNAP</span>
|
|
281
|
+
</button>
|
|
282
|
+
</div>
|
|
283
|
+
|
|
284
|
+
</div>
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
<!-- DATA INJECTION -->
|
|
288
|
+
<script id="nodes-data" type="application/json">
|
|
289
|
+
{{NODES_JSON}}
|
|
290
|
+
</script>
|
|
291
|
+
<script id="edges-data" type="application/json">
|
|
292
|
+
{{EDGES_JSON}}
|
|
293
|
+
</script>
|
|
294
|
+
<script id="functions-data" type="application/json">
|
|
295
|
+
{{FUNCTIONS_JSON}}
|
|
296
|
+
</script>
|
|
297
|
+
<script id="files-data" type="application/json">
|
|
298
|
+
{{FILES_JSON}}
|
|
299
|
+
</script>
|
|
300
|
+
<script id="deps-data" type="application/json">
|
|
301
|
+
{{DEPS_JSON}}
|
|
302
|
+
</script>
|
|
303
|
+
<script id="dep-usage-data" type="application/json">
|
|
304
|
+
{{DEP_USAGE_JSON}}
|
|
305
|
+
</script>
|
|
306
|
+
|
|
307
|
+
<script>
|
|
308
|
+
// Helper to safely decode Base64 JSON
|
|
309
|
+
const decodeData = (id) => {
|
|
310
|
+
try {
|
|
311
|
+
const raw = document.getElementById(id).textContent.trim();
|
|
312
|
+
return JSON.parse(atob(raw));
|
|
313
|
+
} catch (e) {
|
|
314
|
+
console.error(`Failed to decode data for ${id}`, e);
|
|
315
|
+
return {};
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const nodesData = decodeData('nodes-data');
|
|
320
|
+
const edgesData = decodeData('edges-data');
|
|
321
|
+
const functionsData = decodeData('functions-data');
|
|
322
|
+
const filesData = decodeData('files-data');
|
|
323
|
+
const depsData = decodeData('deps-data');
|
|
324
|
+
const depUsageData = decodeData('dep-usage-data');
|
|
325
|
+
|
|
326
|
+
const nodes = new vis.DataSet(nodesData);
|
|
327
|
+
const edges = new vis.DataSet(edgesData);
|
|
328
|
+
|
|
329
|
+
const container = document.getElementById('network-view');
|
|
330
|
+
const data = { nodes: nodes, edges: edges };
|
|
331
|
+
|
|
332
|
+
const options = {
|
|
333
|
+
nodes: { borderWidth: 0, shadow: true, font: { face: 'JetBrains Mono', size: 12, color: '#a5f3fc', strokeWidth: 0, vadjust: -30 }, title: undefined },
|
|
334
|
+
edges: { width: 1, smooth: { type: 'continuous', roundness: 0.4 }, color: { inherit: false, color: 'rgba(30, 41, 59, 0.4)', highlight: '#38bdf8' }, arrows: { to: { enabled: true, scaleFactor: 0.5 } } },
|
|
335
|
+
physics: { forceAtlas2Based: { gravitationalConstant: -50, centralGravity: 0.005, springLength: 200, springConstant: 0.08 }, maxVelocity: 50, solver: 'forceAtlas2Based', timestep: 0.35, stabilization: { enabled: true, iterations: 1000 } },
|
|
336
|
+
interaction: { hover: true, tooltipDelay: 100, hideEdgesOnDrag: true, dragNodes: true, zoomView: true }
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const network = new vis.Network(container, data, options);
|
|
340
|
+
|
|
341
|
+
// -- MOUSE INTERACTION --
|
|
342
|
+
network.on("hoverNode", function (params) {
|
|
343
|
+
const nodeId = params.node;
|
|
344
|
+
const node = nodes.get(nodeId);
|
|
345
|
+
const tooltip = document.getElementById('custom-tooltip');
|
|
346
|
+
document.getElementById('tooltip-title').innerText = node.label.startsWith('GET') || node.label.startsWith('POST') ? node.label : 'FUNCTION / MODULE';
|
|
347
|
+
document.getElementById('tooltip-path').innerText = node.label;
|
|
348
|
+
document.getElementById('tooltip-file').innerText = node.id.split('/').pop();
|
|
349
|
+
document.getElementById('tooltip-line').innerText = '--';
|
|
350
|
+
const canvasPos = network.canvasToDOM(network.getPositions([nodeId])[nodeId]);
|
|
351
|
+
tooltip.style.left = (canvasPos.x + 20) + 'px';
|
|
352
|
+
tooltip.style.top = (canvasPos.y - 20) + 'px';
|
|
353
|
+
tooltip.style.display = 'block';
|
|
354
|
+
});
|
|
355
|
+
network.on("blurNode", params => document.getElementById('custom-tooltip').style.display = 'none');
|
|
356
|
+
network.on("click", params => params.nodes.length > 0 ? openNodeDetails(nodes.get(params.nodes[0])) : closeDetails());
|
|
357
|
+
|
|
358
|
+
function openNodeDetails(node) {
|
|
359
|
+
const container = document.getElementById('detail-content');
|
|
360
|
+
document.getElementById('detail-type').innerText = "NODE DETAILS";
|
|
361
|
+
document.getElementById('detail-title').innerText = node.label;
|
|
362
|
+
container.innerHTML = `<div class="grid grid-cols-2 gap-4"><div class="bg-zinc-900/50 p-4 rounded border border-zinc-800"><div class="text-xs text-zinc-500 uppercase tracking-widest mb-1">Impact</div><div class="text-2xl font-mono text-accent">${node.data.blastRadius}%</div></div><div class="bg-zinc-900/50 p-4 rounded border border-zinc-800"><div class="text-xs text-zinc-500 uppercase tracking-widest mb-1">References</div><div class="text-2xl font-mono text-secondary">${node.data.affectedFiles}</div></div></div><div><div class="text-xs text-zinc-500 uppercase tracking-widest mb-3 flex items-center gap-2"><span class="w-1.5 h-1.5 bg-primary rounded-full animate-pulse"></span>AI Analysis</div><div class="text-sm text-zinc-400 leading-relaxed font-light">High connectivity detected. Recommended to decouple dependencies.</div></div><div><div class="text-xs text-zinc-500 uppercase tracking-widest mb-3">Location</div><div class="text-sm font-mono text-zinc-300 bg-zinc-900/50 p-2 rounded">${node.id}</div></div>`;
|
|
363
|
+
document.getElementById('details-panel').classList.remove('translate-x-full');
|
|
364
|
+
document.getElementById('details-panel').classList.remove('hidden');
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function openFunctionDetails(fn) {
|
|
368
|
+
const container = document.getElementById('detail-content');
|
|
369
|
+
document.getElementById('detail-type').innerText = "FUNCTION DETAILS";
|
|
370
|
+
document.getElementById('detail-title').innerText = fn.name;
|
|
371
|
+
const safeCode = fn.code.replace(/</g, '<').replace(/>/g, '>');
|
|
372
|
+
const safeDoc = fn.doc || 'No documentation.';
|
|
373
|
+
container.innerHTML = `<div class="space-y-6"><div class="flex gap-4 border-b border-zinc-800 pb-6"><div class="flex-1"><div class="text-[10px] text-zinc-500 uppercase tracking-widest mb-1">File</div><div class="text-xs font-mono text-zinc-300 break-all">${fn.file.split('/').pop()}</div></div><div><div class="text-[10px] text-zinc-500 uppercase tracking-widest mb-1">Line</div><div class="text-xs font-mono text-accent">L${fn.line}</div></div></div><div><div class="text-[10px] text-zinc-500 uppercase tracking-widest mb-2">Parameters</div><div class="flex flex-wrap gap-2">${fn.params.map(p => `<span class="px-2 py-1 bg-zinc-800 rounded text-xs font-mono text-primary">${p}</span>`).join('') || '<span class="text-zinc-600 text-xs italic">None</span>'}</div></div><div><div class="text-[10px] text-zinc-500 uppercase tracking-widest mb-2 flex items-center gap-2">Description</div><div class="text-sm text-zinc-400 font-light leading-relaxed bg-zinc-900/30 p-3 rounded border border-white/5 whitespace-pre-wrap">${safeDoc}</div></div><div><div class="text-[10px] text-zinc-500 uppercase tracking-widest mb-2">Source Preview</div><pre class="language-javascript text-xs max-h-[300px] overflow-auto"><code class="language-javascript">${safeCode}</code></pre></div></div>`;
|
|
374
|
+
Prism.highlightAllUnder(container);
|
|
375
|
+
document.getElementById('details-panel').classList.remove('translate-x-full');
|
|
376
|
+
document.getElementById('details-panel').classList.remove('hidden');
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function closeDetails() { document.getElementById('details-panel').classList.add('translate-x-full'); network.unselectAll(); }
|
|
380
|
+
function resetView() { network.fit({ animation: true }); }
|
|
381
|
+
function searchNodes() {
|
|
382
|
+
const query = document.getElementById('search-input').value.toLowerCase();
|
|
383
|
+
if (!query) return;
|
|
384
|
+
const found = nodes.get().find(n => n.label.toLowerCase().includes(query));
|
|
385
|
+
if (found) { switchView('network'); network.focus(found.id, { scale: 1.5, animation: { duration: 1000 } }); network.selectNodes([found.id]); openNodeDetails(found); }
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function switchView(viewName) {
|
|
389
|
+
document.querySelectorAll('.nav-tab').forEach(el => el.classList.remove('active'));
|
|
390
|
+
const btns = document.querySelectorAll('.nav-tab');
|
|
391
|
+
for (let btn of btns) { if (btn.getAttribute('onclick').includes(viewName)) { btn.classList.add('active'); break; } }
|
|
392
|
+
document.querySelectorAll('.view-panel').forEach(el => el.classList.add('hidden'));
|
|
393
|
+
if (viewName === 'network') document.getElementById('network-view').classList.remove('hidden');
|
|
394
|
+
else if (viewName === 'functions') { document.getElementById('functions-view').classList.remove('hidden'); renderFunctions(); }
|
|
395
|
+
else if (viewName === 'files') { document.getElementById('files-view').classList.remove('hidden'); renderFiles(); }
|
|
396
|
+
else if (viewName === 'dependencies') { document.getElementById('dependencies-view').classList.remove('hidden'); renderDependencies(); }
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
let functionsRendered = false;
|
|
400
|
+
function renderFunctions() {
|
|
401
|
+
if (functionsRendered) return;
|
|
402
|
+
const grid = document.getElementById('functions-grid');
|
|
403
|
+
functionsData.forEach(fn => {
|
|
404
|
+
const card = document.createElement('div');
|
|
405
|
+
card.className = 'bg-zinc-900/50 border border-zinc-800 p-4 rounded hover:border-primary/50 transition-colors group cursor-pointer flex flex-col justify-between h-[100px]';
|
|
406
|
+
card.onclick = () => openFunctionDetails(fn);
|
|
407
|
+
card.innerHTML = `<div class="flex justify-between items-start"><span class="text-primary font-mono font-bold group-hover:text-white transition-colors truncate w-3/4">${fn.name}</span><span class="text-xs text-zinc-500 font-mono">L${fn.line}</span></div><div class="mt-2"><div class="text-[10px] text-zinc-500 uppercase tracking-widest mb-1">File</div><div class="text-xs text-zinc-400 truncate">${fn.file.split('/').slice(-2).join('/')}</div></div>`;
|
|
408
|
+
grid.appendChild(card);
|
|
409
|
+
});
|
|
410
|
+
functionsRendered = true;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
let depsRendered = false;
|
|
414
|
+
function renderDependencies() {
|
|
415
|
+
if (depsRendered) return;
|
|
416
|
+
const grid = document.getElementById('deps-grid');
|
|
417
|
+
Object.entries(depsData).forEach(([name, version]) => {
|
|
418
|
+
const usages = depUsageData[name] || [];
|
|
419
|
+
const card = document.createElement('div');
|
|
420
|
+
card.className = 'bg-zinc-900/50 border border-zinc-800 p-4 rounded flex flex-col gap-2';
|
|
421
|
+
|
|
422
|
+
let usageHtml = '';
|
|
423
|
+
if (usages.length > 0) {
|
|
424
|
+
usageHtml = `
|
|
425
|
+
<div class="mt-2 text-xs text-zinc-500 border-t border-zinc-800 pt-2">
|
|
426
|
+
<span class="uppercase tracking-widest font-bold">Used by (${usages.length})</span>
|
|
427
|
+
<div class="mt-1 flex flex-col gap-1 max-h-32 overflow-y-auto custom-scrollbar">
|
|
428
|
+
${usages.map(f => `<span class="text-zinc-400 font-mono truncate hover:text-white cursor-pointer" onclick="switchView('files'); viewFileByPath('${f}')">${f}</span>`).join('')}
|
|
429
|
+
</div>
|
|
430
|
+
</div>
|
|
431
|
+
`;
|
|
432
|
+
} else {
|
|
433
|
+
usageHtml = `<div class="mt-2 text-xs text-zinc-600 italic">No direct imports detected.</div>`;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
card.innerHTML = `
|
|
437
|
+
<div class="flex justify-between items-center">
|
|
438
|
+
<span class="text-zinc-300 font-mono font-bold">${name}</span>
|
|
439
|
+
<span class="px-2 py-1 rounded bg-secondary/10 text-secondary text-xs border border-secondary/20">${version}</span>
|
|
440
|
+
</div>
|
|
441
|
+
${usageHtml}
|
|
442
|
+
`;
|
|
443
|
+
grid.appendChild(card);
|
|
444
|
+
});
|
|
445
|
+
depsRendered = true;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
let filesRendered = false;
|
|
449
|
+
function renderFiles() {
|
|
450
|
+
if (filesRendered) return;
|
|
451
|
+
const treeContainer = document.getElementById('file-tree');
|
|
452
|
+
const paths = Object.keys(filesData).sort();
|
|
453
|
+
const root = {};
|
|
454
|
+
|
|
455
|
+
// Build Tree
|
|
456
|
+
paths.forEach(p => {
|
|
457
|
+
const parts = p.split('/');
|
|
458
|
+
let current = root;
|
|
459
|
+
parts.forEach((part, i) => {
|
|
460
|
+
if (!current[part]) current[part] = (i === parts.length - 1) ? { __file: true, path: p } : {};
|
|
461
|
+
current = current[part];
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
// Render Tree
|
|
466
|
+
function createTreeEl(obj, depth = 0) {
|
|
467
|
+
const ul = document.createElement('ul');
|
|
468
|
+
ul.className = depth > 0 ? 'ml-4 border-l border-zinc-800 pl-2' : '';
|
|
469
|
+
Object.keys(obj).sort().forEach(key => {
|
|
470
|
+
if (key === '__file' || key === 'path') return;
|
|
471
|
+
const li = document.createElement('li');
|
|
472
|
+
const isFile = obj[key].__file;
|
|
473
|
+
|
|
474
|
+
if (isFile) {
|
|
475
|
+
li.innerHTML = `<div class="py-1 cursor-pointer text-zinc-400 hover:text-white flex items-center gap-2 group" onclick="viewFile('${obj[key].path}', this)"><svg class="w-3 h-3 text-zinc-600 group-hover:text-primary transition-colors" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /></svg>${key}</div>`;
|
|
476
|
+
} else {
|
|
477
|
+
li.innerHTML = `<div class="py-1 text-zinc-500 text-xs font-bold uppercase tracking-wider mb-1 mt-2">${key}</div>`;
|
|
478
|
+
li.appendChild(createTreeEl(obj[key], depth + 1));
|
|
479
|
+
}
|
|
480
|
+
ul.appendChild(li);
|
|
481
|
+
});
|
|
482
|
+
return ul;
|
|
483
|
+
}
|
|
484
|
+
treeContainer.appendChild(createTreeEl(root));
|
|
485
|
+
filesRendered = true;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
window.viewFile = (path, el) => {
|
|
489
|
+
document.querySelectorAll('#file-tree div').forEach(d => d.classList.remove('text-primary', 'font-bold'));
|
|
490
|
+
el && el.classList.add('text-primary', 'font-bold');
|
|
491
|
+
|
|
492
|
+
const fileData = filesData[path];
|
|
493
|
+
document.getElementById('file-placeholder').classList.add('hidden');
|
|
494
|
+
document.getElementById('code-container').classList.remove('hidden');
|
|
495
|
+
document.getElementById('current-filename').innerText = path;
|
|
496
|
+
const lid = fileData.language || 'javascript';
|
|
497
|
+
document.getElementById('current-lang').innerText = lid;
|
|
498
|
+
|
|
499
|
+
const codeEl = document.getElementById('code-content');
|
|
500
|
+
codeEl.className = 'language-' + lid;
|
|
501
|
+
codeEl.textContent = fileData.content;
|
|
502
|
+
Prism.highlightElement(codeEl);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
window.viewFileByPath = (filePath) => {
|
|
506
|
+
const fileData = filesData[filePath];
|
|
507
|
+
if (fileData) {
|
|
508
|
+
window.viewFile(filePath, null);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
document.querySelector('.nav-tab').classList.add('active');
|
|
513
|
+
</script>
|
|
514
|
+
</body>
|
|
515
|
+
|
|
516
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "projectify-cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.5",
|
|
4
4
|
"description": "Project Analyzer using LangChain",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
21
|
"prepublishOnly": "npm run build",
|
|
22
|
-
"build": "tsc && echo '#!/usr/bin/env node' | cat - dist/index.js > dist/index.js.tmp && mv dist/index.js.tmp dist/index.js && chmod +x dist/index.js",
|
|
22
|
+
"build": "tsc && cp src/report/template.html dist/report/ && echo '#!/usr/bin/env node' | cat - dist/index.js > dist/index.js.tmp && mv dist/index.js.tmp dist/index.js && chmod +x dist/index.js",
|
|
23
23
|
"start": "node dist/index.js",
|
|
24
24
|
"test": "jest",
|
|
25
25
|
"dev": "ts-node src/index.ts"
|
|
@@ -36,9 +36,10 @@
|
|
|
36
36
|
"@babel/traverse": "^7.24.1",
|
|
37
37
|
"@google/generative-ai": "^0.24.1",
|
|
38
38
|
"@langchain/core": "^1.1.8",
|
|
39
|
+
"@langchain/google-genai": "^2.1.3",
|
|
39
40
|
"@langchain/langgraph": "^1.0.7",
|
|
40
41
|
"@langchain/ollama": "^1.1.0",
|
|
41
|
-
"@langchain/openai": "^
|
|
42
|
+
"@langchain/openai": "^1.2.0",
|
|
42
43
|
"@types/inquirer": "^8.2.12",
|
|
43
44
|
"chalk": "^4.1.2",
|
|
44
45
|
"commander": "^11.1.0",
|
|
@@ -46,7 +47,7 @@
|
|
|
46
47
|
"fs-extra": "^11.2.0",
|
|
47
48
|
"graphology": "^0.25.4",
|
|
48
49
|
"inquirer": "^8.2.7",
|
|
49
|
-
"langchain": "^
|
|
50
|
+
"langchain": "^1.2.3",
|
|
50
51
|
"ora": "^5.4.1",
|
|
51
52
|
"simple-git": "^3.30.0",
|
|
52
53
|
"type-fest": "^5.3.1"
|