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