impact-analysis 1.0.5 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -11
- package/git/core/analyzer.ts +28 -0
- package/git/getChangedFiles.ts +15 -0
- package/index.mjs +180 -184
- package/package.json +23 -15
- package/src/ai/explain.ts +29 -0
- package/src/cache/graphCache.ts +49 -0
- package/src/cli.ts +110 -0
- package/src/graph/buildGraph.ts +151 -0
- package/src/graph/types.ts +5 -0
- package/src/parser/parseJS.ts +69 -0
- package/src/parser/parseVue.ts +23 -0
- package/src/parser/parseVueTemplate.ts +16 -0
- package/src/report/html-generator.ts +343 -0
- package/src/scanner/scanRepo.ts +25 -0
- package/tsconfig.json +10 -0
package/README.md
CHANGED
|
@@ -1,21 +1,126 @@
|
|
|
1
|
-
# Impact Analysis
|
|
1
|
+
# Impact Analysis
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
---
|
|
3
|
+
Analyze the real impact of code changes in JavaScript, TypeScript, React, Vue, and Angular projects.
|
|
6
4
|
|
|
7
5
|
## Features
|
|
8
6
|
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
- **Smart Dependency Analysis**
|
|
8
|
+
- **Visual Reports** - Beautiful HTML reports with interactive dependency graphs
|
|
9
|
+
- **Fast with Caching** - Caches dependency graphs for quick re-analysis
|
|
10
|
+
- **AI Explanations** (Optional) - Get AI-powered insights using Google Gemini
|
|
11
|
+
- **React Support** - Full support for .js, .jsx, .ts, .tsx files
|
|
12
|
+
- **Angular Support** - Supports TypeScript components
|
|
13
|
+
- **Vue Support** - Full support for Vue SFCs including `<script>` and `<script setup>`
|
|
14
|
+
- **Risk Scoring** - Automatic LOW/MEDIUM/HIGH risk classification
|
|
15
15
|
|
|
16
16
|
## Installation
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g impact-analysis
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or use locally in a project:
|
|
23
|
+
```bash
|
|
24
|
+
npm install --save-dev impact-analysis
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
### Basic Analysis
|
|
19
30
|
|
|
20
31
|
```bash
|
|
32
|
+
# Analyze changes compared to main branch
|
|
33
|
+
impact-analysis
|
|
34
|
+
|
|
35
|
+
# Compare against a different branch
|
|
36
|
+
impact-analysis develop
|
|
37
|
+
|
|
38
|
+
# Open HTML report in browser
|
|
39
|
+
impact-analysis --html
|
|
40
|
+
|
|
41
|
+
# Clear cache and rebuild dependency graph
|
|
42
|
+
impact-analysis --clear-cache
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### AI Explanations (If you really want to get an AI powered analysis)
|
|
46
|
+
|
|
47
|
+
To enable AI-powered impact explanations:
|
|
48
|
+
|
|
49
|
+
1. Get a free API key from [Google AI Studio](https://aistudio.google.com/app/apikey)
|
|
50
|
+
2. Create a `.env` file in your project root:
|
|
51
|
+
```bash
|
|
52
|
+
GEMINI_API_KEY=your-api-key-here
|
|
53
|
+
```
|
|
54
|
+
3. Run with `--ai` flag:
|
|
55
|
+
```bash
|
|
56
|
+
impact-analysis --ai
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Note: AI features are completely optional. The tool works perfectly without an API key.
|
|
60
|
+
|
|
61
|
+
## How It Works
|
|
62
|
+
|
|
63
|
+
1. **Scans** your repository for all `.js`, `.ts`, and `.vue` files
|
|
64
|
+
2. **Builds** a dependency graph using AST parsing
|
|
65
|
+
3. **Detects** changed files using Git diff
|
|
66
|
+
4. **Analyzes** which files import the changed files
|
|
67
|
+
5. **Generates** an HTML report with interactive visualization
|
|
68
|
+
|
|
69
|
+
## Supported Import Formats
|
|
70
|
+
|
|
71
|
+
- ES6 imports: `import x from './module'`
|
|
72
|
+
- Dynamic imports: `import('./module')`
|
|
73
|
+
- CommonJS: `require('./module')`
|
|
74
|
+
- Export from: `export { x } from './module'`
|
|
75
|
+
- Export all: `export * from './module'`
|
|
76
|
+
- Path aliases: `@/`, `~/`, `#`, tsconfig paths
|
|
77
|
+
|
|
78
|
+
## Path Alias Support
|
|
79
|
+
|
|
80
|
+
The tool automatically resolves:
|
|
81
|
+
- `@/components/Header` → `components/Header`
|
|
82
|
+
- `~/utils/helper` → `utils/helper`
|
|
83
|
+
- `#components/Footer` → `components/Footer`
|
|
84
|
+
- Custom tsconfig paths
|
|
85
|
+
|
|
86
|
+
## Output
|
|
87
|
+
|
|
88
|
+
### Console Output
|
|
89
|
+
```
|
|
90
|
+
Scanning repository...
|
|
91
|
+
Building dependency graph from 850 files...
|
|
92
|
+
Dependency graph built: 543 files with dependencies
|
|
93
|
+
Getting changed files against main...
|
|
94
|
+
Found 2 changed files. Analyzing impact...
|
|
95
|
+
|
|
96
|
+
--- Impact Analysis Results ---
|
|
97
|
+
|
|
98
|
+
1. GlobalNavNuxt.vue
|
|
99
|
+
Risk: HIGH
|
|
100
|
+
Impacted files: 12
|
|
101
|
+
- Header.vue
|
|
102
|
+
- Layout.vue
|
|
103
|
+
- NavBar.vue
|
|
104
|
+
... and 9 more
|
|
105
|
+
|
|
106
|
+
Generating HTML report...
|
|
107
|
+
Report saved: impact-analysis.html
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### HTML Report
|
|
111
|
+
|
|
112
|
+
Interactive visualization with:
|
|
113
|
+
- **Graph View**: Visual dependency network
|
|
114
|
+
- **Table View**: Detailed file-by-file breakdown
|
|
115
|
+
- Clickable nodes and expandable lists
|
|
116
|
+
|
|
117
|
+
## Requirements
|
|
118
|
+
|
|
119
|
+
- Node.js 18+
|
|
120
|
+
- Git (for change detection)
|
|
121
|
+
- A Git repository
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
MITbash
|
|
21
126
|
npm install -g impact-analysis
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
export interface ImpactResult {
|
|
4
|
+
changedFile: string;
|
|
5
|
+
impactedFiles: string[];
|
|
6
|
+
risk: 'LOW' | 'MEDIUM' | 'HIGH';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function analyzeImpact(
|
|
10
|
+
changedFiles: string[],
|
|
11
|
+
dependencyMap: Record<string, string[]>
|
|
12
|
+
): ImpactResult[] {
|
|
13
|
+
return changedFiles.map(changed => {
|
|
14
|
+
const impacted = Object.entries(dependencyMap)
|
|
15
|
+
.filter(([, deps]) => deps.includes(changed))
|
|
16
|
+
.map(([file]) => file);
|
|
17
|
+
|
|
18
|
+
let risk: ImpactResult['risk'] = 'LOW';
|
|
19
|
+
if (impacted.length > 5) risk = 'HIGH';
|
|
20
|
+
else if (impacted.length > 2) risk = 'MEDIUM';
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
changedFile: changed,
|
|
24
|
+
impactedFiles: impacted,
|
|
25
|
+
risk
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import simpleGit from "simple-git";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export async function getChangedFiles(base: string): Promise<string[]> {
|
|
5
|
+
try {
|
|
6
|
+
const git = simpleGit();
|
|
7
|
+
const diff = await git.diff(["--name-only", base]);
|
|
8
|
+
return diff
|
|
9
|
+
.split("\n")
|
|
10
|
+
.filter(f => /\.(js|jsx|ts|tsx|vue)$/.test(f))
|
|
11
|
+
.map(f => path.resolve(process.cwd(), f));
|
|
12
|
+
} catch (error) {
|
|
13
|
+
throw new Error(`Failed to get changed files. Make sure Git is installed and you're in a Git repository. Base branch: ${base}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
package/index.mjs
CHANGED
|
@@ -149,7 +149,7 @@ function generateGraphData(dependencyData) {
|
|
|
149
149
|
if (!nodes.has(impactedFile)) {
|
|
150
150
|
nodes.set(impactedFile, { id: impactedFileName, path: impactedFile, isChangedFile: false });
|
|
151
151
|
}
|
|
152
|
-
links.push({
|
|
152
|
+
links.push({ source: changedFile, target: impactedFileName });
|
|
153
153
|
});
|
|
154
154
|
});
|
|
155
155
|
|
|
@@ -160,6 +160,7 @@ function generateGraphData(dependencyData) {
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
const graphData = generateGraphData(results);
|
|
163
|
+
|
|
163
164
|
const htmlContent = `
|
|
164
165
|
<!DOCTYPE html>
|
|
165
166
|
<html lang="en">
|
|
@@ -196,23 +197,23 @@ const htmlContent = `
|
|
|
196
197
|
}
|
|
197
198
|
td:first-child {
|
|
198
199
|
background: #91fd9117;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
200
|
+
}
|
|
201
|
+
.btn-theme {
|
|
202
|
+
min-width: 142px;
|
|
203
|
+
padding: 12px 16px;
|
|
204
|
+
font-size: 14px;
|
|
205
|
+
border-radius: 100px;
|
|
206
|
+
border: 1px solid #565454;
|
|
207
|
+
outline: none;
|
|
208
|
+
box-shadow: none;
|
|
209
|
+
cursor: pointer;
|
|
210
|
+
transition: all .3s ease-in-out;
|
|
211
|
+
text-decoration: none;
|
|
212
|
+
display: inline-flex;
|
|
213
|
+
align-items: center;
|
|
214
|
+
font-family: "Exo 2", serif;
|
|
215
|
+
background: #222;
|
|
216
|
+
color: #fff;
|
|
216
217
|
}
|
|
217
218
|
|
|
218
219
|
.btn-active {
|
|
@@ -221,40 +222,36 @@ const htmlContent = `
|
|
|
221
222
|
color: #fff
|
|
222
223
|
}
|
|
223
224
|
|
|
224
|
-
.btn-svg path {
|
|
225
|
-
fill: #fefefe;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
225
|
.btn-active:hover svg path {
|
|
229
226
|
fill: #1f4afe;
|
|
230
227
|
}
|
|
231
228
|
|
|
232
|
-
.btn-theme:hover svg path {
|
|
233
|
-
fill: #1f4afe;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
229
|
.btn-theme:hover {
|
|
237
230
|
background: #fff;
|
|
238
231
|
color: #1f4afe;
|
|
239
232
|
border: 1px solid #1F4AFE
|
|
240
|
-
|
|
233
|
+
}
|
|
241
234
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
235
|
+
.btn-svg {
|
|
236
|
+
width: 20px;
|
|
237
|
+
height: 20px;
|
|
238
|
+
margin-right: 10px;
|
|
239
|
+
}
|
|
247
240
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
241
|
+
.btn-svg path {
|
|
242
|
+
fill: #fefefe;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
th {
|
|
246
|
+
background: #1f4afe;
|
|
247
|
+
background: linear-gradient(90deg,#1f4afe 35%,#167cfc);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
th:last-child {
|
|
251
|
+
background: #1f4afe;
|
|
252
|
+
background: linear-gradient(-90deg,#1f4afe 35%,#167cfc);
|
|
253
|
+
}
|
|
253
254
|
|
|
254
|
-
th:last-child {
|
|
255
|
-
background: #1f4afe;
|
|
256
|
-
background: linear-gradient(-90deg,#1f4afe 35%,#167cfc);
|
|
257
|
-
}
|
|
258
255
|
</style>
|
|
259
256
|
</head>
|
|
260
257
|
<body>
|
|
@@ -295,161 +292,159 @@ const htmlContent = `
|
|
|
295
292
|
|
|
296
293
|
|
|
297
294
|
<script>
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
295
|
+
const graph = ${JSON.stringify(graphData)};
|
|
296
|
+
|
|
297
|
+
const width = window.innerWidth;
|
|
298
|
+
const height = window.innerHeight - 100;
|
|
299
|
+
|
|
300
|
+
const svg = d3.select("#svgGraph")
|
|
301
|
+
.attr("width", width)
|
|
302
|
+
.attr("height", height);
|
|
303
|
+
|
|
304
|
+
const simulation = d3.forceSimulation(graph.nodes)
|
|
305
|
+
.force("link", d3.forceLink(graph.links).id(d => d.id).distance(150)) // Increased distance for better spacing
|
|
306
|
+
.force("charge", d3.forceManyBody().strength(-200)) // Stronger repulsion to avoid overlap
|
|
307
|
+
.force("center", d3.forceCenter(width / 2, height / 2)) // Centering the graph
|
|
308
|
+
.force("collision", d3.forceCollide().radius(20)) // Prevents overlap
|
|
309
|
+
.on("tick", ticked);
|
|
310
|
+
|
|
311
|
+
const link = svg.selectAll("line")
|
|
312
|
+
.data(graph.links)
|
|
313
|
+
.enter().append("line");
|
|
314
|
+
|
|
315
|
+
const node = svg.selectAll("circle")
|
|
316
|
+
.data(graph.nodes)
|
|
317
|
+
.enter().append("circle")
|
|
318
|
+
.attr("r", d => d.isChangedFile ? 18 : 12) // Slightly larger nodes
|
|
319
|
+
.attr("fill", d => d.isChangedFile ? "#ff4500" : "#007bff") // Bright orange for changedFile, blue for others
|
|
320
|
+
.style("stroke", "#fff") // White border for clarity
|
|
321
|
+
.style("stroke-width", 2)
|
|
322
|
+
.call(drag(simulation));
|
|
323
|
+
|
|
324
|
+
const tooltip = d3.select("#tooltip");
|
|
325
|
+
|
|
326
|
+
node.on("mouseover", (event, d) => {
|
|
327
|
+
tooltip.style("display", "block")
|
|
328
|
+
.html(\`<strong>\${d.isChangedFile ? d.id : d.path}</strong>\`)
|
|
329
|
+
.style("left", (event.pageX + 10) + "px")
|
|
330
|
+
.style("top", (event.pageY - 10) + "px");
|
|
331
|
+
}).on("mouseout", () => tooltip.style("display", "none"));
|
|
332
|
+
|
|
333
|
+
svg.selectAll("text")
|
|
334
|
+
.data(graph.nodes)
|
|
335
|
+
.enter().append("text")
|
|
336
|
+
.attr("dy", 4)
|
|
337
|
+
.attr("x", 12)
|
|
338
|
+
.text(d => d.isChangedFile ? d.name : d.id);
|
|
339
|
+
|
|
340
|
+
simulation.on("tick", () => {
|
|
341
|
+
link.attr("x1", d => clamp(d.source.x, 0, width))
|
|
342
|
+
.attr("y1", d => clamp(d.source.y, 0, height))
|
|
343
|
+
.attr("x2", d => clamp(d.target.x, 0, width))
|
|
344
|
+
.attr("y2", d => clamp(d.target.y, 0, height));
|
|
345
|
+
|
|
346
|
+
node.attr("cx", d => clamp(d.x, 10, width - 10))
|
|
347
|
+
.attr("cy", d => clamp(d.y, 10, height - 10));
|
|
348
|
+
|
|
349
|
+
svg.selectAll("text")
|
|
350
|
+
.attr("x", d => clamp(d.x + 20, 10, width - 10))
|
|
351
|
+
.attr("y", d => clamp(d.y + 6, 10, height - 10));
|
|
352
|
+
});
|
|
331
353
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const svg = d3.select("#svgGraph")
|
|
338
|
-
.attr("width", width)
|
|
339
|
-
.attr("height", height);
|
|
340
|
-
|
|
341
|
-
const simulation = d3.forceSimulation(graph.nodes)
|
|
342
|
-
.force("link", d3.forceLink(graph.links)
|
|
343
|
-
.id(d => d.id)
|
|
344
|
-
.distance(80)
|
|
345
|
-
)
|
|
346
|
-
.force("charge", d3.forceManyBody()
|
|
347
|
-
.strength(-50)
|
|
348
|
-
)
|
|
349
|
-
.force("center", d3.forceCenter(width / 2, height / 2))
|
|
350
|
-
.force("collision", d3.forceCollide()
|
|
351
|
-
.radius(35)
|
|
352
|
-
)
|
|
353
|
-
.on("tick", ticked);
|
|
354
|
-
|
|
355
|
-
const link = svg.append("g")
|
|
356
|
-
.selectAll("line")
|
|
357
|
-
.data(graph.links)
|
|
358
|
-
.enter().append("line")
|
|
359
|
-
.attr("stroke", "#aaa");
|
|
360
|
-
|
|
361
|
-
const nodeGroup = svg.append("g")
|
|
362
|
-
.selectAll("g")
|
|
363
|
-
.data(graph.nodes)
|
|
364
|
-
.enter().append("g")
|
|
365
|
-
.call(d3.drag()
|
|
366
|
-
.on("start", dragStarted)
|
|
367
|
-
.on("drag", dragged)
|
|
368
|
-
.on("end", dragEnded));
|
|
369
|
-
|
|
370
|
-
const node = nodeGroup.append("circle")
|
|
371
|
-
.attr("r", 10)
|
|
372
|
-
.attr("fill", d => d.isChangedFile ? "red" : "steelblue");
|
|
373
|
-
|
|
374
|
-
const labels = nodeGroup.append("text")
|
|
375
|
-
.attr("x", 12)
|
|
376
|
-
.attr("y", 5)
|
|
377
|
-
.text(d => d.name);
|
|
378
|
-
|
|
379
|
-
simulation.on("tick", () => {
|
|
380
|
-
link.attr("x1", d => d.source.x)
|
|
381
|
-
.attr("y1", d => d.source.y)
|
|
382
|
-
.attr("x2", d => d.target.x)
|
|
383
|
-
.attr("y2", d => d.target.y);
|
|
384
|
-
|
|
385
|
-
nodeGroup.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
function dragStarted(event, d) {
|
|
389
|
-
if (!event.active) simulation.alphaTarget(0.3).restart();
|
|
390
|
-
d.fx = d.x;
|
|
391
|
-
d.fy = d.y;
|
|
392
|
-
}
|
|
354
|
+
// Helper function to keep nodes within boundaries
|
|
355
|
+
function clamp(value, min, max) {
|
|
356
|
+
return Math.max(min, Math.min(max, value));
|
|
357
|
+
}
|
|
393
358
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
359
|
+
function drag(simulation) {
|
|
360
|
+
function dragstarted(event, d) {
|
|
361
|
+
if (!event.active) simulation.alphaTarget(0.3).restart();
|
|
362
|
+
d.fx = d.x;
|
|
363
|
+
d.fy = d.y;
|
|
364
|
+
}
|
|
398
365
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
}
|
|
366
|
+
function dragged(event, d) {
|
|
367
|
+
d.fx = clamp(event.x, 10, width - 10);
|
|
368
|
+
d.fy = clamp(event.y, 10, height - 10);
|
|
369
|
+
}
|
|
404
370
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
371
|
+
function dragended(event, d) {
|
|
372
|
+
if (!event.active) simulation.alphaTarget(0);
|
|
373
|
+
d.fx = null;
|
|
374
|
+
d.fy = null;
|
|
375
|
+
}
|
|
410
376
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
377
|
+
return d3.drag()
|
|
378
|
+
.on("start", dragstarted)
|
|
379
|
+
.on("drag", dragged)
|
|
380
|
+
.on("end", dragended);
|
|
381
|
+
}
|
|
382
|
+
function ticked() {
|
|
383
|
+
link
|
|
384
|
+
.attr("x1", d => d.source.x)
|
|
385
|
+
.attr("y1", d => d.source.y)
|
|
386
|
+
.attr("x2", d => d.target.x)
|
|
387
|
+
.attr("y2", d => d.target.y);
|
|
388
|
+
|
|
389
|
+
node
|
|
390
|
+
.attr("cx", d => d.x)
|
|
391
|
+
.attr("cy", d => d.y);
|
|
392
|
+
|
|
393
|
+
label
|
|
394
|
+
.attr("x", d => d.x + 10)
|
|
395
|
+
.attr("y", d => d.y + 5);
|
|
396
|
+
}
|
|
417
397
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
398
|
+
document.getElementById("graphViewBtn").addEventListener("click", function () {
|
|
399
|
+
document.getElementById("graphView").style.display = "block";
|
|
400
|
+
document.getElementById("tableView").style.display = "none";
|
|
401
|
+
setActiveButton(this);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
document.getElementById("tableViewBtn").addEventListener("click", function () {
|
|
405
|
+
document.getElementById("graphView").style.display = "none";
|
|
406
|
+
document.getElementById("tableView").style.display = "block";
|
|
407
|
+
setActiveButton(this);
|
|
408
|
+
generateTableView(${JSON.stringify(dependencyJson)});
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// Function to highlight the active button
|
|
412
|
+
function setActiveButton(activeBtn) {
|
|
413
|
+
document.querySelectorAll(".btn-theme").forEach(button => {
|
|
414
|
+
button.classList.remove("btn-active");
|
|
415
|
+
});
|
|
416
|
+
activeBtn.classList.add("btn-active");
|
|
417
|
+
}
|
|
424
418
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
419
|
+
// Function to generate the table
|
|
420
|
+
function generateTableView(data) {
|
|
421
|
+
const tableData = data;
|
|
422
|
+
var tableBody = document.getElementById("tableBody");
|
|
423
|
+
tableBody.innerHTML = ""; // Clear previous content
|
|
428
424
|
|
|
429
|
-
|
|
430
|
-
|
|
425
|
+
// Ensure tableData is an array
|
|
426
|
+
if (!Array.isArray(tableData) || tableData.length === 0) {
|
|
427
|
+
tableBody.innerHTML = '<tr><td colspan="2">No data available</td></tr>';
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
431
430
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
431
|
+
// Populate table
|
|
432
|
+
tableData.forEach(function (result) {
|
|
433
|
+
var row = document.createElement("tr");
|
|
435
434
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
tr.appendChild(tdImpact);
|
|
435
|
+
var changedFileCell = document.createElement("td");
|
|
436
|
+
changedFileCell.textContent = result.changedFile;
|
|
439
437
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
node.attr("cx", d => d.x = Math.max(20, Math.min(width - 20, d.x)))
|
|
445
|
-
.attr("cy", d => d.y = Math.max(20, Math.min(height - 20, d.y)));
|
|
446
|
-
|
|
447
|
-
link.attr("x1", d => d.source.x)
|
|
448
|
-
.attr("y1", d => d.source.y)
|
|
449
|
-
.attr("x2", d => d.target.x)
|
|
450
|
-
.attr("y2", d => d.target.y);
|
|
451
|
-
}
|
|
438
|
+
var impactsToCell = document.createElement("td");
|
|
439
|
+
impactsToCell.innerHTML = result.impactsTo.map(function (file) {
|
|
440
|
+
return '<div class="impact-file-list">' + file + '</div>';
|
|
441
|
+
}).join("");
|
|
452
442
|
|
|
443
|
+
row.appendChild(changedFileCell);
|
|
444
|
+
row.appendChild(impactsToCell);
|
|
445
|
+
tableBody.appendChild(row);
|
|
446
|
+
});
|
|
447
|
+
}
|
|
453
448
|
|
|
454
449
|
|
|
455
450
|
</script>
|
|
@@ -457,5 +452,6 @@ const htmlContent = `
|
|
|
457
452
|
</html>
|
|
458
453
|
`;
|
|
459
454
|
|
|
455
|
+
// Write HTML file
|
|
460
456
|
fs.writeFileSync('impact-analysis.html', htmlContent);
|
|
461
457
|
console.log('✅ Dependency graph generated: impact-analysis.html');
|
package/package.json
CHANGED
|
@@ -1,23 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "impact-analysis",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
-
},
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Analyze real impact of code changes using dependency graphs",
|
|
9
5
|
"bin": {
|
|
10
|
-
"impact-analysis": "
|
|
6
|
+
"impact-analysis": "dist/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "ts-node src/cli.ts"
|
|
11
13
|
},
|
|
12
|
-
"keywords": [
|
|
13
|
-
"impact",
|
|
14
|
-
"analysis",
|
|
15
|
-
"dependency",
|
|
16
|
-
"npm"
|
|
17
|
-
],
|
|
18
|
-
"author": "Sushant Keshav Thorat",
|
|
19
14
|
"license": "MIT",
|
|
20
15
|
"dependencies": {
|
|
21
|
-
"
|
|
16
|
+
"@babel/parser": "^7.24.0",
|
|
17
|
+
"@babel/traverse": "^7.24.0",
|
|
18
|
+
"@google/generative-ai": "^0.24.1",
|
|
19
|
+
"@vue/compiler-sfc": "^3.4.0",
|
|
20
|
+
"commander": "^11.0.0",
|
|
21
|
+
"dotenv": "^17.2.3",
|
|
22
|
+
"fast-glob": "^3.3.2",
|
|
23
|
+
"simple-git": "^3.22.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^20.11.0",
|
|
27
|
+
"parse-json": "^8.3.0",
|
|
28
|
+
"ts-node": "^10.9.2",
|
|
29
|
+
"typescript": "^5.4.0"
|
|
22
30
|
}
|
|
23
31
|
}
|