impact-analysis 1.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 +21 -0
- package/index.mjs +457 -0
- package/package.json +23 -0
package/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Impact Analysis Tool
|
|
2
|
+
|
|
3
|
+
**Impact Analysis** is a tool that analyzes file dependencies in a code repository. It identifies the impact of code changes by generating a dependency tree and presenting the results in both tabular and graphical formats.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Impact Analysis:** Identifies dependent files for each changed file.
|
|
10
|
+
- **Interactive Graph View:** Visualizes dependencies with an interactive graph.
|
|
11
|
+
- **Table View:** Displays results in a clear tabular format.
|
|
12
|
+
- **User-Friendly UI:** Toggle between Graph View and Table View.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
Install globally using npm:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g impact-analysis
|
package/index.mjs
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import simpleGit from 'simple-git';
|
|
5
|
+
|
|
6
|
+
const git = simpleGit();
|
|
7
|
+
|
|
8
|
+
function readFile(filePath) {
|
|
9
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function extractDependencies(content, filePath, baseDir) {
|
|
13
|
+
const dependencies = [];
|
|
14
|
+
const importRegex = /import\s+.*\s+from\s+['"]([^'"]+)['"]/g;
|
|
15
|
+
|
|
16
|
+
let match;
|
|
17
|
+
while ((match = importRegex.exec(content))) {
|
|
18
|
+
const importPath = match[1];
|
|
19
|
+
const resolvedPath = resolveImportPath(importPath, filePath, baseDir);
|
|
20
|
+
if (resolvedPath) {
|
|
21
|
+
dependencies.push(resolvedPath);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return dependencies;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function resolveImportPath(importPath, filePath, baseDir) {
|
|
29
|
+
if (importPath.startsWith('@/')) {
|
|
30
|
+
return path.resolve(baseDir, importPath.substring(2));
|
|
31
|
+
}
|
|
32
|
+
if (importPath.startsWith('./') || importPath.startsWith('../')) {
|
|
33
|
+
return path.resolve(path.dirname(filePath), importPath);
|
|
34
|
+
}
|
|
35
|
+
const absolutePath = path.resolve(baseDir, importPath);
|
|
36
|
+
if (fs.existsSync(absolutePath)) {
|
|
37
|
+
return absolutePath;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getAllFiles(dirPath, arrayOfFiles = []) {
|
|
43
|
+
const files = fs.readdirSync(dirPath);
|
|
44
|
+
|
|
45
|
+
files.forEach(file => {
|
|
46
|
+
const fullPath = path.join(dirPath, file);
|
|
47
|
+
if (fs.statSync(fullPath).isDirectory()) {
|
|
48
|
+
getAllFiles(fullPath, arrayOfFiles);
|
|
49
|
+
} else if (/\.(js|ts|vue)$/.test(file)) {
|
|
50
|
+
arrayOfFiles.push(fullPath);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return arrayOfFiles;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function buildDependencyMap(baseDir) {
|
|
58
|
+
const dependencyMap = {};
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const allFiles = getAllFiles(baseDir);
|
|
62
|
+
|
|
63
|
+
for (const file of allFiles) {
|
|
64
|
+
const code = readFile(file);
|
|
65
|
+
const dependencies = extractDependencies(code, file, baseDir);
|
|
66
|
+
dependencyMap[file] = dependencies;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return dependencyMap;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('Error building dependency map:', error);
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function generateImpactList(baseDir) {
|
|
77
|
+
const impactMap = {};
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const dependencyMap = await buildDependencyMap(baseDir);
|
|
81
|
+
|
|
82
|
+
const status = await git.status();
|
|
83
|
+
const changedFiles = status.modified
|
|
84
|
+
.map(file => path.resolve(baseDir, file))
|
|
85
|
+
.filter(file => /\.(js|ts|vue)$/.test(file));
|
|
86
|
+
|
|
87
|
+
const reverseDependencyMap = {};
|
|
88
|
+
Object.keys(dependencyMap).forEach(file => {
|
|
89
|
+
dependencyMap[file].forEach(importPath => {
|
|
90
|
+
if (!reverseDependencyMap[importPath]) {
|
|
91
|
+
reverseDependencyMap[importPath] = new Set();
|
|
92
|
+
}
|
|
93
|
+
reverseDependencyMap[importPath].add(file);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
changedFiles.forEach(changedFile => {
|
|
97
|
+
impactMap[changedFile] = [];
|
|
98
|
+
for (const [file, dependencies] of Object.entries(dependencyMap)) {
|
|
99
|
+
if (dependencies.includes(changedFile)) {
|
|
100
|
+
impactMap[changedFile].push(file);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
const createImpactJson = (changedFiles, dependencyMap) => {
|
|
105
|
+
const result = {};
|
|
106
|
+
|
|
107
|
+
const getLastPart = (path) => path.split('\\').pop().split('/').pop().replace(/\.[^/.]+$/, "");
|
|
108
|
+
|
|
109
|
+
changedFiles.forEach(changedFile => {
|
|
110
|
+
const changedFileName = getLastPart(changedFile);
|
|
111
|
+
result[changedFile] = [];
|
|
112
|
+
|
|
113
|
+
for (const [file, dependencies] of Object.entries(dependencyMap)) {
|
|
114
|
+
if (dependencies.some(dep => getLastPart(dep) === changedFileName)) {
|
|
115
|
+
if (!result[changedFile].includes(file)) {
|
|
116
|
+
result[changedFile].push(file);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return Object.entries(result).map(([changedFile, impactsTo]) => ({
|
|
123
|
+
changedFile,
|
|
124
|
+
impactsTo
|
|
125
|
+
}));
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const impactJson = createImpactJson(changedFiles, dependencyMap);
|
|
129
|
+
return impactJson;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error('Error generating impact list:', error);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const baseDir = process.cwd();
|
|
136
|
+
const dependencyJson = await generateImpactList(baseDir);
|
|
137
|
+
let results = dependencyJson;
|
|
138
|
+
function generateGraphData(dependencyData) {
|
|
139
|
+
const nodes = new Map();
|
|
140
|
+
const links = [];
|
|
141
|
+
|
|
142
|
+
dependencyData.forEach(({ changedFile, impactsTo }) => {
|
|
143
|
+
const changedFileName = path.basename(changedFile);
|
|
144
|
+
if (!nodes.has(changedFile)) {
|
|
145
|
+
nodes.set(changedFile, { id: changedFile, name: changedFileName, isChangedFile: true });
|
|
146
|
+
}
|
|
147
|
+
impactsTo.forEach(impactedFile => {
|
|
148
|
+
const impactedFileName = path.basename(impactedFile);
|
|
149
|
+
if (!nodes.has(impactedFile)) {
|
|
150
|
+
nodes.set(impactedFile, { id: impactedFileName, path: impactedFile, isChangedFile: false });
|
|
151
|
+
}
|
|
152
|
+
links.push({ source: changedFile, target: impactedFileName });
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
nodes: Array.from(nodes.values()),
|
|
158
|
+
links
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const graphData = generateGraphData(results);
|
|
163
|
+
|
|
164
|
+
const htmlContent = `
|
|
165
|
+
<!DOCTYPE html>
|
|
166
|
+
<html lang="en">
|
|
167
|
+
<head>
|
|
168
|
+
<meta charset="UTF-8">
|
|
169
|
+
<title>Impact Analysis</title>
|
|
170
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"></script>
|
|
171
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
172
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
173
|
+
<link href="https://fonts.googleapis.com/css2?family=Exo+2:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
|
|
174
|
+
<style>
|
|
175
|
+
body { font-family: "Exo 2", serif; background-color: #282828; color: #fff; overflow-x: hidden;
|
|
176
|
+
font-optical-sizing: auto; margin: 0; padding: 0; text-align: center; font-weight: 400;}
|
|
177
|
+
svg { width: 100%; height: 100vh; }
|
|
178
|
+
circle { cursor: pointer; }
|
|
179
|
+
text { font-size: 12px; fill: #fff; }
|
|
180
|
+
line { stroke: #fefefe; stroke-opacity: 0.6; }
|
|
181
|
+
#tooltip { position: absolute; background: #fff;color: #000; padding: 8px; border: 1px solid #ddd; display: none; }
|
|
182
|
+
.svg-wrapper{ padding: 10px; }
|
|
183
|
+
.impact-file-list {
|
|
184
|
+
display: list-item;
|
|
185
|
+
list-style: number;
|
|
186
|
+
text-align: left;
|
|
187
|
+
padding-left: 7px;
|
|
188
|
+
}
|
|
189
|
+
.impact-analysis-table {
|
|
190
|
+
width: 100%; border-collapse: collapse;
|
|
191
|
+
}
|
|
192
|
+
.impact-analysis-table td {
|
|
193
|
+
padding: 10px 20px;
|
|
194
|
+
}
|
|
195
|
+
th {
|
|
196
|
+
padding: 10px;
|
|
197
|
+
}
|
|
198
|
+
td:first-child {
|
|
199
|
+
background: #91fd9117;
|
|
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;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.btn-active {
|
|
220
|
+
background: #1f4afe;
|
|
221
|
+
background: linear-gradient(90deg,#1f4afe 35%,#167cfc);
|
|
222
|
+
color: #fff
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.btn-active:hover svg path {
|
|
226
|
+
fill: #1f4afe;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.btn-theme:hover {
|
|
230
|
+
background: #fff;
|
|
231
|
+
color: #1f4afe;
|
|
232
|
+
border: 1px solid #1F4AFE
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.btn-svg {
|
|
236
|
+
width: 20px;
|
|
237
|
+
height: 20px;
|
|
238
|
+
margin-right: 10px;
|
|
239
|
+
}
|
|
240
|
+
|
|
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
|
+
}
|
|
254
|
+
|
|
255
|
+
</style>
|
|
256
|
+
</head>
|
|
257
|
+
<body>
|
|
258
|
+
<h1>Impact Analysis</h1>
|
|
259
|
+
<div id="tooltip"></div>
|
|
260
|
+
<div style="text-align: center; margin-bottom: 10px;">
|
|
261
|
+
<button id="graphViewBtn" class="btn-theme btn-active"><svg class="btn-svg" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 256 256" enable-background="new 0 0 256 256" xml:space="preserve">
|
|
262
|
+
<g><g><path fill="#000000" d="M224.5,7.9c-11.8,0-21.5,9.6-21.5,21.5c0,7.4,3.8,14,9.6,17.9l-40.5,106.9c-1.1-0.2-2.3-0.3-3.4-0.3c-5.2,0-9.8,1.9-13.5,4.9l-40.6-33.5c1.6-3,2.6-6.4,2.6-10c0-11.8-9.6-21.5-21.5-21.5c-11.8,0-21.5,9.6-21.5,21.5c0,6.4,2.9,12.1,7.3,16l-43.3,75.3c-2.2-0.7-4.5-1.3-6.9-1.3c-11.8,0-21.5,9.6-21.5,21.5c0,11.8,9.6,21.5,21.5,21.5c11.8,0,21.5-9.6,21.5-21.5c0-6.3-2.8-11.9-7.1-15.8l43.3-75.4c2.1,0.7,4.3,1.2,6.7,1.2c5.1,0,9.7-1.9,13.4-4.8l40.6,33.6c-1.6,3-2.5,6.3-2.5,9.9c0,11.8,9.6,21.5,21.5,21.5c11.8,0,21.5-9.6,21.5-21.5c0-7.6-4-14.3-10-18.1l40.5-106.7c1.3,0.2,2.6,0.4,3.9,0.4c11.8,0,21.5-9.6,21.5-21.5C246,17.5,236.4,7.9,224.5,7.9L224.5,7.9z M31.5,239.6c-7.1,0-12.9-5.8-12.9-12.9c0-7.1,5.8-12.9,12.9-12.9c7.1,0,12.9,5.8,12.9,12.9C44.3,233.8,38.6,239.6,31.5,239.6z M95.7,128c-7.1,0-12.9-5.8-12.9-12.9c0-7.1,5.8-12.9,12.9-12.9c7.1,0,12.9,5.8,12.9,12.9C108.6,122.3,102.8,128,95.7,128z M168.7,188.1c-7.1,0-12.9-5.8-12.9-12.9c0-7.1,5.8-12.9,12.9-12.9c7.1,0,12.9,5.8,12.9,12.9C181.6,182.3,175.8,188.1,168.7,188.1L168.7,188.1z M224.5,42.2c-7.1,0-12.9-5.8-12.9-12.9c0-7.1,5.8-12.9,12.9-12.9c7.1,0,12.9,5.8,12.9,12.9C237.4,36.5,231.6,42.2,224.5,42.2L224.5,42.2z"/></g></g>
|
|
263
|
+
</svg> Graph View</button>
|
|
264
|
+
<button id="tableViewBtn" class="btn-theme"><svg class="btn-svg" width="12px" height="12px" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
265
|
+
</defs>
|
|
266
|
+
<g id="64px-Line" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
|
267
|
+
<g id="db-table">
|
|
268
|
+
|
|
269
|
+
</g>
|
|
270
|
+
<path d="M56,12 C56,9.794 54.206,8 52,8 L10,8 C7.794,8 6,9.794 6,12 L6,54 C6,56.206 7.794,58 10,58 L52,58 C54.206,58 56,56.206 56,54 L56,12 L56,12 Z M30,22 L30,32 L20,32 L20,22 L30,22 L30,22 Z M42,22 L42,32 L32,32 L32,22 L42,22 L42,22 Z M54,22 L54,32 L44,32 L44,22 L54,22 L54,22 Z M18,32 L8,32 L8,22 L18,22 L18,32 L18,32 Z M8,34 L18,34 L18,44 L8,44 L8,34 L8,34 Z M20,34 L30,34 L30,44 L20,44 L20,34 L20,34 Z M30,46 L30,56 L20,56 L20,46 L30,46 L30,46 Z M32,46 L42,46 L42,56 L32,56 L32,46 L32,46 Z M32,44 L32,34 L42,34 L42,44 L32,44 L32,44 Z M44,34 L54,34 L54,44 L44,44 L44,34 L44,34 Z M10,10 L52,10 C53.103,10 54,10.897 54,12 L54,20 L8,20 L8,12 C8,10.897 8.897,10 10,10 L10,10 Z M8,54 L8,46 L18,46 L18,56 L10,56 C8.897,56 8,55.103 8,54 L8,54 Z M52,56 L44,56 L44,46 L54,46 L54,54 C54,55.103 53.103,56 52,56 L52,56 Z" id="Shape" fill="#000000">
|
|
271
|
+
|
|
272
|
+
</path>
|
|
273
|
+
</g>
|
|
274
|
+
</svg> Table View</button>
|
|
275
|
+
</div>
|
|
276
|
+
|
|
277
|
+
<div id="graphView" style="display: block;">
|
|
278
|
+
<svg id="svgGraph" width="1000" height="600"></svg>
|
|
279
|
+
</div>
|
|
280
|
+
|
|
281
|
+
<div id="tableView" style="display: none;padding: 20px;">
|
|
282
|
+
<table class="impact-analysis-table" border="1">
|
|
283
|
+
<thead>
|
|
284
|
+
<tr>
|
|
285
|
+
<th>Changed File</th>
|
|
286
|
+
<th>Impacted Files</th>
|
|
287
|
+
</tr>
|
|
288
|
+
</thead>
|
|
289
|
+
<tbody id="tableBody"></tbody>
|
|
290
|
+
</table>
|
|
291
|
+
</div>
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
<script>
|
|
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
|
+
});
|
|
353
|
+
|
|
354
|
+
// Helper function to keep nodes within boundaries
|
|
355
|
+
function clamp(value, min, max) {
|
|
356
|
+
return Math.max(min, Math.min(max, value));
|
|
357
|
+
}
|
|
358
|
+
|
|
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
|
+
}
|
|
365
|
+
|
|
366
|
+
function dragged(event, d) {
|
|
367
|
+
d.fx = clamp(event.x, 10, width - 10);
|
|
368
|
+
d.fy = clamp(event.y, 10, height - 10);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function dragended(event, d) {
|
|
372
|
+
if (!event.active) simulation.alphaTarget(0);
|
|
373
|
+
d.fx = null;
|
|
374
|
+
d.fy = null;
|
|
375
|
+
}
|
|
376
|
+
|
|
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
|
+
}
|
|
397
|
+
|
|
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
|
+
}
|
|
418
|
+
|
|
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
|
|
424
|
+
|
|
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
|
+
}
|
|
430
|
+
|
|
431
|
+
// Populate table
|
|
432
|
+
tableData.forEach(function (result) {
|
|
433
|
+
var row = document.createElement("tr");
|
|
434
|
+
|
|
435
|
+
var changedFileCell = document.createElement("td");
|
|
436
|
+
changedFileCell.textContent = result.changedFile;
|
|
437
|
+
|
|
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("");
|
|
442
|
+
|
|
443
|
+
row.appendChild(changedFileCell);
|
|
444
|
+
row.appendChild(impactsToCell);
|
|
445
|
+
tableBody.appendChild(row);
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
</script>
|
|
451
|
+
</body>
|
|
452
|
+
</html>
|
|
453
|
+
`;
|
|
454
|
+
|
|
455
|
+
// Write HTML file
|
|
456
|
+
fs.writeFileSync('impact-analysis.html', htmlContent);
|
|
457
|
+
console.log('✅ Dependency graph generated: impact-analysis.html');
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "impact-analysis",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Tool to analyze code impact based on changed files and dependencies.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"impact-analysis": "./index.mjs"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"impact",
|
|
14
|
+
"analysis",
|
|
15
|
+
"dependency",
|
|
16
|
+
"npm"
|
|
17
|
+
],
|
|
18
|
+
"author": "Sushant Keshav Thorat",
|
|
19
|
+
"license": "ISC",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"simple-git": "^3.27.0"
|
|
22
|
+
}
|
|
23
|
+
}
|