dep-brain 1.2.0 → 1.4.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/CHANGELOG.md +24 -0
- package/README.md +42 -28
- package/depbrain.config.json +6 -1
- package/depbrain.config.schema.json +6 -1
- package/depbrain.output.schema.json +22 -3
- package/dist/checks/risk.d.ts +3 -1
- package/dist/checks/risk.js +205 -35
- package/dist/cli.js +11 -3
- package/dist/core/analyzer.d.ts +12 -1
- package/dist/core/analyzer.js +56 -11
- package/dist/core/graph-builder.d.ts +1 -0
- package/dist/core/graph-builder.js +153 -58
- package/dist/core/plugin-manager.d.ts +9 -0
- package/dist/core/plugin-manager.js +142 -18
- package/dist/index.d.ts +2 -2
- package/dist/reporters/console.js +9 -2
- package/dist/reporters/dashboard.d.ts +2 -0
- package/dist/reporters/dashboard.js +123 -0
- package/dist/reporters/markdown.js +9 -2
- package/dist/utils/config.d.ts +5 -0
- package/dist/utils/config.js +12 -2
- package/package.json +1 -1
|
@@ -31,8 +31,8 @@ export function renderConsoleReport(result) {
|
|
|
31
31
|
? `${item.name}: ${item.current} -> ${item.latest} [${item.updateType}] [${item.package}]`
|
|
32
32
|
: `${item.name}: ${item.current} -> ${item.latest} [${item.updateType}]`, item.confidence, item.explanation, item.recommendation)));
|
|
33
33
|
appendSection(lines, "Risky dependencies", result.risks.map((item) => formatEntry(item.package
|
|
34
|
-
? `${item.name}: ${item.reasons.join("; ")} [${item.package}] [trust ${item.trustScore.toUpperCase()}]`
|
|
35
|
-
: `${item.name}: ${item.reasons.join("; ")} [trust ${item.trustScore.toUpperCase()}]`, item.confidence, item.explanation, item.recommendation)));
|
|
34
|
+
? `${item.name}: ${item.reasons.join("; ")} [${item.package}] [trust ${item.trustScore.toUpperCase()}]${formatTransitiveRiskSuffix(item)}`
|
|
35
|
+
: `${item.name}: ${item.reasons.join("; ")} [trust ${item.trustScore.toUpperCase()}]${formatTransitiveRiskSuffix(item)}`, item.confidence, item.explanation, item.recommendation)));
|
|
36
36
|
appendSection(lines, "Policy reasons", result.policy.reasons);
|
|
37
37
|
if (result.suggestions.length > 0) {
|
|
38
38
|
lines.push("");
|
|
@@ -43,6 +43,13 @@ export function renderConsoleReport(result) {
|
|
|
43
43
|
}
|
|
44
44
|
return lines.join("\n");
|
|
45
45
|
}
|
|
46
|
+
function formatTransitiveRiskSuffix(item) {
|
|
47
|
+
if (item.riskyTransitiveDeps.length === 0) {
|
|
48
|
+
return "";
|
|
49
|
+
}
|
|
50
|
+
const names = item.riskyTransitiveDeps.slice(0, 3).map((entry) => entry.name).join(", ");
|
|
51
|
+
return ` [transitive score ${item.transitiveRiskScore}] [via ${names}]`;
|
|
52
|
+
}
|
|
46
53
|
function summaryLine(label, count) {
|
|
47
54
|
const indicator = count === 0 ? "OK" : "WARN";
|
|
48
55
|
return `${indicator} ${label}: ${count}`;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
export function renderDashboardReport(result) {
|
|
2
|
+
const topIssues = result.topIssues.map(renderTopIssue).join("");
|
|
3
|
+
const suggestions = result.suggestions.map((item) => `<li>${escapeHtml(item)}</li>`).join("");
|
|
4
|
+
const transitiveHotspots = result.risks
|
|
5
|
+
.filter((item) => item.riskyTransitiveDeps.length > 0)
|
|
6
|
+
.sort((left, right) => right.transitiveRiskScore - left.transitiveRiskScore)
|
|
7
|
+
.map(renderTransitiveHotspot)
|
|
8
|
+
.join("");
|
|
9
|
+
return [
|
|
10
|
+
"<!doctype html>",
|
|
11
|
+
'<html lang="en">',
|
|
12
|
+
"<head>",
|
|
13
|
+
'<meta charset="utf-8">',
|
|
14
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
15
|
+
"<title>Dependency Brain Dashboard</title>",
|
|
16
|
+
"<style>",
|
|
17
|
+
"body{font-family:Arial,sans-serif;margin:0;color:#172033;background:#f6f8fb}",
|
|
18
|
+
"main{max-width:1120px;margin:0 auto;padding:32px}",
|
|
19
|
+
"header{display:flex;justify-content:space-between;gap:24px;align-items:flex-start;margin-bottom:24px}",
|
|
20
|
+
"h1{font-size:28px;margin:0 0 8px}",
|
|
21
|
+
"h2{font-size:18px;margin:0 0 12px}",
|
|
22
|
+
"h3{font-size:15px;margin:0 0 8px}",
|
|
23
|
+
".muted{color:#637083;font-size:13px}",
|
|
24
|
+
".score{font-size:48px;font-weight:700}",
|
|
25
|
+
".grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin-bottom:20px}",
|
|
26
|
+
".split{display:grid;grid-template-columns:1.2fr .8fr;gap:16px;margin-top:16px}",
|
|
27
|
+
".panel{background:#fff;border:1px solid #dce3ee;border-radius:8px;padding:16px}",
|
|
28
|
+
".metric{font-size:28px;font-weight:700;margin-top:4px}",
|
|
29
|
+
".pass{color:#167a43}.fail{color:#b42318}",
|
|
30
|
+
"ol,ul{margin:0;padding-left:20px}",
|
|
31
|
+
"li{margin:8px 0}",
|
|
32
|
+
".issue{margin-bottom:10px}",
|
|
33
|
+
".kind{font-size:12px;text-transform:uppercase;color:#637083}",
|
|
34
|
+
".hotspot{border-top:1px solid #e7ecf4;padding-top:12px;margin-top:12px}",
|
|
35
|
+
".path{font-family:Consolas,monospace;font-size:12px;background:#f3f6fb;border-radius:6px;padding:6px 8px;margin:6px 0}",
|
|
36
|
+
"@media(max-width:760px){main{padding:20px}.grid{grid-template-columns:repeat(2,minmax(0,1fr))}.split{grid-template-columns:1fr}header{display:block}.score{font-size:40px}}",
|
|
37
|
+
"</style>",
|
|
38
|
+
"</head>",
|
|
39
|
+
"<body>",
|
|
40
|
+
"<main>",
|
|
41
|
+
"<header>",
|
|
42
|
+
"<div>",
|
|
43
|
+
"<h1>Dependency Brain Dashboard</h1>",
|
|
44
|
+
`<div class="muted">${escapeHtml(result.rootDir)}</div>`,
|
|
45
|
+
"</div>",
|
|
46
|
+
`<div class="${result.policy.passed ? "pass" : "fail"} score">${result.score}/100</div>`,
|
|
47
|
+
"</header>",
|
|
48
|
+
'<section class="grid">',
|
|
49
|
+
renderMetric("Duplicates", result.duplicates.length),
|
|
50
|
+
renderMetric("Unused", result.unused.length),
|
|
51
|
+
renderMetric("Outdated", result.outdated.length),
|
|
52
|
+
renderMetric("Risks", result.risks.length),
|
|
53
|
+
"</section>",
|
|
54
|
+
'<section class="split">',
|
|
55
|
+
'<div class="panel">',
|
|
56
|
+
"<h2>Policy</h2>",
|
|
57
|
+
`<p class="${result.policy.passed ? "pass" : "fail"}">${result.policy.passed ? "Passed" : "Failed"}</p>`,
|
|
58
|
+
result.policy.reasons.length > 0
|
|
59
|
+
? `<ul>${result.policy.reasons.map((item) => `<li>${escapeHtml(item)}</li>`).join("")}</ul>`
|
|
60
|
+
: '<p class="muted">No policy failures.</p>',
|
|
61
|
+
"</div>",
|
|
62
|
+
'<div class="panel">',
|
|
63
|
+
"<h2>Risk Snapshot</h2>",
|
|
64
|
+
`<div class="muted">${result.risks.filter((item) => item.riskyTransitiveDeps.length > 0).length} direct dependencies carry transitive risk.</div>`,
|
|
65
|
+
`<div class="metric">${result.risks.reduce((total, item) => total + item.transitiveRiskScore, 0)}</div>`,
|
|
66
|
+
'<div class="muted">Total transitive risk score</div>',
|
|
67
|
+
"</div>",
|
|
68
|
+
"</section>",
|
|
69
|
+
'<section class="split">',
|
|
70
|
+
'<div class="panel">',
|
|
71
|
+
"<h2>Top Issues</h2>",
|
|
72
|
+
topIssues.length > 0 ? `<ol>${topIssues}</ol>` : '<p class="muted">No actionable issues found.</p>',
|
|
73
|
+
"</div>",
|
|
74
|
+
'<div class="panel">',
|
|
75
|
+
"<h2>Suggestions</h2>",
|
|
76
|
+
suggestions.length > 0 ? `<ul>${suggestions}</ul>` : '<p class="muted">No suggestions.</p>',
|
|
77
|
+
"</div>",
|
|
78
|
+
"</section>",
|
|
79
|
+
'<section class="panel">',
|
|
80
|
+
"<h2>Transitive Risk Hotspots</h2>",
|
|
81
|
+
transitiveHotspots.length > 0
|
|
82
|
+
? transitiveHotspots
|
|
83
|
+
: '<p class="muted">No transitive risk hotspots found.</p>',
|
|
84
|
+
"</section>",
|
|
85
|
+
"</main>",
|
|
86
|
+
"</body>",
|
|
87
|
+
"</html>"
|
|
88
|
+
].join("\n");
|
|
89
|
+
}
|
|
90
|
+
function renderMetric(label, value) {
|
|
91
|
+
return `<div class="panel"><div class="muted">${escapeHtml(label)}</div><div class="metric">${value}</div></div>`;
|
|
92
|
+
}
|
|
93
|
+
function renderTopIssue(item) {
|
|
94
|
+
return [
|
|
95
|
+
'<li class="issue">',
|
|
96
|
+
`<div class="kind">${escapeHtml(item.kind)} ${escapeHtml(item.priority)}</div>`,
|
|
97
|
+
`<strong>${escapeHtml(item.name)}</strong>`,
|
|
98
|
+
`<div>${escapeHtml(item.recommendation.summary)}</div>`,
|
|
99
|
+
"</li>"
|
|
100
|
+
].join("");
|
|
101
|
+
}
|
|
102
|
+
function renderTransitiveHotspot(item) {
|
|
103
|
+
return [
|
|
104
|
+
'<div class="hotspot">',
|
|
105
|
+
`<h3>${escapeHtml(item.name)} <span class="muted">score ${item.transitiveRiskScore}</span></h3>`,
|
|
106
|
+
`<div class="muted">${item.riskFactors.transitiveDependencyCount} transitive dependencies, ${item.riskyTransitiveDeps.length} risky transitive dependencies</div>`,
|
|
107
|
+
"<ul>",
|
|
108
|
+
item.riskyTransitiveDeps
|
|
109
|
+
.slice(0, 4)
|
|
110
|
+
.map((entry) => `<li><strong>${escapeHtml(entry.name)}</strong> [${escapeHtml(entry.trustScore.toUpperCase())}]<div>${escapeHtml(entry.reasons.join("; "))}</div>${entry.introducedByPaths.map((trace) => `<div class="path">${escapeHtml(trace)}</div>`).join("")}</li>`)
|
|
111
|
+
.join(""),
|
|
112
|
+
"</ul>",
|
|
113
|
+
"</div>"
|
|
114
|
+
].join("");
|
|
115
|
+
}
|
|
116
|
+
function escapeHtml(value) {
|
|
117
|
+
return value
|
|
118
|
+
.replace(/&/g, "&")
|
|
119
|
+
.replace(/</g, "<")
|
|
120
|
+
.replace(/>/g, ">")
|
|
121
|
+
.replace(/"/g, """)
|
|
122
|
+
.replace(/'/g, "'");
|
|
123
|
+
}
|
|
@@ -35,8 +35,8 @@ export function renderMarkdownReport(result) {
|
|
|
35
35
|
? `${item.name}: ${item.current} -> ${item.latest} [${item.updateType}] [${item.package}]`
|
|
36
36
|
: `${item.name}: ${item.current} -> ${item.latest} [${item.updateType}]`, item.confidence, item.explanation, item.recommendation)));
|
|
37
37
|
appendSection(lines, "Risky dependencies", result.risks.map((item) => formatEntry(item.package
|
|
38
|
-
? `${item.name}: ${item.reasons.join("; ")} [${item.package}] [trust ${item.trustScore.toUpperCase()}]`
|
|
39
|
-
: `${item.name}: ${item.reasons.join("; ")} [trust ${item.trustScore.toUpperCase()}]`, item.confidence, item.explanation, item.recommendation)));
|
|
38
|
+
? `${item.name}: ${item.reasons.join("; ")} [${item.package}] [trust ${item.trustScore.toUpperCase()}]${formatTransitiveRiskSuffix(item)}`
|
|
39
|
+
: `${item.name}: ${item.reasons.join("; ")} [trust ${item.trustScore.toUpperCase()}]${formatTransitiveRiskSuffix(item)}`, item.confidence, item.explanation, item.recommendation)));
|
|
40
40
|
appendSection(lines, "Policy reasons", result.policy.reasons);
|
|
41
41
|
if (result.suggestions.length > 0) {
|
|
42
42
|
lines.push("## Suggestions");
|
|
@@ -47,6 +47,13 @@ export function renderMarkdownReport(result) {
|
|
|
47
47
|
}
|
|
48
48
|
return lines.join("\n");
|
|
49
49
|
}
|
|
50
|
+
function formatTransitiveRiskSuffix(item) {
|
|
51
|
+
if (item.riskyTransitiveDeps.length === 0) {
|
|
52
|
+
return "";
|
|
53
|
+
}
|
|
54
|
+
const names = item.riskyTransitiveDeps.slice(0, 3).map((entry) => entry.name).join(", ");
|
|
55
|
+
return ` [transitive score ${item.transitiveRiskScore}] [via ${names}]`;
|
|
56
|
+
}
|
|
50
57
|
function appendSection(lines, title, items) {
|
|
51
58
|
if (items.length === 0) {
|
|
52
59
|
return;
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -26,6 +26,11 @@ export interface DepBrainConfig {
|
|
|
26
26
|
risk: {
|
|
27
27
|
transitiveBloatThreshold: number;
|
|
28
28
|
typosquattingDistanceThreshold: number;
|
|
29
|
+
staleReleaseDays: number;
|
|
30
|
+
agingReleaseDays: number;
|
|
31
|
+
lowDownloadThreshold: number;
|
|
32
|
+
lowTrustWeightThreshold: number;
|
|
33
|
+
mediumTrustWeightThreshold: number;
|
|
29
34
|
};
|
|
30
35
|
dashboard: {
|
|
31
36
|
outputPath: string;
|
package/dist/utils/config.js
CHANGED
|
@@ -27,7 +27,12 @@ export const defaultConfig = {
|
|
|
27
27
|
},
|
|
28
28
|
risk: {
|
|
29
29
|
transitiveBloatThreshold: 50,
|
|
30
|
-
typosquattingDistanceThreshold: 2
|
|
30
|
+
typosquattingDistanceThreshold: 2,
|
|
31
|
+
staleReleaseDays: 730,
|
|
32
|
+
agingReleaseDays: 365,
|
|
33
|
+
lowDownloadThreshold: 1000,
|
|
34
|
+
lowTrustWeightThreshold: 6,
|
|
35
|
+
mediumTrustWeightThreshold: 3
|
|
31
36
|
},
|
|
32
37
|
dashboard: {
|
|
33
38
|
outputPath: "depbrain-dashboard.html"
|
|
@@ -84,7 +89,12 @@ function normalizeConfig(loaded) {
|
|
|
84
89
|
},
|
|
85
90
|
risk: {
|
|
86
91
|
transitiveBloatThreshold: normalizeNumber(loaded.risk?.transitiveBloatThreshold, defaultConfig.risk.transitiveBloatThreshold),
|
|
87
|
-
typosquattingDistanceThreshold: normalizeNumber(loaded.risk?.typosquattingDistanceThreshold, defaultConfig.risk.typosquattingDistanceThreshold)
|
|
92
|
+
typosquattingDistanceThreshold: normalizeNumber(loaded.risk?.typosquattingDistanceThreshold, defaultConfig.risk.typosquattingDistanceThreshold),
|
|
93
|
+
staleReleaseDays: normalizeNumber(loaded.risk?.staleReleaseDays, defaultConfig.risk.staleReleaseDays),
|
|
94
|
+
agingReleaseDays: normalizeNumber(loaded.risk?.agingReleaseDays, defaultConfig.risk.agingReleaseDays),
|
|
95
|
+
lowDownloadThreshold: normalizeNumber(loaded.risk?.lowDownloadThreshold, defaultConfig.risk.lowDownloadThreshold),
|
|
96
|
+
lowTrustWeightThreshold: normalizeNumber(loaded.risk?.lowTrustWeightThreshold, defaultConfig.risk.lowTrustWeightThreshold),
|
|
97
|
+
mediumTrustWeightThreshold: normalizeNumber(loaded.risk?.mediumTrustWeightThreshold, defaultConfig.risk.mediumTrustWeightThreshold)
|
|
88
98
|
},
|
|
89
99
|
dashboard: {
|
|
90
100
|
outputPath: normalizeString(loaded.dashboard?.outputPath, defaultConfig.dashboard.outputPath)
|