dependency-radar 0.7.0 → 0.8.1
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 +108 -7
- package/dist/aggregator.js +35 -9
- package/dist/cli.js +347 -39
- package/dist/compare.js +79 -0
- package/dist/failOn.js +16 -2
- package/dist/findings.js +166 -0
- package/dist/generated/spdx.js +3 -0
- package/dist/nodeEngine.js +181 -0
- package/dist/outputFormats.js +185 -0
- package/dist/report-assets.js +2 -2
- package/dist/report.js +137 -71
- package/dist/runners/importGraphRunner.js +9 -5
- package/dist/runners/lockfileGraph.js +144 -1
- package/dist/runners/lockfileSignals.js +303 -0
- package/dist/runners/npmLs.js +15 -0
- package/dist/schema.js +107 -0
- package/dist/utils.js +62 -3
- package/dist/why.js +69 -0
- package/dist/workspaceFilter.js +25 -0
- package/package.json +6 -5
- package/dist/runners/depcheckRunner.js +0 -23
- package/dist/runners/licenseChecker.js +0 -33
- package/dist/runners/madgeRunner.js +0 -29
package/dist/compare.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.compareReports = compareReports;
|
|
4
|
+
exports.formatCompareOutput = formatCompareOutput;
|
|
5
|
+
function byName(deps) {
|
|
6
|
+
const map = new Map();
|
|
7
|
+
for (const dep of Object.values(deps || {})) {
|
|
8
|
+
if (!map.has(dep.package.name))
|
|
9
|
+
map.set(dep.package.name, []);
|
|
10
|
+
map.get(dep.package.name).push(dep);
|
|
11
|
+
}
|
|
12
|
+
return map;
|
|
13
|
+
}
|
|
14
|
+
function compareReports(previous, current) {
|
|
15
|
+
const previousIds = new Set(Object.keys(previous.dependencies || {}));
|
|
16
|
+
const currentIds = new Set(Object.keys(current.dependencies || {}));
|
|
17
|
+
const added = Array.from(currentIds).filter((id) => !previousIds.has(id)).sort();
|
|
18
|
+
const removed = Array.from(previousIds).filter((id) => !currentIds.has(id)).sort();
|
|
19
|
+
const previousByName = byName(previous.dependencies || {});
|
|
20
|
+
const currentByName = byName(current.dependencies || {});
|
|
21
|
+
const changedVersions = [];
|
|
22
|
+
for (const [name, previousDeps] of previousByName.entries()) {
|
|
23
|
+
const currentDeps = currentByName.get(name);
|
|
24
|
+
if (!currentDeps || currentDeps.length !== 1 || previousDeps.length !== 1)
|
|
25
|
+
continue;
|
|
26
|
+
const prev = previousDeps[0];
|
|
27
|
+
const next = currentDeps[0];
|
|
28
|
+
if (prev.package.version !== next.package.version) {
|
|
29
|
+
changedVersions.push({ name, from: prev.package.version, to: next.package.version });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
changedVersions.sort((a, b) => a.name.localeCompare(b.name));
|
|
33
|
+
const previousFindingIds = new Set((previous.findings || []).map((finding) => finding.id));
|
|
34
|
+
const currentFindingIds = new Set((current.findings || []).map((finding) => finding.id));
|
|
35
|
+
const byFindingId = (a, b) => (a.id || '').localeCompare(b.id || '');
|
|
36
|
+
const previousFindingById = new Map((previous.findings || []).map((finding) => [finding.id, finding]));
|
|
37
|
+
const serializeFinding = (finding) => JSON.stringify({
|
|
38
|
+
category: finding.category,
|
|
39
|
+
severity: finding.severity,
|
|
40
|
+
packageId: finding.packageId,
|
|
41
|
+
packageName: finding.packageName,
|
|
42
|
+
packageVersion: finding.packageVersion,
|
|
43
|
+
title: finding.title,
|
|
44
|
+
message: finding.message,
|
|
45
|
+
evidence: finding.evidence,
|
|
46
|
+
recommendation: finding.recommendation
|
|
47
|
+
});
|
|
48
|
+
return {
|
|
49
|
+
added,
|
|
50
|
+
removed,
|
|
51
|
+
changedVersions,
|
|
52
|
+
newFindings: (current.findings || []).filter((finding) => !previousFindingIds.has(finding.id)).sort(byFindingId),
|
|
53
|
+
resolvedFindings: (previous.findings || []).filter((finding) => !currentFindingIds.has(finding.id)).sort(byFindingId),
|
|
54
|
+
changedFindings: (current.findings || [])
|
|
55
|
+
.filter((finding) => {
|
|
56
|
+
const previousFinding = previousFindingById.get(finding.id);
|
|
57
|
+
return previousFinding ? serializeFinding(previousFinding) !== serializeFinding(finding) : false;
|
|
58
|
+
})
|
|
59
|
+
.sort(byFindingId)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function section(title, lines, empty) {
|
|
63
|
+
return [
|
|
64
|
+
title,
|
|
65
|
+
'-'.repeat(title.length),
|
|
66
|
+
...(lines.length > 0 ? lines : [empty]),
|
|
67
|
+
''
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
function formatCompareOutput(result) {
|
|
71
|
+
const lines = ['Dependency Radar comparison', '===========================', ''];
|
|
72
|
+
lines.push(...section('Added dependencies', result.added.map((id) => `+ ${id}`), 'none'));
|
|
73
|
+
lines.push(...section('Removed dependencies', result.removed.map((id) => `- ${id}`), 'none'));
|
|
74
|
+
lines.push(...section('Changed versions', result.changedVersions.map((entry) => `${entry.name}: ${entry.from} -> ${entry.to}`), 'none'));
|
|
75
|
+
lines.push(...section('New findings', result.newFindings.map((finding) => `${finding.severity.toUpperCase()} ${finding.packageId}: ${finding.title}`), 'none'));
|
|
76
|
+
lines.push(...section('Changed findings', result.changedFindings.map((finding) => `${finding.severity.toUpperCase()} ${finding.packageId}: ${finding.title}`), 'none'));
|
|
77
|
+
lines.push(...section('Resolved findings', result.resolvedFindings.map((finding) => `${finding.packageId}: ${finding.title}`), 'none'));
|
|
78
|
+
return lines.join('\n').trimEnd();
|
|
79
|
+
}
|
package/dist/failOn.js
CHANGED
|
@@ -10,7 +10,8 @@ exports.SUPPORTED_FAIL_ON_RULES = [
|
|
|
10
10
|
'high-severity-vuln',
|
|
11
11
|
'licence-mismatch',
|
|
12
12
|
'copyleft-detected',
|
|
13
|
-
'unknown-licence'
|
|
13
|
+
'unknown-licence',
|
|
14
|
+
'supply-chain-source'
|
|
14
15
|
];
|
|
15
16
|
const SUPPORTED_FAIL_ON_RULE_SET = new Set(exports.SUPPORTED_FAIL_ON_RULES);
|
|
16
17
|
/**
|
|
@@ -97,7 +98,7 @@ function parseFailOnRules(value) {
|
|
|
97
98
|
* @returns An array of PolicyViolation objects for each rule that has one or more matching issues; returns an empty array if no violations are found
|
|
98
99
|
*/
|
|
99
100
|
function evaluatePolicyViolations(aggregated, rules) {
|
|
100
|
-
var _a;
|
|
101
|
+
var _a, _b;
|
|
101
102
|
if (rules.size === 0)
|
|
102
103
|
return [];
|
|
103
104
|
let reachableProductionVulnCount = 0;
|
|
@@ -106,6 +107,7 @@ function evaluatePolicyViolations(aggregated, rules) {
|
|
|
106
107
|
let licenceMismatchCount = 0;
|
|
107
108
|
let copyleftDetectedCount = 0;
|
|
108
109
|
let unknownLicenceCount = 0;
|
|
110
|
+
let supplyChainSourceCount = 0;
|
|
109
111
|
for (const dep of Object.values(aggregated.dependencies || {})) {
|
|
110
112
|
const isRuntime = dep.usage.scope === 'runtime';
|
|
111
113
|
const hasVuln = vulnerabilityCount(dep) > 0;
|
|
@@ -130,6 +132,11 @@ function evaluatePolicyViolations(aggregated, rules) {
|
|
|
130
132
|
unknownLicenceCount += 1;
|
|
131
133
|
}
|
|
132
134
|
}
|
|
135
|
+
supplyChainSourceCount = (((_b = aggregated.supplyChain) === null || _b === void 0 ? void 0 : _b.signals) || []).filter((signal) => signal.type === 'git-dependency' ||
|
|
136
|
+
signal.type === 'file-dependency' ||
|
|
137
|
+
signal.type === 'non-registry-tarball' ||
|
|
138
|
+
signal.type === 'missing-integrity' ||
|
|
139
|
+
signal.type === 'unexpected-registry-host').length;
|
|
133
140
|
const violations = [];
|
|
134
141
|
if (rules.has('reachable-vuln') && reachableProductionVulnCount > 0) {
|
|
135
142
|
violations.push({
|
|
@@ -173,5 +180,12 @@ function evaluatePolicyViolations(aggregated, rules) {
|
|
|
173
180
|
message: `${unknownLicenceCount} ${pluralize(unknownLicenceCount, 'dependency with unknown licence', 'dependencies with unknown licence')}`
|
|
174
181
|
});
|
|
175
182
|
}
|
|
183
|
+
if (rules.has('supply-chain-source') && supplyChainSourceCount > 0) {
|
|
184
|
+
violations.push({
|
|
185
|
+
rule: 'supply-chain-source',
|
|
186
|
+
count: supplyChainSourceCount,
|
|
187
|
+
message: `${supplyChainSourceCount} ${pluralize(supplyChainSourceCount, 'lockfile supply-chain source finding', 'lockfile supply-chain source findings')}`
|
|
188
|
+
});
|
|
189
|
+
}
|
|
176
190
|
return violations;
|
|
177
191
|
}
|
package/dist/findings.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildDependencyFindings = buildDependencyFindings;
|
|
4
|
+
const nodeEngine_1 = require("./nodeEngine");
|
|
5
|
+
function vulnerabilityTotal(dep) {
|
|
6
|
+
const summary = dep.security.summary;
|
|
7
|
+
return ((summary.critical || 0) +
|
|
8
|
+
(summary.high || 0) +
|
|
9
|
+
(summary.moderate || 0) +
|
|
10
|
+
(summary.low || 0));
|
|
11
|
+
}
|
|
12
|
+
function findingId(dep, suffix) {
|
|
13
|
+
return `${dep.package.id}:${suffix}`.replace(/[^a-zA-Z0-9_.@/-]+/g, '-');
|
|
14
|
+
}
|
|
15
|
+
function supportsTargetNode(dep, targetNodeMajor) {
|
|
16
|
+
if (!targetNodeMajor || !dep.upgrade.nodeEngine)
|
|
17
|
+
return undefined;
|
|
18
|
+
return (0, nodeEngine_1.isNodeEngineTargetCompatible)(dep.upgrade.nodeEngine, targetNodeMajor);
|
|
19
|
+
}
|
|
20
|
+
function baseFinding(dep, suffix, fields) {
|
|
21
|
+
return {
|
|
22
|
+
id: findingId(dep, suffix),
|
|
23
|
+
packageId: dep.package.id,
|
|
24
|
+
packageName: dep.package.name,
|
|
25
|
+
packageVersion: dep.package.version,
|
|
26
|
+
...fields
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function buildDependencyFindings(data, options = {}) {
|
|
30
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
31
|
+
const findings = [];
|
|
32
|
+
for (const dep of Object.values(data.dependencies || {})) {
|
|
33
|
+
const vulnCount = vulnerabilityTotal(dep);
|
|
34
|
+
if (vulnCount > 0) {
|
|
35
|
+
const highest = dep.security.summary.highest;
|
|
36
|
+
findings.push(baseFinding(dep, 'vulnerabilities', {
|
|
37
|
+
category: 'security',
|
|
38
|
+
severity: highest === 'critical' || highest === 'high' ? 'error' : 'warning',
|
|
39
|
+
title: `${vulnCount} known ${vulnCount === 1 ? 'vulnerability' : 'vulnerabilities'}`,
|
|
40
|
+
message: `${dep.package.id} has audit advisories; highest severity is ${highest}.`,
|
|
41
|
+
evidence: (_a = dep.security.advisories) === null || _a === void 0 ? void 0 : _a.map((advisory) => advisory.id).join(', '),
|
|
42
|
+
recommendation: dep.upgrade.latestVersion
|
|
43
|
+
? `Review and upgrade toward ${dep.upgrade.latestVersion}.`
|
|
44
|
+
: 'Review the advisory and upgrade path.'
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
if (dep.compliance.license.status === 'mismatch') {
|
|
48
|
+
findings.push(baseFinding(dep, 'license-mismatch', {
|
|
49
|
+
category: 'license',
|
|
50
|
+
severity: 'warning',
|
|
51
|
+
title: 'Declared and inferred licenses differ',
|
|
52
|
+
message: `${dep.package.id} declares ${((_b = dep.compliance.license.declared) === null || _b === void 0 ? void 0 : _b.spdxId) || 'unknown'} but the local license file looks like ${((_c = dep.compliance.license.inferred) === null || _c === void 0 ? void 0 : _c.spdxId) || 'unknown'}.`,
|
|
53
|
+
recommendation: 'Verify the installed package license before release or compliance sign-off.'
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
else if (dep.compliance.license.status === 'invalid-spdx' || dep.compliance.license.status === 'unknown') {
|
|
57
|
+
findings.push(baseFinding(dep, `license-${dep.compliance.license.status}`, {
|
|
58
|
+
category: 'license',
|
|
59
|
+
severity: dep.usage.scope === 'runtime' ? 'warning' : 'info',
|
|
60
|
+
title: dep.compliance.license.status === 'unknown' ? 'License is unknown' : 'License declaration is invalid SPDX',
|
|
61
|
+
message: `${dep.package.id} needs license review.`,
|
|
62
|
+
evidence: (_d = dep.compliance.license.declared) === null || _d === void 0 ? void 0 : _d.spdxId,
|
|
63
|
+
recommendation: 'Check package metadata and LICENSE files.'
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
if (dep.compliance.licenseRisk === 'red' && dep.usage.scope === 'runtime') {
|
|
67
|
+
findings.push(baseFinding(dep, 'runtime-license-risk', {
|
|
68
|
+
category: 'license',
|
|
69
|
+
severity: 'error',
|
|
70
|
+
title: 'High-risk runtime license',
|
|
71
|
+
message: `${dep.package.id} is in the runtime tree and has red license risk.`,
|
|
72
|
+
recommendation: 'Review legal/compliance acceptability or replace the package.'
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
if ((_g = (_f = (_e = dep.execution) === null || _e === void 0 ? void 0 : _e.scripts) === null || _f === void 0 ? void 0 : _f.hooks) === null || _g === void 0 ? void 0 : _g.length) {
|
|
76
|
+
findings.push(baseFinding(dep, 'install-scripts', {
|
|
77
|
+
category: 'execution',
|
|
78
|
+
severity: dep.execution.risk === 'red' ? 'error' : 'warning',
|
|
79
|
+
title: 'Install lifecycle script',
|
|
80
|
+
message: `${dep.package.id} runs ${dep.execution.scripts.hooks.join(', ')} during install.`,
|
|
81
|
+
evidence: (_h = dep.execution.scripts.signals) === null || _h === void 0 ? void 0 : _h.join(', '),
|
|
82
|
+
recommendation: 'Review install-time behavior, especially in CI and release environments.'
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
if ((_j = dep.execution) === null || _j === void 0 ? void 0 : _j.native) {
|
|
86
|
+
findings.push(baseFinding(dep, 'native-bindings', {
|
|
87
|
+
category: 'upgrade',
|
|
88
|
+
severity: 'warning',
|
|
89
|
+
title: 'Native binding or build surface',
|
|
90
|
+
message: `${dep.package.id} includes native binding or native build indicators.`,
|
|
91
|
+
recommendation: 'Check platform and Node major compatibility before upgrades.'
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
if (dep.package.deprecated) {
|
|
95
|
+
findings.push(baseFinding(dep, 'deprecated', {
|
|
96
|
+
category: 'supply-chain',
|
|
97
|
+
severity: dep.usage.scope === 'runtime' ? 'warning' : 'info',
|
|
98
|
+
title: 'Package is deprecated',
|
|
99
|
+
message: `${dep.package.id} is marked deprecated in local package metadata.`,
|
|
100
|
+
recommendation: 'Plan migration to a maintained replacement.'
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
const targetSupport = supportsTargetNode(dep, options.targetNodeMajor);
|
|
104
|
+
if (targetSupport === false && options.targetNodeMajor) {
|
|
105
|
+
findings.push(baseFinding(dep, `target-node-${options.targetNodeMajor}`, {
|
|
106
|
+
category: 'upgrade',
|
|
107
|
+
severity: dep.usage.scope === 'runtime' ? 'error' : 'warning',
|
|
108
|
+
title: `May block Node ${options.targetNodeMajor}`,
|
|
109
|
+
message: `${dep.package.id} declares engines.node "${dep.upgrade.nodeEngine}", which does not appear to include Node ${options.targetNodeMajor}.`,
|
|
110
|
+
recommendation: 'Upgrade, replace, or verify engine compatibility manually.'
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
for (const signal of ((_k = data.supplyChain) === null || _k === void 0 ? void 0 : _k.signals) || []) {
|
|
115
|
+
findings.push(buildSupplyChainFinding(signal));
|
|
116
|
+
}
|
|
117
|
+
const signatureAudit = (_l = data.supplyChain) === null || _l === void 0 ? void 0 : _l.signatureAudit;
|
|
118
|
+
if ((signatureAudit === null || signatureAudit === void 0 ? void 0 : signatureAudit.attempted) && !signatureAudit.ok) {
|
|
119
|
+
findings.push({
|
|
120
|
+
id: 'supply-chain:signature-verification-failed',
|
|
121
|
+
category: 'supply-chain',
|
|
122
|
+
severity: 'warning',
|
|
123
|
+
packageId: 'project',
|
|
124
|
+
packageName: 'project',
|
|
125
|
+
packageVersion: '',
|
|
126
|
+
title: 'npm signature/provenance verification failed',
|
|
127
|
+
message: signatureAudit.error || 'npm audit signatures did not complete successfully.',
|
|
128
|
+
evidence: signatureAudit.output,
|
|
129
|
+
recommendation: 'Review npm audit signatures output and verify registry/provenance status.'
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return findings.sort((a, b) => {
|
|
133
|
+
const severityOrder = { error: 2, warning: 1, info: 0 };
|
|
134
|
+
const diff = severityOrder[b.severity] - severityOrder[a.severity];
|
|
135
|
+
if (diff !== 0)
|
|
136
|
+
return diff;
|
|
137
|
+
return a.packageId.localeCompare(b.packageId) || a.id.localeCompare(b.id);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
function buildSupplyChainFinding(signal) {
|
|
141
|
+
const packageId = signal.packageId || (signal.packageName && signal.packageVersion
|
|
142
|
+
? `${signal.packageName}@${signal.packageVersion}`
|
|
143
|
+
: signal.packageName || 'lockfile');
|
|
144
|
+
const titleByType = {
|
|
145
|
+
'git-dependency': 'Git dependency source',
|
|
146
|
+
'file-dependency': 'Local file dependency source',
|
|
147
|
+
'non-registry-tarball': 'Non-registry tarball source',
|
|
148
|
+
'missing-integrity': 'Missing lockfile integrity',
|
|
149
|
+
'unexpected-registry-host': 'Unexpected registry host'
|
|
150
|
+
};
|
|
151
|
+
const severity = signal.type === 'missing-integrity' || signal.type === 'unexpected-registry-host'
|
|
152
|
+
? 'warning'
|
|
153
|
+
: 'info';
|
|
154
|
+
return {
|
|
155
|
+
id: `${packageId}:${signal.type}`.replace(/[^a-zA-Z0-9_.@/-]+/g, '-'),
|
|
156
|
+
category: 'supply-chain',
|
|
157
|
+
severity,
|
|
158
|
+
packageId,
|
|
159
|
+
packageName: signal.packageName || packageId,
|
|
160
|
+
packageVersion: signal.packageVersion || '',
|
|
161
|
+
title: titleByType[signal.type],
|
|
162
|
+
message: signal.detail,
|
|
163
|
+
evidence: signal.source,
|
|
164
|
+
recommendation: 'Review the dependency source and confirm it is expected for this project.'
|
|
165
|
+
};
|
|
166
|
+
}
|
package/dist/generated/spdx.js
CHANGED
|
@@ -75,6 +75,7 @@ exports.SPDX_LICENSE_IDS = new Set([
|
|
|
75
75
|
'Borceux',
|
|
76
76
|
'Brian-Gladman-2-Clause',
|
|
77
77
|
'Brian-Gladman-3-Clause',
|
|
78
|
+
'Brian-Gladman-3-Clause-no-conversion',
|
|
78
79
|
'BSD-1-Clause',
|
|
79
80
|
'BSD-2-Clause',
|
|
80
81
|
'BSD-2-Clause-Darwin',
|
|
@@ -488,6 +489,7 @@ exports.SPDX_LICENSE_IDS = new Set([
|
|
|
488
489
|
'MulanPSL-2.0',
|
|
489
490
|
'Multics',
|
|
490
491
|
'Mup',
|
|
492
|
+
'MVT-1.1',
|
|
491
493
|
'NAIST-2003',
|
|
492
494
|
'NASA-1.3',
|
|
493
495
|
'Naumen',
|
|
@@ -801,6 +803,7 @@ exports.SPDX_EXCEPTION_IDS = new Set([
|
|
|
801
803
|
'GNOME-examples-exception',
|
|
802
804
|
'GNU-compiler-exception',
|
|
803
805
|
'gnu-javamail-exception',
|
|
806
|
+
'Google-Patent-WebM',
|
|
804
807
|
'GPL-3.0-389-ds-base-exception',
|
|
805
808
|
'GPL-3.0-interface-exception',
|
|
806
809
|
'GPL-3.0-linking-exception',
|
|
@@ -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
|
+
}
|