@aiready/visualizer 0.7.3 → 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/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,121 @@ var GraphBuilder = class _GraphBuilder {
|
|
|
166
184
|
if (Array.isArray(toolData)) return toolData;
|
|
167
185
|
return toolData.results ?? toolData.issues ?? [];
|
|
168
186
|
};
|
|
187
|
+
this.processPatterns(builder, getResults(ToolName.PatternDetect, "patterns"), rootDir, bumpIssue);
|
|
188
|
+
this.processDuplicates(builder, report, rootDir, fileIssues);
|
|
189
|
+
this.processContext(builder, getResults(ToolName.ContextAnalyzer, "context"), rootDir, bumpIssue);
|
|
190
|
+
this.processToolResults(builder, ToolName.DocDrift, "docDrift", report, bumpIssue, "Doc-Drift Issue");
|
|
191
|
+
this.processToolResults(builder, ToolName.DependencyHealth, "dependencyHealth", report, bumpIssue, "Dependency Issue");
|
|
192
|
+
this.processToolResults(builder, ToolName.ContractEnforcement, "contractEnforcement", report, bumpIssue, "Contract Gap");
|
|
193
|
+
return this.finalizeGraph(builder, fileIssues, report);
|
|
194
|
+
}
|
|
195
|
+
static processPatterns(builder, results, rootDir, bumpIssue) {
|
|
169
196
|
const basenameMap = /* @__PURE__ */ new Map();
|
|
170
|
-
|
|
171
|
-
patternResults.forEach((p) => {
|
|
197
|
+
results.forEach((p) => {
|
|
172
198
|
const fileName = p.fileName ?? p.file;
|
|
173
199
|
if (fileName) {
|
|
174
|
-
const base =
|
|
200
|
+
const base = path2.basename(fileName);
|
|
175
201
|
if (!basenameMap.has(base)) basenameMap.set(base, /* @__PURE__ */ new Set());
|
|
176
202
|
basenameMap.get(base).add(fileName);
|
|
177
203
|
}
|
|
178
204
|
});
|
|
179
|
-
|
|
180
|
-
const
|
|
205
|
+
results.forEach((entry) => {
|
|
206
|
+
const normalized = normalizeAnalysisResult(entry);
|
|
207
|
+
const file = normalized.fileName;
|
|
181
208
|
if (!file) return;
|
|
182
209
|
builder.addNode(
|
|
183
210
|
file,
|
|
184
|
-
`Issues: ${
|
|
185
|
-
|
|
211
|
+
`Issues: ${normalized.issues.length}`,
|
|
212
|
+
normalized.metrics.tokenCost || GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE
|
|
186
213
|
);
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
214
|
+
const rawIssues = Array.isArray(entry.issues) ? entry.issues : [];
|
|
215
|
+
if (rawIssues.length > 0) {
|
|
216
|
+
rawIssues.forEach((issue) => {
|
|
217
|
+
bumpIssue(file, rankSeverity(issue.severity));
|
|
218
|
+
});
|
|
219
|
+
} else {
|
|
220
|
+
normalized.issues.forEach((issue) => {
|
|
221
|
+
bumpIssue(file, issue.severity);
|
|
193
222
|
});
|
|
194
223
|
}
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
const refs = builder.extractReferencedPaths(message);
|
|
224
|
+
normalized.issues.forEach((issue) => {
|
|
225
|
+
const refs = extractReferencedPaths(issue.message);
|
|
198
226
|
refs.forEach((ref) => {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
target = path.resolve(path.dirname(file), ref);
|
|
202
|
-
}
|
|
203
|
-
builder.addNode(target, "Referenced file", 5);
|
|
227
|
+
const target = path2.isAbsolute(ref) ? ref : path2.resolve(path2.dirname(file), ref);
|
|
228
|
+
builder.addNode(target, "Referenced file", GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE);
|
|
204
229
|
builder.addEdge(file, target, "reference");
|
|
205
230
|
});
|
|
206
|
-
const percMatch = (message.match(/(\d+)%/) || [])[1];
|
|
231
|
+
const percMatch = (issue.message.match(/(\d+)%/) || [])[1];
|
|
207
232
|
const perc = percMatch ? parseInt(percMatch, 10) : null;
|
|
208
|
-
const wantFuzzy = issue.type === "duplicate-pattern" || /similar/i.test(message) || perc && perc >=
|
|
233
|
+
const wantFuzzy = issue.type === "duplicate-pattern" || /similar/i.test(issue.message) || perc !== null && perc >= GRAPH_CONSTANTS.FUZZY_MATCH_THRESHOLD;
|
|
209
234
|
if (wantFuzzy) {
|
|
210
|
-
const fileGroup =
|
|
235
|
+
const fileGroup = getPackageGroup(file);
|
|
211
236
|
for (const [base, pathsSet] of basenameMap.entries()) {
|
|
212
|
-
if (!message.includes(base) || base ===
|
|
213
|
-
continue;
|
|
237
|
+
if (!issue.message.includes(base) || base === path2.basename(file)) continue;
|
|
214
238
|
for (const target of pathsSet) {
|
|
215
|
-
const targetGroup =
|
|
216
|
-
if (fileGroup !== targetGroup && !(perc && perc >=
|
|
217
|
-
builder.addNode(target, "Fuzzy match",
|
|
239
|
+
const targetGroup = getPackageGroup(target);
|
|
240
|
+
if (fileGroup !== targetGroup && !(perc !== null && perc >= GRAPH_CONSTANTS.FUZZY_MATCH_HIGH_THRESHOLD)) continue;
|
|
241
|
+
builder.addNode(target, "Fuzzy match", GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE);
|
|
218
242
|
builder.addEdge(file, target, "similarity");
|
|
219
243
|
}
|
|
220
244
|
}
|
|
221
245
|
}
|
|
222
246
|
});
|
|
223
247
|
});
|
|
248
|
+
}
|
|
249
|
+
static processDuplicates(builder, report, rootDir, fileIssues) {
|
|
224
250
|
const patternData = report[ToolName.PatternDetect] || report.patternDetect || report.patterns || {};
|
|
225
251
|
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
252
|
duplicates.forEach((dup) => {
|
|
227
|
-
builder.addNode(dup.file1, "Similarity target",
|
|
228
|
-
builder.addNode(dup.file2, "Similarity target",
|
|
253
|
+
builder.addNode(dup.file1, "Similarity target", GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE);
|
|
254
|
+
builder.addNode(dup.file2, "Similarity target", GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE);
|
|
229
255
|
builder.addEdge(dup.file1, dup.file2, "similarity");
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
fileIssues.
|
|
236
|
-
|
|
237
|
-
fileIssues.get(f2).duplicates += 1;
|
|
256
|
+
[dup.file1, dup.file2].forEach((file) => {
|
|
257
|
+
const id = path2.resolve(rootDir, file);
|
|
258
|
+
if (!fileIssues.has(id)) {
|
|
259
|
+
fileIssues.set(id, { count: 0, maxSeverity: null, duplicates: 0 });
|
|
260
|
+
}
|
|
261
|
+
fileIssues.get(id).duplicates += 1;
|
|
262
|
+
});
|
|
238
263
|
});
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
264
|
+
}
|
|
265
|
+
static processContext(builder, results, rootDir, bumpIssue) {
|
|
266
|
+
results.forEach((ctx) => {
|
|
267
|
+
const normalized = normalizeAnalysisResult(ctx);
|
|
268
|
+
const file = normalized.fileName;
|
|
242
269
|
if (!file) return;
|
|
243
|
-
builder.addNode(file, `Deps: ${ctx.dependencyCount || 0}`,
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
typeof issue === "string" ? ctx.severity : issue.severity ?? issue.severityLevel ?? null
|
|
248
|
-
);
|
|
249
|
-
bumpIssue(file, sev);
|
|
250
|
-
});
|
|
251
|
-
}
|
|
270
|
+
builder.addNode(file, `Deps: ${ctx.dependencyCount || 0}`, GRAPH_CONSTANTS.DEFAULT_CONTEXT_SIZE);
|
|
271
|
+
normalized.issues.forEach((issue) => {
|
|
272
|
+
bumpIssue(file, issue.severity);
|
|
273
|
+
});
|
|
252
274
|
(ctx.relatedFiles ?? []).forEach((rel) => {
|
|
253
|
-
const resolvedRel =
|
|
254
|
-
const
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
builder.
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
try {
|
|
264
|
-
builder.addEdge(file, resolvedRel, "related");
|
|
265
|
-
} catch {
|
|
275
|
+
const resolvedRel = path2.isAbsolute(rel) ? rel : path2.resolve(path2.dirname(file), rel);
|
|
276
|
+
const resolvedFile = path2.resolve(builder.rootDir, file);
|
|
277
|
+
const resolvedTarget = path2.resolve(builder.rootDir, resolvedRel);
|
|
278
|
+
const keyA = `${resolvedFile}->${resolvedTarget}`;
|
|
279
|
+
const keyB = `${resolvedTarget}->${resolvedFile}`;
|
|
280
|
+
if (builder["edgesSet"].has(keyA) || builder["edgesSet"].has(keyB)) return;
|
|
281
|
+
builder.addNode(resolvedRel, "Related file", GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE);
|
|
282
|
+
const node = builder["nodesMap"].get(resolvedTarget);
|
|
283
|
+
if (node) {
|
|
284
|
+
node.size = (node.size || 1) + 2;
|
|
266
285
|
}
|
|
286
|
+
builder.addEdge(file, resolvedRel, "related");
|
|
267
287
|
});
|
|
268
|
-
const
|
|
269
|
-
const fileDir = path.dirname(absoluteFile);
|
|
288
|
+
const fileDir = path2.dirname(path2.resolve(builder.rootDir, file));
|
|
270
289
|
(ctx.dependencyList ?? []).forEach((dep) => {
|
|
271
290
|
if (dep.startsWith(".") || dep.startsWith("/")) {
|
|
272
291
|
const possiblePaths = [
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
292
|
+
path2.resolve(fileDir, dep),
|
|
293
|
+
path2.resolve(fileDir, dep + ".ts"),
|
|
294
|
+
path2.resolve(fileDir, dep + ".tsx"),
|
|
295
|
+
path2.resolve(fileDir, dep + ".js"),
|
|
296
|
+
path2.resolve(fileDir, dep, "index.ts"),
|
|
297
|
+
path2.resolve(fileDir, dep, "index.tsx")
|
|
279
298
|
];
|
|
280
299
|
for (const p of possiblePaths) {
|
|
281
300
|
if (fs.existsSync(p)) {
|
|
282
|
-
builder.addNode(p, "Dependency",
|
|
301
|
+
builder.addNode(p, "Dependency", GRAPH_CONSTANTS.DEFAULT_DEPENDENCY_SIZE);
|
|
283
302
|
builder.addEdge(file, p, "dependency");
|
|
284
303
|
break;
|
|
285
304
|
}
|
|
@@ -287,100 +306,60 @@ var GraphBuilder = class _GraphBuilder {
|
|
|
287
306
|
}
|
|
288
307
|
});
|
|
289
308
|
});
|
|
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);
|
|
309
|
+
}
|
|
310
|
+
static processToolResults(builder, toolName, legacyKey, report, bumpIssue, title) {
|
|
311
|
+
const camelKey = toolName.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
312
|
+
const toolData = report[toolName] ?? report[camelKey] ?? report[legacyKey];
|
|
313
|
+
if (!toolData) return;
|
|
314
|
+
const results = Array.isArray(toolData) ? toolData : toolData.results ?? toolData.issues ?? [];
|
|
315
|
+
results.forEach((item) => {
|
|
316
|
+
if (!Array.isArray(item.issues) && (item.severity || item.message)) {
|
|
317
|
+
const file2 = item.fileName ?? item.file ?? item.location?.file;
|
|
318
|
+
if (file2) {
|
|
319
|
+
builder.addNode(file2, title, GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE);
|
|
320
|
+
bumpIssue(file2, rankSeverity(item.severity));
|
|
321
|
+
}
|
|
322
|
+
return;
|
|
309
323
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
ToolName.ContractEnforcement,
|
|
313
|
-
"contractEnforcement"
|
|
314
|
-
);
|
|
315
|
-
contractEnforcementResults.forEach((issue) => {
|
|
316
|
-
const file = issue.fileName ?? issue.location?.file;
|
|
324
|
+
const normalized = normalizeAnalysisResult(item);
|
|
325
|
+
const file = normalized.fileName;
|
|
317
326
|
if (file) {
|
|
318
|
-
builder.addNode(file,
|
|
319
|
-
|
|
320
|
-
|
|
327
|
+
builder.addNode(file, title, GRAPH_CONSTANTS.DEFAULT_REFERENCE_SIZE);
|
|
328
|
+
normalized.issues.forEach((issue) => {
|
|
329
|
+
bumpIssue(file, issue.severity);
|
|
330
|
+
});
|
|
321
331
|
}
|
|
322
332
|
});
|
|
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
|
-
};
|
|
333
|
+
}
|
|
334
|
+
static finalizeGraph(builder, fileIssues, report) {
|
|
335
|
+
const graph = builder.build();
|
|
343
336
|
let criticalIssues = 0;
|
|
344
337
|
let majorIssues = 0;
|
|
345
338
|
let minorIssues = 0;
|
|
346
339
|
let infoIssues = 0;
|
|
347
|
-
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
if (
|
|
355
|
-
else if (
|
|
356
|
-
else if (
|
|
357
|
-
else if (rec.maxSeverity === Severity.Info) infoIssues += rec.count;
|
|
340
|
+
graph.nodes.forEach((node) => {
|
|
341
|
+
const record = fileIssues.get(node.id);
|
|
342
|
+
if (record) {
|
|
343
|
+
node.duplicates = record.duplicates || 0;
|
|
344
|
+
node.color = getColorForSeverity(record.maxSeverity);
|
|
345
|
+
node.group = getPackageGroup(node.id);
|
|
346
|
+
if (record.maxSeverity === Severity.Critical) criticalIssues += record.count;
|
|
347
|
+
else if (record.maxSeverity === Severity.Major) majorIssues += record.count;
|
|
348
|
+
else if (record.maxSeverity === Severity.Minor) minorIssues += record.count;
|
|
349
|
+
else if (record.maxSeverity === Severity.Info) infoIssues += record.count;
|
|
358
350
|
} 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
|
|
351
|
+
node.color = getColorForSeverity(null);
|
|
352
|
+
node.group = getPackageGroup(node.id);
|
|
353
|
+
node.duplicates = 0;
|
|
383
354
|
}
|
|
355
|
+
});
|
|
356
|
+
graph.metadata = {
|
|
357
|
+
...graph.metadata,
|
|
358
|
+
criticalIssues,
|
|
359
|
+
majorIssues,
|
|
360
|
+
minorIssues,
|
|
361
|
+
infoIssues,
|
|
362
|
+
tokenBudget: report.scoring?.tokenBudget
|
|
384
363
|
};
|
|
385
364
|
return graph;
|
|
386
365
|
}
|
|
@@ -390,11 +369,7 @@ function createSampleGraph() {
|
|
|
390
369
|
builder.addNode("src/components/Button.tsx", "Button", 15);
|
|
391
370
|
builder.addNode("src/utils/helpers.ts", "helpers", 12);
|
|
392
371
|
builder.addNode("src/services/api.ts", "api", 18);
|
|
393
|
-
builder.addEdge(
|
|
394
|
-
"src/components/Button.tsx",
|
|
395
|
-
"src/utils/helpers.ts",
|
|
396
|
-
"dependency"
|
|
397
|
-
);
|
|
372
|
+
builder.addEdge("src/components/Button.tsx", "src/utils/helpers.ts", "dependency");
|
|
398
373
|
builder.addEdge("src/utils/helpers.ts", "src/services/api.ts", "dependency");
|
|
399
374
|
return builder.build();
|
|
400
375
|
}
|