@kingironman2011/license-checker 1.0.2 → 1.1.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 +0 -1
- package/dist/cli/options.d.ts +60 -4
- package/dist/cli/options.js +234 -44
- package/dist/cli/preflight.d.ts +8 -0
- package/dist/cli/preflight.js +32 -0
- package/dist/cli/usage.d.ts +1 -0
- package/dist/cli/usage.js +39 -0
- package/dist/cli.js +17 -33
- package/dist/dependencies/direct-dependencies.d.ts +10 -0
- package/dist/dependencies/direct-dependencies.js +28 -0
- package/dist/dependencies/read-installed-packages.d.ts +9 -2
- package/dist/dependencies/read-installed-packages.js +174 -52
- package/dist/dependencies/walk-dependency-tree.d.ts +19 -0
- package/dist/dependencies/walk-dependency-tree.js +38 -0
- package/dist/files/read-json.d.ts +1 -0
- package/dist/files/read-json.js +8 -0
- package/dist/index.d.ts +88 -3
- package/dist/index.js +272 -24
- package/dist/licenses/clarifications.d.ts +3 -0
- package/dist/licenses/clarifications.js +33 -0
- package/dist/licenses/collect-license-results.d.ts +26 -2
- package/dist/licenses/collect-license-results.js +321 -12
- package/dist/licenses/copyright.d.ts +1 -0
- package/dist/licenses/copyright.js +13 -0
- package/dist/licenses/detect-license-title.d.ts +1 -0
- package/dist/licenses/detect-license-title.js +145 -0
- package/dist/licenses/find-license-files.d.ts +1 -0
- package/dist/licenses/find-license-files.js +26 -0
- package/dist/licenses/package-metadata.d.ts +20 -0
- package/dist/licenses/package-metadata.js +41 -0
- package/dist/licenses/spdx-data.d.ts +4 -0
- package/dist/licenses/spdx-data.js +922 -0
- package/dist/licenses/spdx.d.ts +17 -0
- package/dist/licenses/spdx.js +394 -0
- package/dist/output/ansi.d.ts +4 -0
- package/dist/output/ansi.js +94 -0
- package/dist/output/csv.d.ts +4 -0
- package/dist/output/csv.js +50 -0
- package/dist/output/filter-attributes.d.ts +2 -0
- package/dist/output/filter-attributes.js +10 -0
- package/dist/output/format-output.d.ts +4 -0
- package/dist/output/format-output.js +81 -0
- package/dist/output/renderers.d.ts +15 -0
- package/dist/output/renderers.js +112 -0
- package/dist/output/write-output.d.ts +3 -2
- package/dist/output/write-output.js +18 -10
- package/dist/policies/license-policy.d.ts +20 -0
- package/dist/policies/license-policy.js +112 -0
- package/dist/policies/package-filters.d.ts +6 -0
- package/dist/policies/package-filters.js +56 -0
- package/dist/shared/debug.d.ts +1 -0
- package/dist/shared/debug.js +25 -0
- package/dist/shared/first-defined.d.ts +1 -0
- package/dist/shared/first-defined.js +7 -0
- package/dist/shared/semver.d.ts +1 -0
- package/dist/shared/semver.js +269 -0
- package/dist/types.d.ts +132 -20
- package/package.json +1 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface SpdxLicense {
|
|
2
|
+
license: string;
|
|
3
|
+
plus?: true;
|
|
4
|
+
exception?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface SpdxBinaryExpression {
|
|
7
|
+
left: SpdxExpression;
|
|
8
|
+
conjunction: "and" | "or";
|
|
9
|
+
right: SpdxExpression;
|
|
10
|
+
}
|
|
11
|
+
export type SpdxExpression = SpdxLicense | SpdxBinaryExpression;
|
|
12
|
+
export declare function parseSpdxExpression(source: string): SpdxExpression;
|
|
13
|
+
export declare function isValidSpdxExpression(source: string): boolean;
|
|
14
|
+
export declare function correctSpdxExpression(source: string, options?: {
|
|
15
|
+
upgrade?: boolean;
|
|
16
|
+
}): string | null;
|
|
17
|
+
export declare function spdxExpressionSatisfies(source: string, allowedExpressions: string[]): boolean;
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import { SPDX_DEPRECATED_LICENSE_IDS, SPDX_EXCEPTION_IDS, SPDX_LICENSE_IDS, SPDX_VERSION_RANGES, } from "./spdx-data.js";
|
|
2
|
+
const licenseIds = new Set([
|
|
3
|
+
...SPDX_LICENSE_IDS,
|
|
4
|
+
...SPDX_DEPRECATED_LICENSE_IDS,
|
|
5
|
+
]);
|
|
6
|
+
const exceptionIds = new Set(SPDX_EXCEPTION_IDS);
|
|
7
|
+
function tokenize(expression) {
|
|
8
|
+
const tokens = [];
|
|
9
|
+
let offset = 0;
|
|
10
|
+
while (offset < expression.length) {
|
|
11
|
+
const rest = expression.slice(offset);
|
|
12
|
+
const whitespace = /^\s+/.exec(rest);
|
|
13
|
+
if (whitespace) {
|
|
14
|
+
offset += whitespace[0].length;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const documentLicenseRef = /^(DocumentRef-([A-Za-z0-9.-]+):LicenseRef-([A-Za-z0-9.-]+))/.exec(rest);
|
|
18
|
+
const licenseRef = /^LicenseRef-([A-Za-z0-9.-]+)/.exec(rest);
|
|
19
|
+
if (documentLicenseRef) {
|
|
20
|
+
tokens.push({ type: "licenseRef", value: documentLicenseRef[1] });
|
|
21
|
+
offset += documentLicenseRef[1].length;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (licenseRef) {
|
|
25
|
+
const value = `LicenseRef-${licenseRef[1]}`;
|
|
26
|
+
tokens.push({ type: "licenseRef", value });
|
|
27
|
+
offset += value.length;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const operator = /^(AND|OR|WITH)\b/i.exec(rest);
|
|
31
|
+
if (operator) {
|
|
32
|
+
tokens.push({
|
|
33
|
+
type: "operator",
|
|
34
|
+
value: operator[1].toUpperCase(),
|
|
35
|
+
});
|
|
36
|
+
offset += operator[0].length;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const punctuation = /^[()+:]/.exec(rest);
|
|
40
|
+
if (punctuation) {
|
|
41
|
+
if (punctuation[0] === "+" && /\s/.test(expression[offset - 1] ?? "")) {
|
|
42
|
+
throw new Error("A plus marker cannot be separated from its license identifier");
|
|
43
|
+
}
|
|
44
|
+
tokens.push({ type: "operator", value: punctuation[0] });
|
|
45
|
+
offset += punctuation[0].length;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const identifier = /^[A-Za-z0-9.-]+/.exec(rest);
|
|
49
|
+
if (identifier) {
|
|
50
|
+
const value = identifier[0];
|
|
51
|
+
if (licenseIds.has(value)) {
|
|
52
|
+
tokens.push({ type: "license", value });
|
|
53
|
+
}
|
|
54
|
+
else if (exceptionIds.has(value)) {
|
|
55
|
+
tokens.push({ type: "exception", value });
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
throw new Error(`Unknown SPDX identifier: ${value}`);
|
|
59
|
+
}
|
|
60
|
+
offset += value.length;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
throw new Error(`Invalid SPDX expression at offset ${offset}`);
|
|
64
|
+
}
|
|
65
|
+
return tokens;
|
|
66
|
+
}
|
|
67
|
+
export function parseSpdxExpression(source) {
|
|
68
|
+
const tokens = tokenize(source);
|
|
69
|
+
let index = 0;
|
|
70
|
+
const peek = () => tokens[index];
|
|
71
|
+
const takeOperator = (value) => {
|
|
72
|
+
if (peek()?.type === "operator" && peek()?.value === value) {
|
|
73
|
+
index += 1;
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
};
|
|
78
|
+
const parseAtom = () => {
|
|
79
|
+
if (takeOperator("(")) {
|
|
80
|
+
const nested = parseOr();
|
|
81
|
+
if (!takeOperator(")"))
|
|
82
|
+
throw new Error("Expected closing parenthesis");
|
|
83
|
+
return nested;
|
|
84
|
+
}
|
|
85
|
+
const token = peek();
|
|
86
|
+
if (!token || (token.type !== "license" && token.type !== "licenseRef")) {
|
|
87
|
+
throw new Error("Expected a license identifier");
|
|
88
|
+
}
|
|
89
|
+
index += 1;
|
|
90
|
+
const node = { license: token.value };
|
|
91
|
+
if (token.type === "license" && takeOperator("+"))
|
|
92
|
+
node.plus = true;
|
|
93
|
+
if (takeOperator("WITH")) {
|
|
94
|
+
const exception = peek();
|
|
95
|
+
if (!exception || exception.type !== "exception") {
|
|
96
|
+
throw new Error("Expected an SPDX exception after WITH");
|
|
97
|
+
}
|
|
98
|
+
node.exception = exception.value;
|
|
99
|
+
index += 1;
|
|
100
|
+
}
|
|
101
|
+
return node;
|
|
102
|
+
};
|
|
103
|
+
const parseAnd = () => {
|
|
104
|
+
let expression = parseAtom();
|
|
105
|
+
while (takeOperator("AND")) {
|
|
106
|
+
expression = { left: expression, conjunction: "and", right: parseAtom() };
|
|
107
|
+
}
|
|
108
|
+
return expression;
|
|
109
|
+
};
|
|
110
|
+
const parseOr = () => {
|
|
111
|
+
let expression = parseAnd();
|
|
112
|
+
while (takeOperator("OR")) {
|
|
113
|
+
expression = { left: expression, conjunction: "or", right: parseAnd() };
|
|
114
|
+
}
|
|
115
|
+
return expression;
|
|
116
|
+
};
|
|
117
|
+
if (tokens.length === 0)
|
|
118
|
+
throw new Error("SPDX expression cannot be empty");
|
|
119
|
+
const result = parseOr();
|
|
120
|
+
if (index !== tokens.length)
|
|
121
|
+
throw new Error("Unexpected token in SPDX expression");
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
export function isValidSpdxExpression(source) {
|
|
125
|
+
try {
|
|
126
|
+
parseSpdxExpression(source);
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const transpositions = [
|
|
134
|
+
["GNU LESSER GENERAL PUBLIC LICENSE", "LGPL-2.1"],
|
|
135
|
+
["GNU Lesser General Public License", "LGPL-2.1"],
|
|
136
|
+
["GNU LESSER GENERAL PUBLIC LICENSE", "LGPL"],
|
|
137
|
+
["GNU Lesser General Public License", "LGPL"],
|
|
138
|
+
["LESSER GENERAL PUBLIC LICENSE", "LGPL-2.1"],
|
|
139
|
+
["Lesser General Public License", "LGPL-2.1"],
|
|
140
|
+
["LESSER GENERAL PUBLIC LICENSE", "LGPL"],
|
|
141
|
+
["Lesser General Public License", "LGPL"],
|
|
142
|
+
["GNU General Public License", "GPL"],
|
|
143
|
+
["Gnu public license", "GPL"],
|
|
144
|
+
["GNU Public License", "GPL"],
|
|
145
|
+
["GNU GENERAL PUBLIC LICENSE", "GPL"],
|
|
146
|
+
["Mozilla Public License", "MPL"],
|
|
147
|
+
["Universal Permissive License", "UPL"],
|
|
148
|
+
["GNU/GPL", "GPL"],
|
|
149
|
+
["GNU GLP", "GPL"],
|
|
150
|
+
["GNU LGPL", "LGPL"],
|
|
151
|
+
["GNU", "GPL"],
|
|
152
|
+
["GUN", "GPL"],
|
|
153
|
+
["GLP", "GPL"],
|
|
154
|
+
["Gpl", "GPL"],
|
|
155
|
+
["APGL", "AGPL"],
|
|
156
|
+
["APL", "Apache"],
|
|
157
|
+
["ISD", "ISC"],
|
|
158
|
+
["IST", "ISC"],
|
|
159
|
+
["MTI", "MIT"],
|
|
160
|
+
["WTH", "WTF"],
|
|
161
|
+
["WTFGPL", "WTFPL"],
|
|
162
|
+
[" or later", "+"],
|
|
163
|
+
[" International", ""],
|
|
164
|
+
["Claude", "Clause"],
|
|
165
|
+
["+", ""],
|
|
166
|
+
["-License", ""],
|
|
167
|
+
].toSorted(([left], [right]) => right.length - left.length);
|
|
168
|
+
const transforms = [
|
|
169
|
+
(value) => value.toUpperCase(),
|
|
170
|
+
(value) => value.trim(),
|
|
171
|
+
(value) => value.replace(/\./g, ""),
|
|
172
|
+
(value) => value.replace(/\s+/g, ""),
|
|
173
|
+
(value) => value.replace(/\s+/g, "-"),
|
|
174
|
+
(value) => value.replace("v", "-"),
|
|
175
|
+
(value) => value.replace(/,?\s*(\d)/, "-$1"),
|
|
176
|
+
(value) => value.replace(/,?\s*(\d)/, "-$1.0"),
|
|
177
|
+
(value) => value.replace(/,?\s*(?:V\.?|Version|version)\s*(\d)/i, "-$1"),
|
|
178
|
+
(value) => value.replace(/,?\s*(?:V\.?|Version|version)\s*(\d)/i, "-$1.0"),
|
|
179
|
+
(value) => value[0]?.toUpperCase() + value.slice(1),
|
|
180
|
+
(value) => value.replace("/", "-"),
|
|
181
|
+
(value) => value.replace(/\s*V\s*(\d)/, "-$1").replace(/(\d)$/, "$1.0"),
|
|
182
|
+
(value) => `${value}${value.includes("3.0") ? "-or-later" : "-only"}`,
|
|
183
|
+
(value) => `${value}only`,
|
|
184
|
+
(value) => value.replace(/(\d)$/, "-$1.0"),
|
|
185
|
+
(value) => value.replace(/(-| )?(\d)$/, "-$2-Clause"),
|
|
186
|
+
(value) => value.replace(/(-| )clause(-| )(\d)/i, "-$3-Clause"),
|
|
187
|
+
(value) => value.replace(/\b(Modified|New|Revised)(-| )?BSD((-| )License)?/i, "BSD-3-Clause"),
|
|
188
|
+
(value) => value.replace(/\bSimplified(-| )?BSD((-| )License)?/i, "BSD-2-Clause"),
|
|
189
|
+
(value) => value.replace(/\b(Free|Net)(-| )?BSD((-| )License)?/i, "BSD-2-Clause-$1BSD"),
|
|
190
|
+
(value) => value.replace(/\bClear(-| )?BSD((-| )License)?/i, "BSD-3-Clause-Clear"),
|
|
191
|
+
(value) => value.replace(/\b(Old|Original)(-| )?BSD((-| )License)?/i, "BSD-4-Clause"),
|
|
192
|
+
(value) => `CC-${value}`,
|
|
193
|
+
(value) => `CC-${value}-4.0`,
|
|
194
|
+
(value) => `CC-${value
|
|
195
|
+
.replace(/Attribution/g, "BY")
|
|
196
|
+
.replace(/NonCommercial/g, "NC")
|
|
197
|
+
.replace(/NoDerivatives/g, "ND")
|
|
198
|
+
.replace(/ (\d)/, "-$1")
|
|
199
|
+
.replace(/ ?International/, "")}`,
|
|
200
|
+
(value) => `CC-${value
|
|
201
|
+
.replace(/Attribution/g, "BY")
|
|
202
|
+
.replace(/NonCommercial/g, "NC")
|
|
203
|
+
.replace(/NoDerivatives/g, "ND")
|
|
204
|
+
.replace(/ (\d)/, "-$1")
|
|
205
|
+
.replace(/ ?International/, "")}-4.0`,
|
|
206
|
+
];
|
|
207
|
+
const basicLastResorts = [
|
|
208
|
+
["UNLI", "Unlicense"],
|
|
209
|
+
["WTF", "WTFPL"],
|
|
210
|
+
["2 CLAUSE", "BSD-2-Clause"],
|
|
211
|
+
["2-CLAUSE", "BSD-2-Clause"],
|
|
212
|
+
["3 CLAUSE", "BSD-3-Clause"],
|
|
213
|
+
["3-CLAUSE", "BSD-3-Clause"],
|
|
214
|
+
["AFFERO", "AGPL-3.0-or-later"],
|
|
215
|
+
["AGPL", "AGPL-3.0-or-later"],
|
|
216
|
+
["APACHE", "Apache-2.0"],
|
|
217
|
+
["ARTISTIC", "Artistic-2.0"],
|
|
218
|
+
["BEER", "Beerware"],
|
|
219
|
+
["BOOST", "BSL-1.0"],
|
|
220
|
+
["BSD", "BSD-2-Clause"],
|
|
221
|
+
["CDDL", "CDDL-1.1"],
|
|
222
|
+
["ECLIPSE", "EPL-1.0"],
|
|
223
|
+
["FUCK", "WTFPL"],
|
|
224
|
+
["GNU", "GPL-3.0-or-later"],
|
|
225
|
+
["LGPL", "LGPL-3.0-or-later"],
|
|
226
|
+
["GPLV1", "GPL-1.0-only"],
|
|
227
|
+
["GPL-1", "GPL-1.0-only"],
|
|
228
|
+
["GPLV2", "GPL-2.0-only"],
|
|
229
|
+
["GPL-2", "GPL-2.0-only"],
|
|
230
|
+
["GPL", "GPL-3.0-or-later"],
|
|
231
|
+
["MIT +NO-FALSE-ATTRIBS", "MITNFA"],
|
|
232
|
+
["MIT", "MIT"],
|
|
233
|
+
["MPL", "MPL-2.0"],
|
|
234
|
+
["X11", "X11"],
|
|
235
|
+
["ZLIB", "Zlib"],
|
|
236
|
+
];
|
|
237
|
+
const licensesByFamily = new Map();
|
|
238
|
+
for (const licenseId of SPDX_LICENSE_IDS) {
|
|
239
|
+
const match = /^(.*)-\d+\.\d+$/.exec(licenseId);
|
|
240
|
+
if (!match)
|
|
241
|
+
continue;
|
|
242
|
+
const family = match[1];
|
|
243
|
+
const versions = licensesByFamily.get(family) ?? [];
|
|
244
|
+
versions.push(licenseId);
|
|
245
|
+
licensesByFamily.set(family, versions);
|
|
246
|
+
}
|
|
247
|
+
const singleVersionAliases = Array.from(licensesByFamily.entries())
|
|
248
|
+
.filter(([family, versions]) => family !== "APL" && versions.length === 1)
|
|
249
|
+
.map(([family, versions]) => [family, versions[0]]);
|
|
250
|
+
const lastResorts = [
|
|
251
|
+
...basicLastResorts,
|
|
252
|
+
...singleVersionAliases,
|
|
253
|
+
].toSorted(([left], [right]) => right.length - left.length);
|
|
254
|
+
const validLastResort = (value) => {
|
|
255
|
+
const uppercase = value.toUpperCase();
|
|
256
|
+
return (lastResorts.find(([substring]) => uppercase.includes(substring))?.[1] ??
|
|
257
|
+
null);
|
|
258
|
+
};
|
|
259
|
+
const anyCorrection = (value, check) => {
|
|
260
|
+
for (const [transposed, corrected] of transpositions) {
|
|
261
|
+
if (value.includes(transposed)) {
|
|
262
|
+
const result = check(value.replace(transposed, corrected));
|
|
263
|
+
if (result !== null)
|
|
264
|
+
return result;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return null;
|
|
268
|
+
};
|
|
269
|
+
function upgradeGplIdentifiers(value) {
|
|
270
|
+
const only = new Set([
|
|
271
|
+
"GPL-1.0",
|
|
272
|
+
"LGPL-1.0",
|
|
273
|
+
"AGPL-1.0",
|
|
274
|
+
"GPL-2.0",
|
|
275
|
+
"LGPL-2.0",
|
|
276
|
+
"AGPL-2.0",
|
|
277
|
+
"LGPL-2.1",
|
|
278
|
+
]);
|
|
279
|
+
const later = new Set(["GPL-3.0", "LGPL-3.0", "AGPL-3.0"]);
|
|
280
|
+
const plus = new Set([
|
|
281
|
+
"GPL-1.0+",
|
|
282
|
+
"GPL-2.0+",
|
|
283
|
+
"GPL-3.0+",
|
|
284
|
+
"LGPL-2.0+",
|
|
285
|
+
"LGPL-2.1+",
|
|
286
|
+
"LGPL-3.0+",
|
|
287
|
+
"AGPL-1.0+",
|
|
288
|
+
"AGPL-3.0+",
|
|
289
|
+
]);
|
|
290
|
+
if (only.has(value))
|
|
291
|
+
return `${value}-only`;
|
|
292
|
+
if (later.has(value))
|
|
293
|
+
return `${value}-or-later`;
|
|
294
|
+
if (plus.has(value))
|
|
295
|
+
return `${value.slice(0, -1)}-or-later`;
|
|
296
|
+
return value;
|
|
297
|
+
}
|
|
298
|
+
export function correctSpdxExpression(source, options = {}) {
|
|
299
|
+
if (typeof source !== "string" || source.trim() === "") {
|
|
300
|
+
throw new Error("Invalid argument. Expected non-empty string.");
|
|
301
|
+
}
|
|
302
|
+
const upgrade = options.upgrade ?? true;
|
|
303
|
+
const postprocess = (value) => upgrade ? upgradeGplIdentifiers(value) : value;
|
|
304
|
+
const input = source.trim();
|
|
305
|
+
if (isValidSpdxExpression(input))
|
|
306
|
+
return postprocess(input);
|
|
307
|
+
const noPlus = input.replace(/\+$/, "").trim();
|
|
308
|
+
if (noPlus !== input && isValidSpdxExpression(noPlus))
|
|
309
|
+
return postprocess(noPlus);
|
|
310
|
+
const validTransformation = (value) => {
|
|
311
|
+
for (const transform of transforms) {
|
|
312
|
+
const transformed = transform(value).trim();
|
|
313
|
+
if (transformed !== value && isValidSpdxExpression(transformed))
|
|
314
|
+
return transformed;
|
|
315
|
+
}
|
|
316
|
+
return null;
|
|
317
|
+
};
|
|
318
|
+
const transformed = validTransformation(input);
|
|
319
|
+
if (transformed !== null)
|
|
320
|
+
return postprocess(transformed);
|
|
321
|
+
const transposed = anyCorrection(input, (candidate) => isValidSpdxExpression(candidate)
|
|
322
|
+
? candidate
|
|
323
|
+
: validTransformation(candidate));
|
|
324
|
+
if (transposed !== null)
|
|
325
|
+
return postprocess(transposed);
|
|
326
|
+
const fallback = validLastResort(input);
|
|
327
|
+
if (fallback !== null)
|
|
328
|
+
return postprocess(fallback);
|
|
329
|
+
const fallbackTransposed = anyCorrection(input, validLastResort);
|
|
330
|
+
return fallbackTransposed === null ? null : postprocess(fallbackTransposed);
|
|
331
|
+
}
|
|
332
|
+
function normalizeGplTerm(term) {
|
|
333
|
+
const legacy = /^(GPL|LGPL|AGPL|GFDL)-(\d+\.\d+)(-only|-or-later)?$/.exec(term.license);
|
|
334
|
+
if (!legacy)
|
|
335
|
+
return term;
|
|
336
|
+
const [, family, version, suffix] = legacy;
|
|
337
|
+
const result = { ...term, license: `${family}-${version}` };
|
|
338
|
+
if (suffix === "-or-later")
|
|
339
|
+
result.plus = true;
|
|
340
|
+
else if (suffix === "-only")
|
|
341
|
+
delete result.plus;
|
|
342
|
+
return result;
|
|
343
|
+
}
|
|
344
|
+
const versionRanges = SPDX_VERSION_RANGES;
|
|
345
|
+
function isInVersionRange(first, second) {
|
|
346
|
+
return versionRanges.some((range) => {
|
|
347
|
+
const indexOf = (license) => range.findIndex((entry) => typeof entry === "string" ? entry === license : entry.includes(license));
|
|
348
|
+
const firstIndex = indexOf(first);
|
|
349
|
+
return firstIndex >= 0 && indexOf(second) >= 0;
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
function isAtLeastInRange(first, second) {
|
|
353
|
+
return versionRanges.some((range) => {
|
|
354
|
+
const indexOf = (license) => range.findIndex((entry) => typeof entry === "string" ? entry === license : entry.includes(license));
|
|
355
|
+
const firstIndex = indexOf(first);
|
|
356
|
+
const secondIndex = indexOf(second);
|
|
357
|
+
return firstIndex >= 0 && secondIndex >= 0 && firstIndex >= secondIndex;
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
function compatibleLicense(expression, approved) {
|
|
361
|
+
if (expression.exception !== approved.exception)
|
|
362
|
+
return false;
|
|
363
|
+
if (!approved.plus && !expression.plus)
|
|
364
|
+
return expression.license === approved.license;
|
|
365
|
+
if (approved.plus && expression.plus) {
|
|
366
|
+
return isInVersionRange(expression.license, approved.license);
|
|
367
|
+
}
|
|
368
|
+
if (approved.plus)
|
|
369
|
+
return isAtLeastInRange(expression.license, approved.license);
|
|
370
|
+
return isAtLeastInRange(approved.license, expression.license);
|
|
371
|
+
}
|
|
372
|
+
function expandToDnf(expression) {
|
|
373
|
+
if (!isBinaryExpression(expression))
|
|
374
|
+
return [[normalizeGplTerm(expression)]];
|
|
375
|
+
const left = expandToDnf(expression.left);
|
|
376
|
+
const right = expandToDnf(expression.right);
|
|
377
|
+
if (expression.conjunction === "or")
|
|
378
|
+
return [...left, ...right];
|
|
379
|
+
return left.flatMap((leftTerms) => right.map((rightTerms) => [...leftTerms, ...rightTerms]));
|
|
380
|
+
}
|
|
381
|
+
function isBinaryExpression(expression) {
|
|
382
|
+
return "conjunction" in expression;
|
|
383
|
+
}
|
|
384
|
+
export function spdxExpressionSatisfies(source, allowedExpressions) {
|
|
385
|
+
const expressionDnf = expandToDnf(parseSpdxExpression(source));
|
|
386
|
+
const allowed = allowedExpressions.map((value) => {
|
|
387
|
+
const parsed = parseSpdxExpression(value);
|
|
388
|
+
if (isBinaryExpression(parsed)) {
|
|
389
|
+
throw new Error("Approved licenses cannot be AND or OR expressions.");
|
|
390
|
+
}
|
|
391
|
+
return normalizeGplTerm(parsed);
|
|
392
|
+
});
|
|
393
|
+
return expressionDnf.some((terms) => terms.every((term) => allowed.some((candidate) => compatibleLicense(term, candidate))));
|
|
394
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function supportsBasicColor(): boolean;
|
|
2
|
+
export declare function boldRed(value: string, enabled?: boolean): string;
|
|
3
|
+
export declare function whiteOnRgb(value: string, rgb: readonly [number, number, number], enabled?: boolean): string;
|
|
4
|
+
export declare function dim(value: string, enabled?: boolean): string;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const ESC = "\u001b[";
|
|
2
|
+
const ansi256Component = (value) => Math.min(5, value < 48 ? 0 : value < 115 ? 1 : Math.round((value - 35) / 40));
|
|
3
|
+
function getColorLevel() {
|
|
4
|
+
const forced = process.env.FORCE_COLOR;
|
|
5
|
+
if (forced !== undefined) {
|
|
6
|
+
if (forced === "0")
|
|
7
|
+
return 0;
|
|
8
|
+
const parsed = Number.parseInt(forced, 10);
|
|
9
|
+
return Number.isNaN(parsed) ? 1 : Math.max(0, Math.min(3, parsed));
|
|
10
|
+
}
|
|
11
|
+
if (process.env.NO_COLOR !== undefined || process.env.NODE_DISABLE_COLORS) {
|
|
12
|
+
return 0;
|
|
13
|
+
}
|
|
14
|
+
const stream = process.stdout;
|
|
15
|
+
if (!stream.isTTY || process.env.TERM === "dumb")
|
|
16
|
+
return 0;
|
|
17
|
+
if (/^(truecolor|24bit)$/i.test(process.env.COLORTERM ?? ""))
|
|
18
|
+
return 3;
|
|
19
|
+
if (/-256(color)?$/i.test(process.env.TERM ?? ""))
|
|
20
|
+
return 2;
|
|
21
|
+
if (process.platform === "win32" || process.env.TERM)
|
|
22
|
+
return 1;
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
export function supportsBasicColor() {
|
|
26
|
+
return getColorLevel() >= 1;
|
|
27
|
+
}
|
|
28
|
+
export function boldRed(value, enabled = supportsBasicColor()) {
|
|
29
|
+
if (!enabled)
|
|
30
|
+
return value;
|
|
31
|
+
return `${ESC}1m${ESC}31m${value}${ESC}39m${ESC}22m`;
|
|
32
|
+
}
|
|
33
|
+
function rgbToAnsi256(red, green, blue) {
|
|
34
|
+
if (red === green && green === blue) {
|
|
35
|
+
if (red < 8)
|
|
36
|
+
return 16;
|
|
37
|
+
if (red > 248)
|
|
38
|
+
return 231;
|
|
39
|
+
return Math.round((red - 8) / 10) + 232;
|
|
40
|
+
}
|
|
41
|
+
return (16 +
|
|
42
|
+
36 * ansi256Component(red) +
|
|
43
|
+
6 * ansi256Component(green) +
|
|
44
|
+
ansi256Component(blue));
|
|
45
|
+
}
|
|
46
|
+
function rgbToAnsi16(red, green, blue) {
|
|
47
|
+
const palette = [
|
|
48
|
+
[0, 0, 0],
|
|
49
|
+
[128, 0, 0],
|
|
50
|
+
[0, 128, 0],
|
|
51
|
+
[128, 128, 0],
|
|
52
|
+
[0, 0, 128],
|
|
53
|
+
[128, 0, 128],
|
|
54
|
+
[0, 128, 128],
|
|
55
|
+
[192, 192, 192],
|
|
56
|
+
[128, 128, 128],
|
|
57
|
+
[255, 0, 0],
|
|
58
|
+
[0, 255, 0],
|
|
59
|
+
[255, 255, 0],
|
|
60
|
+
[0, 0, 255],
|
|
61
|
+
[255, 0, 255],
|
|
62
|
+
[0, 255, 255],
|
|
63
|
+
[255, 255, 255],
|
|
64
|
+
];
|
|
65
|
+
let closest = 0;
|
|
66
|
+
let smallestDistance = Number.POSITIVE_INFINITY;
|
|
67
|
+
palette.forEach(([paletteRed, paletteGreen, paletteBlue], index) => {
|
|
68
|
+
const distance = (red - paletteRed) ** 2 +
|
|
69
|
+
(green - paletteGreen) ** 2 +
|
|
70
|
+
(blue - paletteBlue) ** 2;
|
|
71
|
+
if (distance < smallestDistance) {
|
|
72
|
+
closest = index;
|
|
73
|
+
smallestDistance = distance;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
return closest < 8 ? 40 + closest : 100 + closest - 8;
|
|
77
|
+
}
|
|
78
|
+
function rgbBackground(red, green, blue) {
|
|
79
|
+
const level = getColorLevel();
|
|
80
|
+
if (level >= 3)
|
|
81
|
+
return `${ESC}48;2;${red};${green};${blue}m`;
|
|
82
|
+
if (level >= 2)
|
|
83
|
+
return `${ESC}48;5;${rgbToAnsi256(red, green, blue)}m`;
|
|
84
|
+
return `${ESC}${rgbToAnsi16(red, green, blue)}m`;
|
|
85
|
+
}
|
|
86
|
+
export function whiteOnRgb(value, rgb, enabled = supportsBasicColor()) {
|
|
87
|
+
if (!enabled)
|
|
88
|
+
return value;
|
|
89
|
+
const [red, green, blue] = rgb;
|
|
90
|
+
return `${ESC}37m${rgbBackground(red, green, blue)}${value}${ESC}49m${ESC}39m`;
|
|
91
|
+
}
|
|
92
|
+
export function dim(value, enabled = supportsBasicColor()) {
|
|
93
|
+
return enabled ? `${ESC}2m${value}${ESC}22m` : value;
|
|
94
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CustomFormat, ModuleInfos } from "../types.js";
|
|
2
|
+
export declare const getCsvData: (sorted: ModuleInfos, customFormat?: CustomFormat, csvComponentPrefix?: string) => string[];
|
|
3
|
+
export declare const getCsvHeaders: (customFormat?: CustomFormat, csvComponentPrefix?: string) => string;
|
|
4
|
+
export declare const asCSV: (sorted: ModuleInfos, customFormat?: CustomFormat, csvComponentPrefix?: string) => string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export const getCsvData = (sorted, customFormat, csvComponentPrefix) => {
|
|
2
|
+
const csvDataArr = [];
|
|
3
|
+
Object.entries(sorted).forEach(([key, module]) => {
|
|
4
|
+
const dataElements = [];
|
|
5
|
+
if (csvComponentPrefix) {
|
|
6
|
+
dataElements.push(`"${csvComponentPrefix}"`);
|
|
7
|
+
}
|
|
8
|
+
// Grab the custom keys from the custom format
|
|
9
|
+
if (typeof customFormat === "object" &&
|
|
10
|
+
Object.keys(customFormat).length > 0) {
|
|
11
|
+
dataElements.push(`"${key}"`);
|
|
12
|
+
Object.keys(customFormat).forEach((item) => {
|
|
13
|
+
dataElements.push(`"${module[item]}"`);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
// Be sure to push empty strings for empty values, as this is what CSV expects:
|
|
18
|
+
dataElements.push([
|
|
19
|
+
`"${key}"`,
|
|
20
|
+
`"${module.licenses || ""}"`,
|
|
21
|
+
`"${module.repository || ""}"`,
|
|
22
|
+
]);
|
|
23
|
+
}
|
|
24
|
+
csvDataArr.push(dataElements.join(","));
|
|
25
|
+
});
|
|
26
|
+
return csvDataArr;
|
|
27
|
+
};
|
|
28
|
+
export const getCsvHeaders = (customFormat, csvComponentPrefix) => {
|
|
29
|
+
const prefixName = '"component"';
|
|
30
|
+
const entriesArr = [];
|
|
31
|
+
if (csvComponentPrefix) {
|
|
32
|
+
entriesArr.push(prefixName);
|
|
33
|
+
}
|
|
34
|
+
if (typeof customFormat === "object" &&
|
|
35
|
+
Object.keys(customFormat).length > 0) {
|
|
36
|
+
entriesArr.push('"module name"');
|
|
37
|
+
Object.keys(customFormat).forEach((item) => {
|
|
38
|
+
entriesArr.push(`"${item}"`);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
entriesArr.push('"module name"', '"license"', '"repository"');
|
|
43
|
+
}
|
|
44
|
+
return entriesArr.join(",");
|
|
45
|
+
};
|
|
46
|
+
export const asCSV = (sorted, customFormat, csvComponentPrefix) => {
|
|
47
|
+
const csvHeaders = getCsvHeaders(customFormat, csvComponentPrefix);
|
|
48
|
+
const csvDataArr = getCsvData(sorted, customFormat, csvComponentPrefix);
|
|
49
|
+
return [csvHeaders, ...csvDataArr].join("\n");
|
|
50
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { LicenseCheckOptions, ModuleInfos } from "../types.js";
|
|
2
|
+
export declare function shouldColorizeOutput(args: LicenseCheckOptions): boolean;
|
|
3
|
+
export declare function colorizeOutput(json: ModuleInfos): void;
|
|
4
|
+
export declare function getFormattedOutput(modulesWithVersions: ModuleInfos, args: LicenseCheckOptions): string;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { dim, whiteOnRgb } from "./ansi.js";
|
|
4
|
+
import { asCSV } from "./csv.js";
|
|
5
|
+
import { filterAttributes } from "./filter-attributes.js";
|
|
6
|
+
import { asMarkDown, asPlainVertical, asSummary, asTree } from "./renderers.js";
|
|
7
|
+
export function shouldColorizeOutput(args) {
|
|
8
|
+
return (Boolean(args.color) &&
|
|
9
|
+
!args.out &&
|
|
10
|
+
!args.files &&
|
|
11
|
+
!(args.csv || args.json || args.markdown || args.plainVertical));
|
|
12
|
+
}
|
|
13
|
+
export function colorizeOutput(json) {
|
|
14
|
+
Object.keys(json).forEach((key) => {
|
|
15
|
+
const index = key.lastIndexOf("@");
|
|
16
|
+
const colorizedKey = whiteOnRgb(key.slice(0, index), [47, 79, 79]) +
|
|
17
|
+
dim("@") +
|
|
18
|
+
whiteOnRgb(key.slice(index + 1), [0, 128, 0]);
|
|
19
|
+
if (colorizedKey === key) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
json[colorizedKey] = json[key];
|
|
23
|
+
delete json[key];
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function filterJson(limitAttributes, json) {
|
|
27
|
+
let filteredJson = json;
|
|
28
|
+
if (limitAttributes) {
|
|
29
|
+
filteredJson = {};
|
|
30
|
+
const attributes = limitAttributes
|
|
31
|
+
.split(",")
|
|
32
|
+
.map((attribute) => attribute.trim());
|
|
33
|
+
Object.keys(json).forEach((dependency) => {
|
|
34
|
+
filteredJson[dependency] = filterAttributes(attributes, json[dependency]);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return filteredJson;
|
|
38
|
+
}
|
|
39
|
+
export function getFormattedOutput(modulesWithVersions, args) {
|
|
40
|
+
let filteredJson = filterJson(args.limitAttributes, modulesWithVersions);
|
|
41
|
+
const jsonCopy = Object.fromEntries(Object.entries(filteredJson).map(([moduleName, moduleInfo]) => [
|
|
42
|
+
moduleName,
|
|
43
|
+
{ ...moduleInfo },
|
|
44
|
+
]));
|
|
45
|
+
if (args.files) {
|
|
46
|
+
const filesDirectory = args.files;
|
|
47
|
+
Object.keys(jsonCopy).forEach((moduleName) => {
|
|
48
|
+
const outPath = path.join(filesDirectory, `${moduleName}-LICENSE.txt`);
|
|
49
|
+
const originalLicenseFile = jsonCopy[moduleName].licenseFile;
|
|
50
|
+
if (originalLicenseFile && fs.existsSync(originalLicenseFile)) {
|
|
51
|
+
if (args.relativeLicensePath) {
|
|
52
|
+
if (args.out) {
|
|
53
|
+
jsonCopy[moduleName].licenseFile = path.relative(path.dirname(args.out), outPath);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
jsonCopy[moduleName].licenseFile = path.relative(process.cwd(), outPath);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
jsonCopy[moduleName].licenseFile = outPath;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
if (args.json) {
|
|
66
|
+
return `${JSON.stringify(jsonCopy, null, 4)}\n`;
|
|
67
|
+
}
|
|
68
|
+
if (args.csv) {
|
|
69
|
+
return asCSV(jsonCopy, args.customFormat, args.csvComponentPrefix);
|
|
70
|
+
}
|
|
71
|
+
if (args.markdown) {
|
|
72
|
+
return `${asMarkDown(jsonCopy, args.customFormat)}\n`;
|
|
73
|
+
}
|
|
74
|
+
if (args.summary) {
|
|
75
|
+
return asSummary(jsonCopy);
|
|
76
|
+
}
|
|
77
|
+
if (args.plainVertical || args.angularCli) {
|
|
78
|
+
return asPlainVertical(jsonCopy);
|
|
79
|
+
}
|
|
80
|
+
return asTree(jsonCopy);
|
|
81
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CustomFormat, ModuleInfos } from "../types.js";
|
|
2
|
+
export declare const asTree: (sorted: ModuleInfos) => string;
|
|
3
|
+
export declare const asSummary: (sorted: ModuleInfos) => string;
|
|
4
|
+
/**
|
|
5
|
+
* Exports data as Markdown (*.md) which has its own syntax.
|
|
6
|
+
*
|
|
7
|
+
* @param sorted The sorted JSON data from all packages.
|
|
8
|
+
* @param {object} [customFormat] The custom format with information about the needed keys.
|
|
9
|
+
* @return {string} The returning plain text.
|
|
10
|
+
*/
|
|
11
|
+
export declare const asMarkDown: (sorted: ModuleInfos, customFormat?: CustomFormat) => string;
|
|
12
|
+
/**
|
|
13
|
+
* Output data in plain vertical format like Angular CLI does: https://angular.io/3rdpartylicenses.txt
|
|
14
|
+
*/
|
|
15
|
+
export declare const asPlainVertical: (sorted: ModuleInfos) => string;
|