@aiready/visualizer 0.7.3 → 0.7.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/cli.js +284 -239
- package/dist/cli.js.map +1 -1
- package/dist/graph/index.d.ts +11 -26
- package/dist/graph/index.js +284 -239
- package/dist/graph/index.js.map +1 -1
- package/dist/index.js +284 -239
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/web/dist/assets/index-C8KyAlJg.js.map +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,84 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import { spawn } from 'child_process';
|
|
4
|
-
import
|
|
4
|
+
import path2, { dirname, resolve } from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import fs from 'fs';
|
|
7
|
-
import { generateHTML, UnifiedReportSchema, ToolName,
|
|
7
|
+
import { Severity, generateHTML, UnifiedReportSchema, ToolName, normalizeAnalysisResult } from '@aiready/core';
|
|
8
8
|
|
|
9
|
+
var GRAPH_CONSTANTS = {
|
|
10
|
+
DEFAULT_NODE_SIZE: 1,
|
|
11
|
+
DEFAULT_REFERENCE_SIZE: 5,
|
|
12
|
+
DEFAULT_DEPENDENCY_SIZE: 2,
|
|
13
|
+
DEFAULT_CONTEXT_SIZE: 10,
|
|
14
|
+
FUZZY_MATCH_THRESHOLD: 50,
|
|
15
|
+
FUZZY_MATCH_HIGH_THRESHOLD: 80,
|
|
16
|
+
COLORS: {
|
|
17
|
+
CRITICAL: "#ff4d4f",
|
|
18
|
+
MAJOR: "#ff9900",
|
|
19
|
+
MINOR: "#ffd666",
|
|
20
|
+
INFO: "#91d5ff",
|
|
21
|
+
DEFAULT: "#97c2fc"
|
|
22
|
+
},
|
|
23
|
+
SEVERITY_ORDER: {
|
|
24
|
+
[Severity.Critical]: 3,
|
|
25
|
+
[Severity.Major]: 2,
|
|
26
|
+
[Severity.Minor]: 1,
|
|
27
|
+
[Severity.Info]: 0
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
function normalizeLabel(filePath, rootDir) {
|
|
31
|
+
try {
|
|
32
|
+
return path2.relative(rootDir, filePath);
|
|
33
|
+
} catch {
|
|
34
|
+
return filePath;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function extractReferencedPaths(message) {
|
|
38
|
+
if (!message || typeof message !== "string") return [];
|
|
39
|
+
const reAbs = /\/(?:[\w\-.]+\/)+[\w\-.]+\.(?:ts|tsx|js|jsx|py|java|go)/g;
|
|
40
|
+
const reRel = /(?:\.\/|\.\.\/)(?:[\w\-.]+\/)+[\w\-.]+\.(?:ts|tsx|js|jsx|py|java|go)/g;
|
|
41
|
+
const abs = message.match(reAbs) ?? [];
|
|
42
|
+
const rel = message.match(reRel) ?? [];
|
|
43
|
+
return abs.concat(rel);
|
|
44
|
+
}
|
|
45
|
+
function getPackageGroup(fp) {
|
|
46
|
+
if (!fp) return void 0;
|
|
47
|
+
const parts = fp.split(path2.sep);
|
|
48
|
+
const pkgIdx = parts.indexOf("packages");
|
|
49
|
+
if (pkgIdx >= 0 && parts.length > pkgIdx + 1)
|
|
50
|
+
return `packages/${parts[pkgIdx + 1]}`;
|
|
51
|
+
const landingIdx = parts.indexOf("landing");
|
|
52
|
+
if (landingIdx >= 0) return "landing";
|
|
53
|
+
const scriptsIdx = parts.indexOf("scripts");
|
|
54
|
+
if (scriptsIdx >= 0) return "scripts";
|
|
55
|
+
return parts.length > 1 ? parts[1] : parts[0];
|
|
56
|
+
}
|
|
57
|
+
function rankSeverity(s) {
|
|
58
|
+
if (!s) return null;
|
|
59
|
+
const ss = String(s).toLowerCase();
|
|
60
|
+
if (ss.includes("critical")) return Severity.Critical;
|
|
61
|
+
if (ss.includes("major")) return Severity.Major;
|
|
62
|
+
if (ss.includes("minor")) return Severity.Minor;
|
|
63
|
+
if (ss.includes("info")) return Severity.Info;
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
function getColorForSeverity(sev) {
|
|
67
|
+
switch (sev) {
|
|
68
|
+
case Severity.Critical:
|
|
69
|
+
return GRAPH_CONSTANTS.COLORS.CRITICAL;
|
|
70
|
+
case Severity.Major:
|
|
71
|
+
return GRAPH_CONSTANTS.COLORS.MAJOR;
|
|
72
|
+
case Severity.Minor:
|
|
73
|
+
return GRAPH_CONSTANTS.COLORS.MINOR;
|
|
74
|
+
case Severity.Info:
|
|
75
|
+
return GRAPH_CONSTANTS.COLORS.INFO;
|
|
76
|
+
default:
|
|
77
|
+
return GRAPH_CONSTANTS.COLORS.DEFAULT;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/graph/builder.ts
|
|
9
82
|
var GraphBuilder = class _GraphBuilder {
|
|
10
83
|
constructor(rootDir = process.cwd()) {
|
|
11
84
|
this.rootDir = rootDir;
|
|
@@ -13,97 +86,59 @@ var GraphBuilder = class _GraphBuilder {
|
|
|
13
86
|
this.edges = [];
|
|
14
87
|
this.edgesSet = /* @__PURE__ */ new Set();
|
|
15
88
|
}
|
|
16
|
-
normalizeLabel(filePath) {
|
|
17
|
-
try {
|
|
18
|
-
return path.relative(this.rootDir, filePath);
|
|
19
|
-
} catch {
|
|
20
|
-
return filePath;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
extractReferencedPaths(message) {
|
|
24
|
-
if (!message || typeof message !== "string") return [];
|
|
25
|
-
const reAbs = /\/(?:[\w\-.]+\/)+[\w\-.]+\.(?:ts|tsx|js|jsx|py|java|go)/g;
|
|
26
|
-
const reRel = /(?:\.\/|\.\.\/)(?:[\w\-.]+\/)+[\w\-.]+\.(?:ts|tsx|js|jsx|py|java|go)/g;
|
|
27
|
-
const abs = message.match(reAbs) ?? [];
|
|
28
|
-
const rel = message.match(reRel) ?? [];
|
|
29
|
-
return abs.concat(rel);
|
|
30
|
-
}
|
|
31
|
-
getPackageGroup(fp) {
|
|
32
|
-
if (!fp) return null;
|
|
33
|
-
const parts = fp.split(path.sep);
|
|
34
|
-
const pkgIdx = parts.indexOf("packages");
|
|
35
|
-
if (pkgIdx >= 0 && parts.length > pkgIdx + 1)
|
|
36
|
-
return `packages/${parts[pkgIdx + 1]}`;
|
|
37
|
-
const landingIdx = parts.indexOf("landing");
|
|
38
|
-
if (landingIdx >= 0) return "landing";
|
|
39
|
-
const scriptsIdx = parts.indexOf("scripts");
|
|
40
|
-
if (scriptsIdx >= 0) return "scripts";
|
|
41
|
-
return parts.length > 1 ? parts[1] : parts[0];
|
|
42
|
-
}
|
|
43
89
|
/**
|
|
44
90
|
* Add a new node to the graph or update an existing one.
|
|
45
|
-
*
|
|
46
|
-
* @param file - Unique identifier for the file (node ID).
|
|
47
|
-
* @param title - Optional title or description for the node.
|
|
48
|
-
* @param value - Numerical value representing the node size/weight.
|
|
49
91
|
*/
|
|
50
|
-
addNode(file, title = "",
|
|
92
|
+
addNode(file, title = "", size = GRAPH_CONSTANTS.DEFAULT_NODE_SIZE) {
|
|
51
93
|
if (!file) return;
|
|
52
|
-
const id =
|
|
53
|
-
|
|
94
|
+
const id = path2.resolve(this.rootDir, file);
|
|
95
|
+
const existingNode = this.nodesMap.get(id);
|
|
96
|
+
if (!existingNode) {
|
|
54
97
|
const node = {
|
|
55
98
|
id,
|
|
56
99
|
path: id,
|
|
57
|
-
label:
|
|
100
|
+
label: normalizeLabel(id, this.rootDir),
|
|
58
101
|
title,
|
|
59
|
-
size
|
|
102
|
+
size
|
|
60
103
|
};
|
|
61
104
|
this.nodesMap.set(id, node);
|
|
62
105
|
} else {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
106
|
+
if (title && (!existingNode.title || !existingNode.title.includes(title))) {
|
|
107
|
+
existingNode.title = (existingNode.title ? existingNode.title + "\n" : "") + title;
|
|
108
|
+
}
|
|
109
|
+
if (size > (existingNode.size ?? 0)) {
|
|
110
|
+
existingNode.size = size;
|
|
66
111
|
}
|
|
67
|
-
if (value > (node.size ?? 0)) node.size = value;
|
|
68
112
|
}
|
|
69
113
|
}
|
|
70
114
|
/**
|
|
71
115
|
* Add a directed edge between two nodes in the graph.
|
|
72
|
-
*
|
|
73
|
-
* @param from - Source node ID (file path).
|
|
74
|
-
* @param to - Target node ID (file path).
|
|
75
|
-
* @param type - Type of relationship (e.g., 'dependency', 'reference').
|
|
76
116
|
*/
|
|
77
117
|
addEdge(from, to, type = "link") {
|
|
78
118
|
if (!from || !to) return;
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
82
|
-
const key = `${
|
|
119
|
+
const source = path2.resolve(this.rootDir, from);
|
|
120
|
+
const target = path2.resolve(this.rootDir, to);
|
|
121
|
+
if (source === target) return;
|
|
122
|
+
const key = `${source}->${target}`;
|
|
83
123
|
if (!this.edgesSet.has(key)) {
|
|
84
|
-
this.edges.push({ source
|
|
124
|
+
this.edges.push({ source, target, type });
|
|
85
125
|
this.edgesSet.add(key);
|
|
86
126
|
}
|
|
87
127
|
}
|
|
88
128
|
/**
|
|
89
129
|
* Build the final GraphData object from collected nodes and edges.
|
|
90
|
-
*
|
|
91
|
-
* @returns Consolidated graph data structure.
|
|
92
130
|
*/
|
|
93
131
|
build() {
|
|
94
132
|
const nodes = Array.from(this.nodesMap.values());
|
|
95
|
-
const edges = this.edges.map(
|
|
96
|
-
(e) => ({ source: e.source, target: e.target, type: e.type })
|
|
97
|
-
);
|
|
98
133
|
return {
|
|
99
134
|
nodes,
|
|
100
|
-
edges,
|
|
135
|
+
edges: [...this.edges],
|
|
101
136
|
clusters: [],
|
|
102
137
|
issues: [],
|
|
103
138
|
metadata: {
|
|
104
139
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
105
140
|
totalFiles: nodes.length,
|
|
106
|
-
totalDependencies: edges.length,
|
|
141
|
+
totalDependencies: this.edges.length,
|
|
107
142
|
analysisTypes: [],
|
|
108
143
|
criticalIssues: 0,
|
|
109
144
|
majorIssues: 0,
|
|
@@ -118,10 +153,6 @@ var GraphBuilder = class _GraphBuilder {
|
|
|
118
153
|
}
|
|
119
154
|
/**
|
|
120
155
|
* Static helper to build graph from an AIReady report JSON.
|
|
121
|
-
*
|
|
122
|
-
* @param report - Unified AIReady report object.
|
|
123
|
-
* @param rootDir - Root directory for path resolution.
|
|
124
|
-
* @returns Fully populated GraphData for visualization.
|
|
125
156
|
*/
|
|
126
157
|
static buildFromReport(report, rootDir = process.cwd()) {
|
|
127
158
|
const validation = UnifiedReportSchema.safeParse(report);
|
|
@@ -132,31 +163,18 @@ var GraphBuilder = class _GraphBuilder {
|
|
|
132
163
|
}
|
|
133
164
|
const builder = new _GraphBuilder(rootDir);
|
|
134
165
|
const fileIssues = /* @__PURE__ */ new Map();
|
|
135
|
-
const
|
|
136
|
-
if (!s) return null;
|
|
137
|
-
const ss = String(s).toLowerCase();
|
|
138
|
-
if (ss.includes("critical")) return Severity.Critical;
|
|
139
|
-
if (ss.includes("major")) return Severity.Major;
|
|
140
|
-
if (ss.includes("minor")) return Severity.Minor;
|
|
141
|
-
if (ss.includes("info")) return Severity.Info;
|
|
142
|
-
return null;
|
|
143
|
-
};
|
|
144
|
-
const bumpIssue = (file, sev) => {
|
|
166
|
+
const bumpIssue = (file, severity) => {
|
|
145
167
|
if (!file) return;
|
|
146
|
-
const id =
|
|
147
|
-
if (!fileIssues.has(id))
|
|
168
|
+
const id = path2.resolve(rootDir, file);
|
|
169
|
+
if (!fileIssues.has(id)) {
|
|
148
170
|
fileIssues.set(id, { count: 0, maxSeverity: null, duplicates: 0 });
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
[Severity.Info]: 0
|
|
157
|
-
};
|
|
158
|
-
if (!rec.maxSeverity || order[sev] > order[rec.maxSeverity])
|
|
159
|
-
rec.maxSeverity = sev;
|
|
171
|
+
}
|
|
172
|
+
const record = fileIssues.get(id);
|
|
173
|
+
record.count += 1;
|
|
174
|
+
if (severity) {
|
|
175
|
+
if (!record.maxSeverity || GRAPH_CONSTANTS.SEVERITY_ORDER[severity] > GRAPH_CONSTANTS.SEVERITY_ORDER[record.maxSeverity]) {
|
|
176
|
+
record.maxSeverity = severity;
|
|
177
|
+
}
|
|
160
178
|
}
|
|
161
179
|
};
|
|
162
180
|
const getResults = (toolKey, legacyKey) => {
|
|
@@ -166,120 +184,183 @@ var GraphBuilder = class _GraphBuilder {
|
|
|
166
184
|
if (Array.isArray(toolData)) return toolData;
|
|
167
185
|
return toolData.results ?? toolData.issues ?? [];
|
|
168
186
|
};
|
|
187
|
+
this.processPatterns(
|
|
188
|
+
builder,
|
|
189
|
+
getResults(ToolName.PatternDetect, "patterns"),
|
|
190
|
+
rootDir,
|
|
191
|
+
bumpIssue
|
|
192
|
+
);
|
|
193
|
+
this.processDuplicates(builder, report, rootDir, fileIssues);
|
|
194
|
+
this.processContext(
|
|
195
|
+
builder,
|
|
196
|
+
getResults(ToolName.ContextAnalyzer, "context"),
|
|
197
|
+
rootDir,
|
|
198
|
+
bumpIssue
|
|
199
|
+
);
|
|
200
|
+
this.processToolResults(
|
|
201
|
+
builder,
|
|
202
|
+
ToolName.DocDrift,
|
|
203
|
+
"docDrift",
|
|
204
|
+
report,
|
|
205
|
+
bumpIssue,
|
|
206
|
+
"Doc-Drift Issue"
|
|
207
|
+
);
|
|
208
|
+
this.processToolResults(
|
|
209
|
+
builder,
|
|
210
|
+
ToolName.DependencyHealth,
|
|
211
|
+
"dependencyHealth",
|
|
212
|
+
report,
|
|
213
|
+
bumpIssue,
|
|
214
|
+
"Dependency Issue"
|
|
215
|
+
);
|
|
216
|
+
this.processToolResults(
|
|
217
|
+
builder,
|
|
218
|
+
ToolName.ContractEnforcement,
|
|
219
|
+
"contractEnforcement",
|
|
220
|
+
report,
|
|
221
|
+
bumpIssue,
|
|
222
|
+
"Contract Gap"
|
|
223
|
+
);
|
|
224
|
+
return this.finalizeGraph(builder, fileIssues, report);
|
|
225
|
+
}
|
|
226
|
+
static processPatterns(builder, results, rootDir, bumpIssue) {
|
|
169
227
|
const basenameMap = /* @__PURE__ */ new Map();
|
|
170
|
-
|
|
171
|
-
patternResults.forEach((p) => {
|
|
228
|
+
results.forEach((p) => {
|
|
172
229
|
const fileName = p.fileName ?? p.file;
|
|
173
230
|
if (fileName) {
|
|
174
|
-
const base =
|
|
231
|
+
const base = path2.basename(fileName);
|
|
175
232
|
if (!basenameMap.has(base)) basenameMap.set(base, /* @__PURE__ */ new Set());
|
|
176
233
|
basenameMap.get(base).add(fileName);
|
|
177
234
|
}
|
|
178
235
|
});
|
|
179
|
-
|
|
180
|
-
const
|
|
236
|
+
results.forEach((entry) => {
|
|
237
|
+
const normalized = normalizeAnalysisResult(entry);
|
|
238
|
+
const file = normalized.fileName;
|
|
181
239
|
if (!file) return;
|
|
182
240
|
builder.addNode(
|
|
183
241
|
file,
|
|
184
|
-
`Issues: ${
|
|
185
|
-
|
|
242
|
+
`Issues: ${normalized.issues.length}`,
|
|
243
|
+
normalized.metrics.tokenCost || GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE
|
|
186
244
|
);
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
245
|
+
const rawIssues = Array.isArray(entry.issues) ? entry.issues : [];
|
|
246
|
+
if (rawIssues.length > 0) {
|
|
247
|
+
rawIssues.forEach((issue) => {
|
|
248
|
+
bumpIssue(file, rankSeverity(issue.severity));
|
|
249
|
+
});
|
|
250
|
+
} else {
|
|
251
|
+
normalized.issues.forEach((issue) => {
|
|
252
|
+
bumpIssue(file, issue.severity);
|
|
193
253
|
});
|
|
194
254
|
}
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
const refs = builder.extractReferencedPaths(message);
|
|
255
|
+
normalized.issues.forEach((issue) => {
|
|
256
|
+
const refs = extractReferencedPaths(issue.message);
|
|
198
257
|
refs.forEach((ref) => {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
target
|
|
202
|
-
|
|
203
|
-
|
|
258
|
+
const target = path2.isAbsolute(ref) ? ref : path2.resolve(path2.dirname(file), ref);
|
|
259
|
+
builder.addNode(
|
|
260
|
+
target,
|
|
261
|
+
"Referenced file",
|
|
262
|
+
GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE
|
|
263
|
+
);
|
|
204
264
|
builder.addEdge(file, target, "reference");
|
|
205
265
|
});
|
|
206
|
-
const percMatch = (message.match(/(\d+)%/) || [])[1];
|
|
266
|
+
const percMatch = (issue.message.match(/(\d+)%/) || [])[1];
|
|
207
267
|
const perc = percMatch ? parseInt(percMatch, 10) : null;
|
|
208
|
-
const wantFuzzy = issue.type === "duplicate-pattern" || /similar/i.test(message) || perc && perc >=
|
|
268
|
+
const wantFuzzy = issue.type === "duplicate-pattern" || /similar/i.test(issue.message) || perc !== null && perc >= GRAPH_CONSTANTS.FUZZY_MATCH_THRESHOLD;
|
|
209
269
|
if (wantFuzzy) {
|
|
210
|
-
const fileGroup =
|
|
270
|
+
const fileGroup = getPackageGroup(file);
|
|
211
271
|
for (const [base, pathsSet] of basenameMap.entries()) {
|
|
212
|
-
if (!message.includes(base) || base ===
|
|
272
|
+
if (!issue.message.includes(base) || base === path2.basename(file))
|
|
213
273
|
continue;
|
|
214
274
|
for (const target of pathsSet) {
|
|
215
|
-
const targetGroup =
|
|
216
|
-
if (fileGroup !== targetGroup && !(perc && perc >=
|
|
217
|
-
|
|
275
|
+
const targetGroup = getPackageGroup(target);
|
|
276
|
+
if (fileGroup !== targetGroup && !(perc !== null && perc >= GRAPH_CONSTANTS.FUZZY_MATCH_HIGH_THRESHOLD))
|
|
277
|
+
continue;
|
|
278
|
+
builder.addNode(
|
|
279
|
+
target,
|
|
280
|
+
"Fuzzy match",
|
|
281
|
+
GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE
|
|
282
|
+
);
|
|
218
283
|
builder.addEdge(file, target, "similarity");
|
|
219
284
|
}
|
|
220
285
|
}
|
|
221
286
|
}
|
|
222
287
|
});
|
|
223
288
|
});
|
|
289
|
+
}
|
|
290
|
+
static processDuplicates(builder, report, rootDir, fileIssues) {
|
|
224
291
|
const patternData = report[ToolName.PatternDetect] || report.patternDetect || report.patterns || {};
|
|
225
292
|
const duplicates = (Array.isArray(patternData.duplicates) ? patternData.duplicates : null) || (patternData.summary && Array.isArray(patternData.summary.duplicates) ? patternData.summary.duplicates : null) || (Array.isArray(report.duplicates) ? report.duplicates : []);
|
|
226
293
|
duplicates.forEach((dup) => {
|
|
227
|
-
builder.addNode(
|
|
228
|
-
|
|
294
|
+
builder.addNode(
|
|
295
|
+
dup.file1,
|
|
296
|
+
"Similarity target",
|
|
297
|
+
GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE
|
|
298
|
+
);
|
|
299
|
+
builder.addNode(
|
|
300
|
+
dup.file2,
|
|
301
|
+
"Similarity target",
|
|
302
|
+
GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE
|
|
303
|
+
);
|
|
229
304
|
builder.addEdge(dup.file1, dup.file2, "similarity");
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
fileIssues.
|
|
236
|
-
|
|
237
|
-
fileIssues.get(f2).duplicates += 1;
|
|
305
|
+
[dup.file1, dup.file2].forEach((file) => {
|
|
306
|
+
const id = path2.resolve(rootDir, file);
|
|
307
|
+
if (!fileIssues.has(id)) {
|
|
308
|
+
fileIssues.set(id, { count: 0, maxSeverity: null, duplicates: 0 });
|
|
309
|
+
}
|
|
310
|
+
fileIssues.get(id).duplicates += 1;
|
|
311
|
+
});
|
|
238
312
|
});
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
313
|
+
}
|
|
314
|
+
static processContext(builder, results, rootDir, bumpIssue) {
|
|
315
|
+
results.forEach((ctx) => {
|
|
316
|
+
const normalized = normalizeAnalysisResult(ctx);
|
|
317
|
+
const file = normalized.fileName;
|
|
242
318
|
if (!file) return;
|
|
243
|
-
builder.addNode(
|
|
244
|
-
|
|
245
|
-
ctx.
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
319
|
+
builder.addNode(
|
|
320
|
+
file,
|
|
321
|
+
`Deps: ${ctx.dependencyCount || 0}`,
|
|
322
|
+
GRAPH_CONSTANTS.DEFAULT_CONTEXT_SIZE
|
|
323
|
+
);
|
|
324
|
+
normalized.issues.forEach((issue) => {
|
|
325
|
+
bumpIssue(file, issue.severity);
|
|
326
|
+
});
|
|
252
327
|
(ctx.relatedFiles ?? []).forEach((rel) => {
|
|
253
|
-
const resolvedRel =
|
|
254
|
-
const
|
|
255
|
-
const
|
|
256
|
-
|
|
328
|
+
const resolvedRel = path2.isAbsolute(rel) ? rel : path2.resolve(path2.dirname(file), rel);
|
|
329
|
+
const resolvedFile = path2.resolve(builder.rootDir, file);
|
|
330
|
+
const resolvedTarget = path2.resolve(builder.rootDir, resolvedRel);
|
|
331
|
+
const keyA = `${resolvedFile}->${resolvedTarget}`;
|
|
332
|
+
const keyB = `${resolvedTarget}->${resolvedFile}`;
|
|
333
|
+
if (builder["edgesSet"].has(keyA) || builder["edgesSet"].has(keyB))
|
|
257
334
|
return;
|
|
258
|
-
builder.addNode(
|
|
259
|
-
|
|
260
|
-
|
|
335
|
+
builder.addNode(
|
|
336
|
+
resolvedRel,
|
|
337
|
+
"Related file",
|
|
338
|
+
GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE
|
|
261
339
|
);
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
} catch {
|
|
340
|
+
const node = builder["nodesMap"].get(resolvedTarget);
|
|
341
|
+
if (node) {
|
|
342
|
+
node.size = (node.size || 1) + 2;
|
|
266
343
|
}
|
|
344
|
+
builder.addEdge(file, resolvedRel, "related");
|
|
267
345
|
});
|
|
268
|
-
const
|
|
269
|
-
const fileDir = path.dirname(absoluteFile);
|
|
346
|
+
const fileDir = path2.dirname(path2.resolve(builder.rootDir, file));
|
|
270
347
|
(ctx.dependencyList ?? []).forEach((dep) => {
|
|
271
348
|
if (dep.startsWith(".") || dep.startsWith("/")) {
|
|
272
349
|
const possiblePaths = [
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
350
|
+
path2.resolve(fileDir, dep),
|
|
351
|
+
path2.resolve(fileDir, dep + ".ts"),
|
|
352
|
+
path2.resolve(fileDir, dep + ".tsx"),
|
|
353
|
+
path2.resolve(fileDir, dep + ".js"),
|
|
354
|
+
path2.resolve(fileDir, dep, "index.ts"),
|
|
355
|
+
path2.resolve(fileDir, dep, "index.tsx")
|
|
279
356
|
];
|
|
280
357
|
for (const p of possiblePaths) {
|
|
281
358
|
if (fs.existsSync(p)) {
|
|
282
|
-
builder.addNode(
|
|
359
|
+
builder.addNode(
|
|
360
|
+
p,
|
|
361
|
+
"Dependency",
|
|
362
|
+
GRAPH_CONSTANTS.DEFAULT_DEPENDENCY_SIZE
|
|
363
|
+
);
|
|
283
364
|
builder.addEdge(file, p, "dependency");
|
|
284
365
|
break;
|
|
285
366
|
}
|
|
@@ -287,100 +368,64 @@ var GraphBuilder = class _GraphBuilder {
|
|
|
287
368
|
}
|
|
288
369
|
});
|
|
289
370
|
});
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
const file = issue.fileName ?? issue.location?.file;
|
|
305
|
-
if (file) {
|
|
306
|
-
builder.addNode(file, "Dependency Issue", 5);
|
|
307
|
-
const sev = rankSeverity(issue.severity ?? null);
|
|
308
|
-
bumpIssue(file, sev);
|
|
371
|
+
}
|
|
372
|
+
static processToolResults(builder, toolName, legacyKey, report, bumpIssue, title) {
|
|
373
|
+
const camelKey = toolName.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
374
|
+
const toolData = report[toolName] ?? report[camelKey] ?? report[legacyKey];
|
|
375
|
+
if (!toolData) return;
|
|
376
|
+
const results = Array.isArray(toolData) ? toolData : toolData.results ?? toolData.issues ?? [];
|
|
377
|
+
results.forEach((item) => {
|
|
378
|
+
if (!Array.isArray(item.issues) && (item.severity || item.message)) {
|
|
379
|
+
const file2 = item.fileName ?? item.file ?? item.location?.file;
|
|
380
|
+
if (file2) {
|
|
381
|
+
builder.addNode(file2, title, GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE);
|
|
382
|
+
bumpIssue(file2, rankSeverity(item.severity));
|
|
383
|
+
}
|
|
384
|
+
return;
|
|
309
385
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
ToolName.ContractEnforcement,
|
|
313
|
-
"contractEnforcement"
|
|
314
|
-
);
|
|
315
|
-
contractEnforcementResults.forEach((issue) => {
|
|
316
|
-
const file = issue.fileName ?? issue.location?.file;
|
|
386
|
+
const normalized = normalizeAnalysisResult(item);
|
|
387
|
+
const file = normalized.fileName;
|
|
317
388
|
if (file) {
|
|
318
|
-
builder.addNode(file,
|
|
319
|
-
|
|
320
|
-
|
|
389
|
+
builder.addNode(file, title, GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE);
|
|
390
|
+
normalized.issues.forEach((issue) => {
|
|
391
|
+
bumpIssue(file, issue.severity);
|
|
392
|
+
});
|
|
321
393
|
}
|
|
322
394
|
});
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const
|
|
326
|
-
switch (sev) {
|
|
327
|
-
case Severity.Critical:
|
|
328
|
-
return "#ff4d4f";
|
|
329
|
-
// red
|
|
330
|
-
case Severity.Major:
|
|
331
|
-
return "#ff9900";
|
|
332
|
-
// orange
|
|
333
|
-
case Severity.Minor:
|
|
334
|
-
return "#ffd666";
|
|
335
|
-
// yellow
|
|
336
|
-
case Severity.Info:
|
|
337
|
-
return "#91d5ff";
|
|
338
|
-
// light blue
|
|
339
|
-
default:
|
|
340
|
-
return "#97c2fc";
|
|
341
|
-
}
|
|
342
|
-
};
|
|
395
|
+
}
|
|
396
|
+
static finalizeGraph(builder, fileIssues, report) {
|
|
397
|
+
const graph = builder.build();
|
|
343
398
|
let criticalIssues = 0;
|
|
344
399
|
let majorIssues = 0;
|
|
345
400
|
let minorIssues = 0;
|
|
346
401
|
let infoIssues = 0;
|
|
347
|
-
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
else if (
|
|
356
|
-
|
|
357
|
-
else if (
|
|
402
|
+
graph.nodes.forEach((node) => {
|
|
403
|
+
const record = fileIssues.get(node.id);
|
|
404
|
+
if (record) {
|
|
405
|
+
node.duplicates = record.duplicates || 0;
|
|
406
|
+
node.color = getColorForSeverity(record.maxSeverity);
|
|
407
|
+
node.group = getPackageGroup(node.id);
|
|
408
|
+
if (record.maxSeverity === Severity.Critical)
|
|
409
|
+
criticalIssues += record.count;
|
|
410
|
+
else if (record.maxSeverity === Severity.Major)
|
|
411
|
+
majorIssues += record.count;
|
|
412
|
+
else if (record.maxSeverity === Severity.Minor)
|
|
413
|
+
minorIssues += record.count;
|
|
414
|
+
else if (record.maxSeverity === Severity.Info)
|
|
415
|
+
infoIssues += record.count;
|
|
358
416
|
} else {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
const graph = {
|
|
365
|
-
nodes,
|
|
366
|
-
edges,
|
|
367
|
-
clusters: [],
|
|
368
|
-
issues: [],
|
|
369
|
-
metadata: {
|
|
370
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
371
|
-
totalFiles: nodes.length,
|
|
372
|
-
totalDependencies: edges.length,
|
|
373
|
-
analysisTypes: [],
|
|
374
|
-
criticalIssues,
|
|
375
|
-
majorIssues,
|
|
376
|
-
minorIssues,
|
|
377
|
-
infoIssues,
|
|
378
|
-
tokenBudget: report.scoring?.tokenBudget
|
|
379
|
-
},
|
|
380
|
-
truncated: {
|
|
381
|
-
nodes: false,
|
|
382
|
-
edges: false
|
|
417
|
+
node.color = getColorForSeverity(null);
|
|
418
|
+
node.group = getPackageGroup(node.id);
|
|
419
|
+
node.duplicates = 0;
|
|
383
420
|
}
|
|
421
|
+
});
|
|
422
|
+
graph.metadata = {
|
|
423
|
+
...graph.metadata,
|
|
424
|
+
criticalIssues,
|
|
425
|
+
majorIssues,
|
|
426
|
+
minorIssues,
|
|
427
|
+
infoIssues,
|
|
428
|
+
tokenBudget: report.scoring?.tokenBudget
|
|
384
429
|
};
|
|
385
430
|
return graph;
|
|
386
431
|
}
|