dependency-radar 0.6.1 → 0.8.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.
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isNodeEngineTargetCompatible = isNodeEngineTargetCompatible;
4
+ function parseVersionParts(value) {
5
+ const match = value.trim().match(/^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?$/);
6
+ if (!match)
7
+ return undefined;
8
+ return {
9
+ version: [Number(match[1]), Number(match[2] || 0), Number(match[3] || 0)],
10
+ parts: match[3] ? 3 : match[2] ? 2 : 1,
11
+ };
12
+ }
13
+ function parseVersion(value) {
14
+ var _a;
15
+ return (_a = parseVersionParts(value)) === null || _a === void 0 ? void 0 : _a.version;
16
+ }
17
+ function compare(a, b) {
18
+ for (let i = 0; i < 3; i += 1)
19
+ if (a[i] !== b[i])
20
+ return a[i] - b[i];
21
+ return 0;
22
+ }
23
+ function satisfiesComparator(target, comparator) {
24
+ const match = comparator.trim().match(/^(<=|>=|<|>|=)?\s*v?(\d+(?:\.\d+){0,2})$/);
25
+ if (!match)
26
+ return false;
27
+ const version = parseVersion(match[2]);
28
+ if (!version)
29
+ return false;
30
+ const diff = compare(target, version);
31
+ const op = match[1] || '=';
32
+ if (op === '<')
33
+ return diff < 0;
34
+ if (op === '<=')
35
+ return diff <= 0;
36
+ if (op === '>')
37
+ return diff > 0;
38
+ if (op === '>=')
39
+ return diff >= 0;
40
+ return diff === 0;
41
+ }
42
+ function comparatorAllowsTargetMajor(comparator, minTarget, maxTarget) {
43
+ const bounds = boundsForComparator(comparator);
44
+ return Boolean(bounds && intervalsOverlap(bounds, { lower: minTarget, lowerInclusive: true, upper: maxTarget, upperInclusive: false }));
45
+ }
46
+ function boundsForComparator(comparator) {
47
+ const match = comparator.trim().match(/^(<=|>=|<|>|=)?\s*v?(\d+(?:\.\d+){0,2})$/);
48
+ if (!match)
49
+ return undefined;
50
+ const parsed = parseVersionParts(match[2]);
51
+ if (!parsed)
52
+ return undefined;
53
+ const version = parsed.version;
54
+ const nextMajor = [version[0] + 1, 0, 0];
55
+ const op = match[1] || '=';
56
+ if (op === '<')
57
+ return { lowerInclusive: true, upper: version, upperInclusive: false };
58
+ if (op === '<=' && parsed.parts === 1)
59
+ return { lowerInclusive: true, upper: nextMajor, upperInclusive: false };
60
+ if (op === '<=')
61
+ return { lowerInclusive: true, upper: version, upperInclusive: true };
62
+ if (op === '>' && parsed.parts === 1)
63
+ return { lower: nextMajor, lowerInclusive: true, upperInclusive: true };
64
+ if (op === '>')
65
+ return { lower: version, lowerInclusive: false, upperInclusive: true };
66
+ if (op === '>=')
67
+ return { lower: version, lowerInclusive: true, upperInclusive: true };
68
+ return { lower: version, lowerInclusive: true, upper: version, upperInclusive: true };
69
+ }
70
+ function laterLower(a, b) {
71
+ if (!a.lower)
72
+ return { lower: b.lower, lowerInclusive: b.lowerInclusive };
73
+ if (!b.lower)
74
+ return { lower: a.lower, lowerInclusive: a.lowerInclusive };
75
+ const diff = compare(a.lower, b.lower);
76
+ if (diff > 0)
77
+ return { lower: a.lower, lowerInclusive: a.lowerInclusive };
78
+ if (diff < 0)
79
+ return { lower: b.lower, lowerInclusive: b.lowerInclusive };
80
+ return { lower: a.lower, lowerInclusive: a.lowerInclusive && b.lowerInclusive };
81
+ }
82
+ function earlierUpper(a, b) {
83
+ if (!a.upper)
84
+ return { upper: b.upper, upperInclusive: b.upperInclusive };
85
+ if (!b.upper)
86
+ return { upper: a.upper, upperInclusive: a.upperInclusive };
87
+ const diff = compare(a.upper, b.upper);
88
+ if (diff < 0)
89
+ return { upper: a.upper, upperInclusive: a.upperInclusive };
90
+ if (diff > 0)
91
+ return { upper: b.upper, upperInclusive: b.upperInclusive };
92
+ return { upper: a.upper, upperInclusive: a.upperInclusive && b.upperInclusive };
93
+ }
94
+ function intervalsOverlap(a, b) {
95
+ const lower = laterLower(a, b);
96
+ const upper = earlierUpper(a, b);
97
+ if (!lower.lower || !upper.upper)
98
+ return true;
99
+ const diff = compare(lower.lower, upper.upper);
100
+ if (diff < 0)
101
+ return true;
102
+ if (diff > 0)
103
+ return false;
104
+ return lower.lowerInclusive && upper.upperInclusive;
105
+ }
106
+ function comparatorsOverlapTargetMajor(comparators, targetMajor) {
107
+ const target = {
108
+ lower: [targetMajor, 0, 0],
109
+ lowerInclusive: true,
110
+ upper: [targetMajor + 1, 0, 0],
111
+ upperInclusive: false,
112
+ };
113
+ let interval = {
114
+ lowerInclusive: true,
115
+ upperInclusive: true,
116
+ };
117
+ for (const comparator of comparators) {
118
+ if (!comparatorAllowsTargetMajor(comparator, target.lower, target.upper))
119
+ return false;
120
+ const bounds = boundsForComparator(comparator);
121
+ if (!bounds)
122
+ continue;
123
+ const lower = laterLower(interval, bounds);
124
+ const upper = earlierUpper(interval, bounds);
125
+ interval = { ...lower, ...upper };
126
+ if (!intervalsOverlap(interval, target))
127
+ return false;
128
+ }
129
+ return intervalsOverlap(interval, target);
130
+ }
131
+ function expandToken(token) {
132
+ var _a, _b;
133
+ const trimmed = token.trim();
134
+ if (!trimmed || trimmed === '*' || /^[xX]$/.test(trimmed))
135
+ return [];
136
+ const xRange = trimmed.match(/^v?(\d+)(?:\.(\d+|[xX*]))?(?:\.(\d+|[xX*]))?$/);
137
+ if (xRange && (((_a = xRange[2]) === null || _a === void 0 ? void 0 : _a.match(/^[xX*]$/)) || ((_b = xRange[3]) === null || _b === void 0 ? void 0 : _b.match(/^[xX*]$/)))) {
138
+ const major = Number(xRange[1]);
139
+ if (!xRange[2] || /^[xX*]$/.test(xRange[2]))
140
+ return [`>=${major}.0.0`, `<${major + 1}.0.0`];
141
+ const minor = Number(xRange[2]);
142
+ return [`>=${major}.${minor}.0`, `<${major}.${minor + 1}.0`];
143
+ }
144
+ const caret = trimmed.match(/^\^\s*v?(\d+)(?:\.(\d+))?(?:\.(\d+))?$/);
145
+ if (caret) {
146
+ const major = Number(caret[1]);
147
+ return [`>=${major}.${caret[2] || 0}.${caret[3] || 0}`, `<${major + 1}.0.0`];
148
+ }
149
+ const tilde = trimmed.match(/^~\s*v?(\d+)(?:\.(\d+))?(?:\.(\d+))?$/);
150
+ if (tilde) {
151
+ const major = Number(tilde[1]);
152
+ if (!tilde[2])
153
+ return [`>=${major}.0.0`, `<${major + 1}.0.0`];
154
+ const minor = Number(tilde[2]);
155
+ return [`>=${major}.${minor}.${tilde[3] || 0}`, `<${major}.${minor + 1}.0`];
156
+ }
157
+ const bare = trimmed.match(/^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?$/);
158
+ if (bare) {
159
+ const major = Number(bare[1]);
160
+ if (!bare[2])
161
+ return [`>=${major}.0.0`, `<${major + 1}.0.0`];
162
+ const minor = Number(bare[2]);
163
+ if (!bare[3])
164
+ return [`>=${major}.${minor}.0`, `<${major}.${minor + 1}.0`];
165
+ return [`=${major}.${minor}.${bare[3]}`];
166
+ }
167
+ return [trimmed];
168
+ }
169
+ function isNodeEngineTargetCompatible(range, targetMajor) {
170
+ if (!range || !range.trim())
171
+ return undefined;
172
+ const clauses = range.split('||').map((clause) => clause.trim()).filter(Boolean);
173
+ if (clauses.length === 0)
174
+ return undefined;
175
+ return clauses.some((clause) => {
176
+ const hyphen = clause.match(/^\s*v?(\d+(?:\.\d+){0,2})\s+-\s+v?(\d+(?:\.\d+){0,2})\s*$/);
177
+ const tokens = hyphen ? [`>=${hyphen[1]}`, `<=${hyphen[2]}`] : clause.split(/\s+/);
178
+ const comparators = tokens.flatMap(expandToken);
179
+ return comparators.length === 0 || comparatorsOverlapTargetMajor(comparators, targetMajor);
180
+ });
181
+ }
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderSarif = renderSarif;
4
+ exports.renderCycloneDx = renderCycloneDx;
5
+ exports.renderSpdx = renderSpdx;
6
+ exports.defaultOutputName = defaultOutputName;
7
+ const crypto_1 = require("crypto");
8
+ function purl(dep) {
9
+ const encodedName = dep.package.name.startsWith('@')
10
+ ? `%40${dep.package.name
11
+ .slice(1)
12
+ .split('/')
13
+ .map(encodeURIComponent)
14
+ .join('/')}`
15
+ : encodeURIComponent(dep.package.name);
16
+ return `pkg:npm/${encodedName}@${encodeURIComponent(dep.package.version)}`;
17
+ }
18
+ function findingLevel(finding) {
19
+ if (finding.severity === 'error')
20
+ return 'error';
21
+ if (finding.severity === 'warning')
22
+ return 'warning';
23
+ return 'note';
24
+ }
25
+ function renderSarif(data) {
26
+ const findings = data.findings || [];
27
+ const rules = new Map();
28
+ for (const finding of findings) {
29
+ if (rules.has(finding.category))
30
+ continue;
31
+ rules.set(finding.category, {
32
+ id: finding.category,
33
+ name: finding.category,
34
+ shortDescription: { text: `Dependency Radar ${finding.category} finding` },
35
+ helpUri: 'https://www.dependency-radar.com'
36
+ });
37
+ }
38
+ return JSON.stringify({
39
+ version: '2.1.0',
40
+ $schema: 'https://json.schemastore.org/sarif-2.1.0.json',
41
+ runs: [
42
+ {
43
+ tool: {
44
+ driver: {
45
+ name: 'Dependency Radar',
46
+ informationUri: 'https://www.dependency-radar.com',
47
+ semanticVersion: data.dependencyRadarVersion,
48
+ rules: Array.from(rules.values())
49
+ }
50
+ },
51
+ results: findings.map((finding) => ({
52
+ ruleId: finding.category,
53
+ level: findingLevel(finding),
54
+ message: { text: `${finding.title}: ${finding.message}` },
55
+ properties: {
56
+ dependencyRadarFindingId: finding.id,
57
+ packageId: finding.packageId,
58
+ packageName: finding.packageName,
59
+ packageVersion: finding.packageVersion,
60
+ recommendation: finding.recommendation,
61
+ evidence: finding.evidence
62
+ },
63
+ locations: [sarifLocation(finding)]
64
+ }))
65
+ }
66
+ ]
67
+ }, null, 2);
68
+ }
69
+ function sarifLocation(finding) {
70
+ const extra = finding;
71
+ return {
72
+ physicalLocation: {
73
+ artifactLocation: { uri: extra.sourceFile || extra.sourcePath || 'package.json' },
74
+ region: { startLine: extra.startLine || 1 }
75
+ }
76
+ };
77
+ }
78
+ function licenseIds(dep) {
79
+ const declared = dep.compliance.license.declared;
80
+ const inferred = dep.compliance.license.inferred;
81
+ if ((declared === null || declared === void 0 ? void 0 : declared.valid) && declared.spdxId)
82
+ return [{ license: { id: declared.spdxId } }];
83
+ if (inferred === null || inferred === void 0 ? void 0 : inferred.spdxId)
84
+ return [{ license: { id: inferred.spdxId } }];
85
+ return [{ license: { name: 'UNKNOWN' } }];
86
+ }
87
+ function renderCycloneDx(data) {
88
+ const dependencies = Object.values(data.dependencies || {});
89
+ return JSON.stringify({
90
+ bomFormat: 'CycloneDX',
91
+ specVersion: '1.5',
92
+ version: 1,
93
+ metadata: {
94
+ timestamp: data.generatedAt,
95
+ tools: [{ vendor: 'Dependency Radar', name: 'dependency-radar', version: data.dependencyRadarVersion }],
96
+ component: {
97
+ type: 'application',
98
+ name: data.project.name || data.project.projectDir,
99
+ version: data.project.version || '0.0.0'
100
+ }
101
+ },
102
+ components: dependencies.map((dep) => ({
103
+ type: 'library',
104
+ 'bom-ref': dep.package.id,
105
+ name: dep.package.name,
106
+ version: dep.package.version,
107
+ purl: purl(dep),
108
+ licenses: licenseIds(dep),
109
+ externalReferences: [
110
+ { type: 'distribution', url: dep.package.links.npm },
111
+ ...(dep.package.links.repository ? [{ type: 'vcs', url: dep.package.links.repository }] : []),
112
+ ...(dep.package.links.homepage ? [{ type: 'website', url: dep.package.links.homepage }] : [])
113
+ ]
114
+ })),
115
+ dependencies: dependencies.map((dep) => ({
116
+ ref: dep.package.id,
117
+ dependsOn: subDependencyRefs(dep)
118
+ }))
119
+ }, null, 2);
120
+ }
121
+ function subDependencyRefs(dep) {
122
+ return Object.values(dep.graph.subDeps || {})
123
+ .flatMap((group) => Object.values(group || {}))
124
+ .filter((entry) => Array.isArray(entry) && entry.length >= 2 && (entry[1] === null || typeof entry[1] === 'string'))
125
+ .map((entry) => entry[1])
126
+ .filter((resolved) => Boolean(resolved));
127
+ }
128
+ function spdxNamespace(data) {
129
+ const stable = JSON.stringify({
130
+ name: data.project.name || 'dependency-radar',
131
+ version: data.project.version || '0.0.0',
132
+ generatedAt: data.generatedAt,
133
+ dependencies: Object.keys(data.dependencies || {}).sort()
134
+ });
135
+ return `https://www.dependency-radar.com/spdx/${(0, crypto_1.createHash)('sha256').update(stable).digest('hex').slice(0, 24)}`;
136
+ }
137
+ function renderSpdx(data) {
138
+ const dependencies = Object.values(data.dependencies || {});
139
+ const packages = dependencies.map((dep) => {
140
+ const declared = dep.compliance.license.declared;
141
+ const inferred = dep.compliance.license.inferred;
142
+ const license = (declared === null || declared === void 0 ? void 0 : declared.valid) && declared.spdxId
143
+ ? declared.spdxId
144
+ : (inferred === null || inferred === void 0 ? void 0 : inferred.spdxId) || 'NOASSERTION';
145
+ return {
146
+ SPDXID: `SPDXRef-Package-${dep.package.id.replace(/[^A-Za-z0-9.-]/g, '-')}`,
147
+ name: dep.package.name,
148
+ versionInfo: dep.package.version,
149
+ downloadLocation: dep.package.links.npm,
150
+ filesAnalyzed: false,
151
+ licenseConcluded: license,
152
+ licenseDeclared: license,
153
+ externalRefs: [
154
+ {
155
+ referenceCategory: 'PACKAGE-MANAGER',
156
+ referenceType: 'purl',
157
+ referenceLocator: purl(dep)
158
+ }
159
+ ]
160
+ };
161
+ });
162
+ return JSON.stringify({
163
+ spdxVersion: 'SPDX-2.3',
164
+ dataLicense: 'CC0-1.0',
165
+ SPDXID: 'SPDXRef-DOCUMENT',
166
+ name: `${data.project.name || 'dependency-radar'} dependency report`,
167
+ documentNamespace: spdxNamespace(data),
168
+ creationInfo: {
169
+ created: data.generatedAt,
170
+ creators: [`Tool: dependency-radar-${data.dependencyRadarVersion}`]
171
+ },
172
+ packages
173
+ }, null, 2);
174
+ }
175
+ function defaultOutputName(format) {
176
+ if (format === 'html')
177
+ return 'dependency-radar.html';
178
+ if (format === 'json')
179
+ return 'dependency-radar.json';
180
+ if (format === 'sarif')
181
+ return 'dependency-radar.sarif';
182
+ if (format === 'cyclonedx')
183
+ return 'dependency-radar.cdx.json';
184
+ return 'dependency-radar.spdx.json';
185
+ }