dependency-radar 0.6.1 → 0.7.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 +81 -18
- package/dist/cli.js +169 -79
- package/dist/explain.js +193 -0
- package/dist/report-assets.js +2 -2
- package/dist/report.js +5 -5
- package/package.json +4 -2
package/dist/explain.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findDependenciesByPackageName = findDependenciesByPackageName;
|
|
4
|
+
exports.formatExplainOutput = formatExplainOutput;
|
|
5
|
+
const BLOCKER_LABELS = {
|
|
6
|
+
nodeEngine: 'Node engine constraint',
|
|
7
|
+
peerDependency: 'Peer dependency constraints',
|
|
8
|
+
nativeBindings: 'Native bindings/build tooling',
|
|
9
|
+
installScripts: 'Install lifecycle scripts',
|
|
10
|
+
deprecated: 'Deprecated by author',
|
|
11
|
+
};
|
|
12
|
+
function findDependenciesByPackageName(aggregated, packageName) {
|
|
13
|
+
return Object.values(aggregated.dependencies || {})
|
|
14
|
+
.filter((dep) => dep.package.name === packageName)
|
|
15
|
+
.sort((a, b) => {
|
|
16
|
+
if (a.usage.direct !== b.usage.direct) {
|
|
17
|
+
return a.usage.direct ? -1 : 1;
|
|
18
|
+
}
|
|
19
|
+
return compareVersionStrings(b.package.version, a.package.version);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function formatExplainOutput(packageName, matches, context) {
|
|
23
|
+
var _a, _b, _c, _d;
|
|
24
|
+
if (matches.length === 0) {
|
|
25
|
+
return `✖ Package not found: ${packageName}`;
|
|
26
|
+
}
|
|
27
|
+
const versions = Array.from(new Set(matches.map((dep) => dep.package.version))).sort((a, b) => compareVersionStrings(b, a));
|
|
28
|
+
const lines = [];
|
|
29
|
+
const header = versions.length > 1
|
|
30
|
+
? `${packageName} (${versions.length} versions detected)`
|
|
31
|
+
: packageName;
|
|
32
|
+
lines.push(header);
|
|
33
|
+
lines.push('-'.repeat(Math.max(header.length, 24)));
|
|
34
|
+
lines.push('');
|
|
35
|
+
for (let index = 0; index < matches.length; index += 1) {
|
|
36
|
+
const dep = matches[index];
|
|
37
|
+
const staticImportEvidence = resolveStaticImportEvidence(dep, context);
|
|
38
|
+
const vulnerabilitySummary = formatVulnerabilitySummary(dep, context.audit);
|
|
39
|
+
const licenseSummary = formatLicenseSummary(dep);
|
|
40
|
+
const otherVersions = versions.filter((version) => version !== dep.package.version);
|
|
41
|
+
lines.push(dep.package.id);
|
|
42
|
+
lines.push('');
|
|
43
|
+
lines.push(`Type: ${dep.usage.direct ? 'direct' : 'transitive'}`);
|
|
44
|
+
lines.push(`Scope: ${dep.usage.scope}`);
|
|
45
|
+
lines.push(`Introduction: ${dep.usage.introduction || 'unknown'}`);
|
|
46
|
+
lines.push(`Runtime impact: ${dep.usage.runtimeImpact || 'not detected'}`);
|
|
47
|
+
lines.push(`Static import evidence: ${staticImportEvidence}`);
|
|
48
|
+
lines.push('');
|
|
49
|
+
pushListSection(lines, 'Introduced via root packages', [
|
|
50
|
+
...dep.usage.origins.topRootPackages.map((root) => `${root.name}@${root.version}`),
|
|
51
|
+
...formatOverflowLine(dep.usage.origins.rootPackageCount -
|
|
52
|
+
dep.usage.origins.topRootPackages.length, 'root packages'),
|
|
53
|
+
], dep.usage.direct ? 'top-level dependency' : 'none identified');
|
|
54
|
+
pushListSection(lines, 'Direct parents', [
|
|
55
|
+
...dep.usage.origins.topParentPackages,
|
|
56
|
+
...formatOverflowLine(dep.usage.origins.parentPackageCount -
|
|
57
|
+
dep.usage.origins.topParentPackages.length, 'parents'),
|
|
58
|
+
], dep.usage.direct ? 'top-level dependency' : 'none identified');
|
|
59
|
+
if ((_a = dep.usage.origins.workspaces) === null || _a === void 0 ? void 0 : _a.length) {
|
|
60
|
+
pushListSection(lines, 'Workspaces', dep.usage.origins.workspaces, 'none');
|
|
61
|
+
}
|
|
62
|
+
if ((_c = (_b = dep.usage.importUsage) === null || _b === void 0 ? void 0 : _b.topFiles) === null || _c === void 0 ? void 0 : _c.length) {
|
|
63
|
+
pushListSection(lines, 'Imported in', [
|
|
64
|
+
...dep.usage.importUsage.topFiles,
|
|
65
|
+
...formatOverflowLine(dep.usage.importUsage.fileCount - dep.usage.importUsage.topFiles.length, 'files'),
|
|
66
|
+
]);
|
|
67
|
+
}
|
|
68
|
+
else if (context.importGraphComplete) {
|
|
69
|
+
lines.push('Imported in:');
|
|
70
|
+
lines.push(' No static import references found');
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
lines.push('Imported in:');
|
|
74
|
+
lines.push(' unavailable (import graph incomplete)');
|
|
75
|
+
}
|
|
76
|
+
lines.push('');
|
|
77
|
+
lines.push('Vulnerabilities:');
|
|
78
|
+
lines.push(` ${vulnerabilitySummary}`);
|
|
79
|
+
lines.push('');
|
|
80
|
+
lines.push('License:');
|
|
81
|
+
lines.push(` ${licenseSummary}`);
|
|
82
|
+
lines.push('');
|
|
83
|
+
lines.push('Upgrade blockers:');
|
|
84
|
+
if ((_d = dep.upgrade.blockers) === null || _d === void 0 ? void 0 : _d.length) {
|
|
85
|
+
for (const blocker of dep.upgrade.blockers) {
|
|
86
|
+
lines.push(` - ${BLOCKER_LABELS[blocker] || blocker}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
lines.push(' none');
|
|
91
|
+
}
|
|
92
|
+
if (otherVersions.length > 0) {
|
|
93
|
+
lines.push('');
|
|
94
|
+
lines.push('Other detected versions:');
|
|
95
|
+
for (const version of otherVersions) {
|
|
96
|
+
lines.push(` ${version}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (index < matches.length - 1) {
|
|
100
|
+
lines.push('');
|
|
101
|
+
lines.push('');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return lines.join('\n');
|
|
105
|
+
}
|
|
106
|
+
function pushListSection(lines, label, values, emptyFallback = 'none') {
|
|
107
|
+
lines.push(`${label}:`);
|
|
108
|
+
if (values.length === 0) {
|
|
109
|
+
lines.push(` ${emptyFallback}`);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
for (const value of values) {
|
|
113
|
+
lines.push(` ${value}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
lines.push('');
|
|
117
|
+
}
|
|
118
|
+
function formatOverflowLine(count, noun) {
|
|
119
|
+
if (count <= 0)
|
|
120
|
+
return [];
|
|
121
|
+
return [`+${count} more ${noun}`];
|
|
122
|
+
}
|
|
123
|
+
function resolveStaticImportEvidence(dep, context) {
|
|
124
|
+
var _a;
|
|
125
|
+
if ((_a = dep.usage.importUsage) === null || _a === void 0 ? void 0 : _a.fileCount)
|
|
126
|
+
return 'yes';
|
|
127
|
+
return context.importGraphComplete ? 'no' : 'unknown';
|
|
128
|
+
}
|
|
129
|
+
function formatVulnerabilitySummary(dep, availability) {
|
|
130
|
+
if (availability === 'skipped') {
|
|
131
|
+
return 'not available (--offline)';
|
|
132
|
+
}
|
|
133
|
+
if (availability === 'unavailable') {
|
|
134
|
+
return 'not available (audit failed)';
|
|
135
|
+
}
|
|
136
|
+
const summary = dep.security.summary;
|
|
137
|
+
const total = (summary.critical || 0) +
|
|
138
|
+
(summary.high || 0) +
|
|
139
|
+
(summary.moderate || 0) +
|
|
140
|
+
(summary.low || 0);
|
|
141
|
+
if (total === 0)
|
|
142
|
+
return 'none';
|
|
143
|
+
const advisoryLabel = total === 1 ? 'advisory' : 'advisories';
|
|
144
|
+
return `${total} ${advisoryLabel} (highest severity: ${summary.highest})`;
|
|
145
|
+
}
|
|
146
|
+
function formatLicenseSummary(dep) {
|
|
147
|
+
var _a, _b;
|
|
148
|
+
const license = dep.compliance.license;
|
|
149
|
+
const declared = (_a = license.declared) === null || _a === void 0 ? void 0 : _a.spdxId;
|
|
150
|
+
const inferred = (_b = license.inferred) === null || _b === void 0 ? void 0 : _b.spdxId;
|
|
151
|
+
switch (license.status) {
|
|
152
|
+
case 'match':
|
|
153
|
+
return `${declared || inferred || 'unknown'} (declared + inferred match)`;
|
|
154
|
+
case 'declared-only':
|
|
155
|
+
return `${declared || 'unknown'} (declared)`;
|
|
156
|
+
case 'inferred-only':
|
|
157
|
+
return `${inferred || 'unknown'} (inferred)`;
|
|
158
|
+
case 'mismatch':
|
|
159
|
+
return `mismatch (declared ${declared || 'unknown'}, inferred ${inferred || 'unknown'})`;
|
|
160
|
+
case 'invalid-spdx':
|
|
161
|
+
return `invalid SPDX (${declared || 'unknown'})`;
|
|
162
|
+
default:
|
|
163
|
+
return 'unknown';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function compareVersionStrings(left, right) {
|
|
167
|
+
const leftParts = tokenizeVersion(left);
|
|
168
|
+
const rightParts = tokenizeVersion(right);
|
|
169
|
+
const maxLength = Math.max(leftParts.length, rightParts.length);
|
|
170
|
+
for (let index = 0; index < maxLength; index += 1) {
|
|
171
|
+
const leftPart = leftParts[index];
|
|
172
|
+
const rightPart = rightParts[index];
|
|
173
|
+
if (leftPart === undefined)
|
|
174
|
+
return -1;
|
|
175
|
+
if (rightPart === undefined)
|
|
176
|
+
return 1;
|
|
177
|
+
if (typeof leftPart === 'number' && typeof rightPart === 'number') {
|
|
178
|
+
if (leftPart !== rightPart)
|
|
179
|
+
return leftPart - rightPart;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const compared = String(leftPart).localeCompare(String(rightPart));
|
|
183
|
+
if (compared !== 0)
|
|
184
|
+
return compared;
|
|
185
|
+
}
|
|
186
|
+
return left.localeCompare(right);
|
|
187
|
+
}
|
|
188
|
+
function tokenizeVersion(value) {
|
|
189
|
+
return value
|
|
190
|
+
.split(/([0-9]+)/)
|
|
191
|
+
.filter(Boolean)
|
|
192
|
+
.map((part) => (/^[0-9]+$/.test(part) ? Number.parseInt(part, 10) : part));
|
|
193
|
+
}
|