@companion-module/tools 3.1.0 → 3.2.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 +14 -0
- package/README.md +12 -0
- package/dist/license/index.d.ts +10 -0
- package/dist/license/index.js +10 -0
- package/dist/license/inventory.d.ts +111 -0
- package/dist/license/inventory.js +427 -0
- package/dist/license/policy.d.ts +48 -0
- package/dist/license/policy.js +376 -0
- package/dist/scripts/build-connection.js +6 -3
- package/dist/scripts/build-surface.js +6 -3
- package/dist/scripts/lib/build-util.d.ts +1 -0
- package/dist/scripts/lib/build-util.js +5 -1
- package/dist/scripts/lib/cli-options.d.ts +1 -0
- package/dist/scripts/lib/cli-options.js +4 -0
- package/dist/scripts/lib/known-package-licenses.d.ts +3 -2
- package/dist/scripts/lib/known-package-licenses.js +5 -10
- package/dist/scripts/lib/license-util.d.ts +13 -52
- package/dist/scripts/lib/license-util.js +21 -389
- package/dist/scripts/lib/license-warning-util.d.ts +6 -22
- package/dist/scripts/lib/license-warning-util.js +16 -366
- package/dist/scripts/lib/prebuild-check-util.d.ts +53 -0
- package/dist/scripts/lib/prebuild-check-util.js +453 -0
- package/package.json +14 -13
- package/tsconfig/node26/recommended.json +5 -20
|
@@ -1,371 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
'MIT',
|
|
16
|
-
'MIT-0',
|
|
17
|
-
'ISC',
|
|
18
|
-
'BSD-2-Clause',
|
|
19
|
-
'BSD-3-Clause',
|
|
20
|
-
'0BSD',
|
|
21
|
-
'CC0-1.0',
|
|
22
|
-
'Unlicense',
|
|
23
|
-
'BlueOak-1.0.0',
|
|
24
|
-
'Python-2.0',
|
|
25
|
-
'Zlib',
|
|
26
|
-
'BSL-1.0',
|
|
27
|
-
// Not an SPDX identifier, SPDX has none for a public domain dedication. Recording it as CC0-1.0 or Unlicense
|
|
28
|
-
// would name a specific legal instrument the package never used, and public domain carries no obligation at all.
|
|
29
|
-
'Public-Domain',
|
|
30
|
-
];
|
|
31
|
-
const GPL2_DEPENDENCY_LICENSES = [
|
|
32
|
-
'GPL-2.0-only',
|
|
33
|
-
'GPL-2.0-or-later',
|
|
34
|
-
'GPL-2.0', // Deprecated SPDX id for GPL-2.0-only, still widely declared on npm
|
|
35
|
-
];
|
|
36
|
-
const GPL3_DEPENDENCY_LICENSES = [
|
|
37
|
-
'GPL-3.0-only',
|
|
38
|
-
'GPL-3.0-or-later',
|
|
39
|
-
'GPL-3.0', // Deprecated SPDX id for GPL-3.0-only, still widely declared on npm
|
|
40
|
-
];
|
|
41
|
-
// The LGPL families split by which GPL version their relicensing clause can reach. LGPL-2.x section 3 offers "version
|
|
42
|
-
// 2 of the ordinary GPL or any later version", so it suits either GPL policy, while LGPL-3.0 only reaches GPL-3.0.
|
|
43
|
-
const LGPL2_DEPENDENCY_LICENSES = [
|
|
44
|
-
'LGPL-2.0-only',
|
|
45
|
-
'LGPL-2.0-or-later',
|
|
46
|
-
'LGPL-2.0', // Deprecated SPDX id for LGPL-2.0-only, still widely declared on npm
|
|
47
|
-
'LGPL-2.1-only',
|
|
48
|
-
'LGPL-2.1-or-later',
|
|
49
|
-
'LGPL-2.1', // Deprecated SPDX id for LGPL-2.1-only
|
|
50
|
-
];
|
|
51
|
-
const LGPL3_DEPENDENCY_LICENSES = [
|
|
52
|
-
'LGPL-3.0-only',
|
|
53
|
-
'LGPL-3.0-or-later',
|
|
54
|
-
'LGPL-3.0', // Deprecated SPDX id for LGPL-3.0-only
|
|
55
|
-
];
|
|
56
|
-
// Each policy pins the combined work to a single GPL version, so no mix of allowed dependencies can end up
|
|
57
|
-
// unlicensable. Offering GPL-2.0-or-later would break that, a GPL-2.0-only and a GPL-3.0-only dependency could then
|
|
58
|
-
// both be accepted while no version satisfies both.
|
|
59
|
-
const PROJECT_LICENSE_POLICIES = {
|
|
60
|
-
MIT: {
|
|
61
|
-
allowedDependencyLicenses: new Set([
|
|
62
|
-
...PERMISSIVE_DEPENDENCY_LICENSES,
|
|
63
|
-
'Apache-2.0',
|
|
64
|
-
'CC-BY-3.0',
|
|
65
|
-
'CC-BY-4.0',
|
|
66
|
-
'MPL-2.0', // File level copyleft, allows bundling if source is available (links bundled LICENSE)
|
|
67
|
-
]),
|
|
68
|
-
// An MIT bundle cannot offer the relinking static linking would require, but the LGPL is written to let a
|
|
69
|
-
// work under any terms use the library as a separate one, so every LGPL version is fine as an external
|
|
70
|
-
externalOnlyLicenses: new Set([...LGPL2_DEPENDENCY_LICENSES, ...LGPL3_DEPENDENCY_LICENSES]),
|
|
71
|
-
},
|
|
72
|
-
// Apache-2.0 and the CC-BY licenses are deliberately absent, they cannot be combined with GPL-2.0-only
|
|
73
|
-
'GPL-2.0-only': {
|
|
74
|
-
allowedDependencyLicenses: new Set([
|
|
75
|
-
...PERMISSIVE_DEPENDENCY_LICENSES,
|
|
76
|
-
...GPL2_DEPENDENCY_LICENSES,
|
|
77
|
-
// Section 3 relicenses these to GPL-2.0, so the bundle stays distributable under this policy
|
|
78
|
-
...LGPL2_DEPENDENCY_LICENSES,
|
|
79
|
-
'MPL-2.0',
|
|
80
|
-
]),
|
|
81
|
-
// LGPL-3.0 is absent entirely rather than external only. It is GPL-3.0 plus permissions, so it carries terms
|
|
82
|
-
// GPL-2.0 section 6 forbids adding, and linking still forms a combined work whichever way it is shipped.
|
|
83
|
-
externalOnlyLicenses: new Set(),
|
|
84
|
-
},
|
|
85
|
-
'GPL-3.0-only': {
|
|
86
|
-
allowedDependencyLicenses: new Set([
|
|
87
|
-
...PERMISSIVE_DEPENDENCY_LICENSES,
|
|
88
|
-
...GPL3_DEPENDENCY_LICENSES,
|
|
89
|
-
'GPL-2.0-or-later', // GPL-2.0-only is absent, only the "or later" form can be taken to GPL-3.0
|
|
90
|
-
// Both families reach GPL-3.0, the 2.x one through its "or any later version" offer
|
|
91
|
-
...LGPL2_DEPENDENCY_LICENSES,
|
|
92
|
-
...LGPL3_DEPENDENCY_LICENSES,
|
|
93
|
-
'Apache-2.0',
|
|
94
|
-
'MPL-2.0',
|
|
95
|
-
]),
|
|
96
|
-
externalOnlyLicenses: new Set(),
|
|
97
|
-
},
|
|
1
|
+
import { createLicensePolicyIssues as createLicensePolicyIssuesWith, enforceLicensePolicy as enforceLicensePolicyWith, } from '../../license/index.js';
|
|
2
|
+
import { MODULE_LICENSE_OVERRIDES } from './known-package-licenses.js';
|
|
3
|
+
// The engine itself is generic and lives in ../../license, so that it can also be used by the rest of the Companion
|
|
4
|
+
// project. These re-exports keep a single import for everything license related in the module scripts and tests.
|
|
5
|
+
export { LicensePolicyError, SUPPORTED_PROJECT_LICENSES, resolveProjectLicense } from '../../license/index.js';
|
|
6
|
+
/** How the policy engine talks about a module: what it is called, where it declares its licenses, and how it ships */
|
|
7
|
+
export const MODULE_LICENSE_POLICY = {
|
|
8
|
+
subject: 'module',
|
|
9
|
+
distributionLicenseLocation: 'companion/manifest.json',
|
|
10
|
+
// For now module source must be MIT, so it stays portable whatever the packaged module is distributed as.
|
|
11
|
+
// Relaxing this means checking the source license is compatible with the distribution license instead of equal.
|
|
12
|
+
requiredSourceLicense: 'MIT',
|
|
13
|
+
externaliseAdvice: 'Add it to the externals in build-config.cjs so it is installed alongside the module and loaded at runtime.',
|
|
14
|
+
helpMessage: 'Not sure what to do about these? Ask in the Bitfocus community Slack, we are happy to help you work out what they mean for your module.',
|
|
98
15
|
};
|
|
99
|
-
export const SUPPORTED_PROJECT_LICENSES = Object.keys(PROJECT_LICENSE_POLICIES);
|
|
100
|
-
/** Policy applied to dependencies while the module itself has no usable license declaration */
|
|
101
|
-
const FALLBACK_PROJECT_LICENSE = 'MIT';
|
|
102
|
-
/** Suggested to module authors, as the one which can use the most of npm and be reused most freely */
|
|
103
|
-
const RECOMMENDED_PROJECT_LICENSE = 'MIT';
|
|
104
|
-
const LICENSE_HELP_MESSAGE = 'Not sure what to do about these? Ask in the Bitfocus community Slack, we are happy to help you work out what they mean for your module.';
|
|
105
|
-
function isProjectLicense(declaration) {
|
|
106
|
-
return SUPPORTED_PROJECT_LICENSES.includes(declaration);
|
|
107
|
-
}
|
|
108
|
-
function orList(items) {
|
|
109
|
-
const remaining = [...items];
|
|
110
|
-
const last = remaining.pop();
|
|
111
|
-
return remaining.length ? `${remaining.join(', ')} or ${last}` : `${last}`;
|
|
112
|
-
}
|
|
113
|
-
function supportedProjectLicenseAdvice() {
|
|
114
|
-
const alternatives = SUPPORTED_PROJECT_LICENSES.filter((license) => license !== RECOMMENDED_PROJECT_LICENSE);
|
|
115
|
-
return `We recommend ${RECOMMENDED_PROJECT_LICENSE} for the widest compatibility, but also accept ${orList(alternatives)} when necessary.`;
|
|
116
|
-
}
|
|
117
|
-
function evaluate(node, allowedLicenses) {
|
|
118
|
-
if ('license' in node) {
|
|
119
|
-
const license = node.plus ? `${node.license}-or-later` : node.license; // "GPL-2.0+" is deprecated for "GPL-2.0-or-later"
|
|
120
|
-
return {
|
|
121
|
-
allowed: !node.exception && allowedLicenses.has(license),
|
|
122
|
-
incompatibleAnd: false,
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
const left = evaluate(node.left, allowedLicenses);
|
|
126
|
-
const right = evaluate(node.right, allowedLicenses);
|
|
127
|
-
if (node.conjunction === 'or') {
|
|
128
|
-
const allowed = left.allowed || right.allowed;
|
|
129
|
-
return {
|
|
130
|
-
allowed,
|
|
131
|
-
incompatibleAnd: !allowed && (left.incompatibleAnd || right.incompatibleAnd),
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
return {
|
|
135
|
-
allowed: left.allowed && right.allowed,
|
|
136
|
-
incompatibleAnd: left.allowed !== right.allowed || left.incompatibleAnd || right.incompatibleAnd,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* spdx-expression-parse rejects the whole expression when any leaf is not a valid SPDX identifier, so a legacy
|
|
141
|
-
* declaration like "(MIT OR GPL)" never reaches the OR logic at all. Re-read those keeping only the boolean
|
|
142
|
-
* structure, so an unrecognised identifier becomes a license which is simply never allowed. OR then still offers a
|
|
143
|
-
* real choice, and AND still requires every branch, so nothing unknown can be accepted on its own.
|
|
144
|
-
*/
|
|
145
|
-
function parseLenient(declaration) {
|
|
146
|
-
const tokens = declaration.match(/\(|\)|[^\s()]+/g);
|
|
147
|
-
if (!tokens)
|
|
148
|
-
return undefined;
|
|
149
|
-
let index = 0;
|
|
150
|
-
const keyword = (token, word) => token !== undefined && token.toUpperCase() === word;
|
|
151
|
-
function parseExpression() {
|
|
152
|
-
let node = parseTerm();
|
|
153
|
-
if (!node)
|
|
154
|
-
return undefined;
|
|
155
|
-
while (keyword(tokens[index], 'OR')) {
|
|
156
|
-
index++;
|
|
157
|
-
const right = parseTerm();
|
|
158
|
-
if (!right)
|
|
159
|
-
return undefined;
|
|
160
|
-
node = { left: node, conjunction: 'or', right };
|
|
161
|
-
}
|
|
162
|
-
return node;
|
|
163
|
-
}
|
|
164
|
-
function parseTerm() {
|
|
165
|
-
let node = parseFactor();
|
|
166
|
-
if (!node)
|
|
167
|
-
return undefined;
|
|
168
|
-
while (keyword(tokens[index], 'AND')) {
|
|
169
|
-
index++;
|
|
170
|
-
const right = parseFactor();
|
|
171
|
-
if (!right)
|
|
172
|
-
return undefined;
|
|
173
|
-
node = { left: node, conjunction: 'and', right };
|
|
174
|
-
}
|
|
175
|
-
return node;
|
|
176
|
-
}
|
|
177
|
-
function parseFactor() {
|
|
178
|
-
const token = tokens[index];
|
|
179
|
-
if (token === undefined || token === ')')
|
|
180
|
-
return undefined;
|
|
181
|
-
if (token === '(') {
|
|
182
|
-
index++;
|
|
183
|
-
const node = parseExpression();
|
|
184
|
-
if (!node || tokens[index] !== ')')
|
|
185
|
-
return undefined;
|
|
186
|
-
index++;
|
|
187
|
-
return node;
|
|
188
|
-
}
|
|
189
|
-
if (keyword(token, 'AND') || keyword(token, 'OR') || keyword(token, 'WITH'))
|
|
190
|
-
return undefined;
|
|
191
|
-
index++;
|
|
192
|
-
const plus = token.endsWith('+');
|
|
193
|
-
const node = { license: plus ? token.slice(0, -1) : token };
|
|
194
|
-
if (plus)
|
|
195
|
-
node.plus = true;
|
|
196
|
-
if (keyword(tokens[index], 'WITH')) {
|
|
197
|
-
index++;
|
|
198
|
-
if (tokens[index] === undefined)
|
|
199
|
-
return undefined;
|
|
200
|
-
node.exception = tokens[index];
|
|
201
|
-
index++;
|
|
202
|
-
}
|
|
203
|
-
return node;
|
|
204
|
-
}
|
|
205
|
-
const node = parseExpression();
|
|
206
|
-
return node && index === tokens.length ? node : undefined;
|
|
207
|
-
}
|
|
208
|
-
function parseDeclaration(declaration) {
|
|
209
|
-
try {
|
|
210
|
-
return { node: parse(declaration), strict: true };
|
|
211
|
-
}
|
|
212
|
-
catch {
|
|
213
|
-
const node = parseLenient(declaration);
|
|
214
|
-
return node ? { node, strict: false } : undefined;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
function normalizedDeclaration(packageInfo) {
|
|
218
|
-
return packageInfo.declaredLicense?.trim() ?? '';
|
|
219
|
-
}
|
|
220
|
-
function packageLabel(packageInfo) {
|
|
221
|
-
return `${packageInfo.name}${packageInfo.version ? `@${packageInfo.version}` : ''}`;
|
|
222
|
-
}
|
|
223
|
-
function displayDeclaration(declaration) {
|
|
224
|
-
return declaration.replace(/[\u0000-\u001f\u007f]/g, (character) => {
|
|
225
|
-
switch (character) {
|
|
226
|
-
case '\r':
|
|
227
|
-
return '\\r';
|
|
228
|
-
case '\n':
|
|
229
|
-
return '\\n';
|
|
230
|
-
case '\t':
|
|
231
|
-
return '\\t';
|
|
232
|
-
default:
|
|
233
|
-
return `\\u${character.codePointAt(0).toString(16).padStart(4, '0')}`;
|
|
234
|
-
}
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
function ambiguitySuffix(evaluation) {
|
|
238
|
-
return evaluation.incompatibleAnd
|
|
239
|
-
? ' Both licenses may apply, making this declaration ambiguous and incompatible. Ask package author to use OR if either license may be chosen, or clarify package licensing.'
|
|
240
|
-
: '';
|
|
241
|
-
}
|
|
242
|
-
function packageSort(a, b) {
|
|
243
|
-
if (a.kind === 'project' && b.kind !== 'project')
|
|
244
|
-
return -1;
|
|
245
|
-
if (a.kind !== 'project' && b.kind === 'project')
|
|
246
|
-
return 1;
|
|
247
|
-
return (a.name.localeCompare(b.name) ||
|
|
248
|
-
(a.version ?? '').localeCompare(b.version ?? '') ||
|
|
249
|
-
normalizedDeclaration(a).localeCompare(normalizedDeclaration(b)) ||
|
|
250
|
-
a.kind.localeCompare(b.kind));
|
|
251
|
-
}
|
|
252
|
-
function issue(packageInfo, message) {
|
|
253
|
-
return {
|
|
254
|
-
packageName: packageInfo.name,
|
|
255
|
-
packageVersion: packageInfo.version,
|
|
256
|
-
packageKind: packageInfo.kind,
|
|
257
|
-
message,
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
/** Resolve the license the packaged module is distributed under, which selects the policy applied to its dependencies */
|
|
261
|
-
export function resolveProjectLicense(inventory) {
|
|
262
|
-
const projectPackage = inventory.packages.find((packageInfo) => packageInfo.kind === 'project');
|
|
263
|
-
const declaration = projectPackage ? normalizedDeclaration(projectPackage) : '';
|
|
264
|
-
return { declaration, license: isProjectLicense(declaration) ? declaration : undefined };
|
|
265
|
-
}
|
|
266
|
-
// For now module source must be MIT, so it stays portable whatever the packaged module is distributed as. Relaxing
|
|
267
|
-
// this means checking the source license is compatible with the distribution license instead of equal to this one.
|
|
268
|
-
const REQUIRED_SOURCE_LICENSE = 'MIT';
|
|
269
|
-
function createSourceLicenseIssue(packageInfo) {
|
|
270
|
-
const sourceLicense = packageInfo.sourceLicense?.trim() ?? '';
|
|
271
|
-
if (sourceLicense === REQUIRED_SOURCE_LICENSE)
|
|
272
|
-
return undefined;
|
|
273
|
-
if (!sourceLicense) {
|
|
274
|
-
return issue(packageInfo, `Your module source does not declare a license in package.json, but the Companion project requires module source to be ${REQUIRED_SOURCE_LICENSE} so it stays portable.`);
|
|
275
|
-
}
|
|
276
|
-
return issue(packageInfo, `Your module source is licensed as ${displayDeclaration(sourceLicense)} in package.json, but the Companion project requires module source to be ${REQUIRED_SOURCE_LICENSE} so it stays portable. Declare the license the module is distributed under in companion/manifest.json instead.`);
|
|
277
|
-
}
|
|
278
|
-
function createDistributionLicenseIssue(packageInfo) {
|
|
279
|
-
const declaration = normalizedDeclaration(packageInfo);
|
|
280
|
-
if (!declaration) {
|
|
281
|
-
return issue(packageInfo, `Your module does not declare a license in companion/manifest.json. ${supportedProjectLicenseAdvice()} Talk to us if you have a reason to use a different license.`);
|
|
282
|
-
}
|
|
283
|
-
if (isProjectLicense(declaration))
|
|
284
|
-
return undefined;
|
|
285
|
-
let reason = 'which is not supported';
|
|
286
|
-
try {
|
|
287
|
-
// A declaration with a conjunction is dual licensing, which a module must pick a single license from
|
|
288
|
-
if (!('license' in parse(declaration)))
|
|
289
|
-
reason = 'and dual licensing is not supported';
|
|
290
|
-
}
|
|
291
|
-
catch {
|
|
292
|
-
// Report the declaration as unsupported, even when SPDX parsing fails.
|
|
293
|
-
}
|
|
294
|
-
return issue(packageInfo, `Your module is published under ${displayDeclaration(declaration)}, ${reason}. ${supportedProjectLicenseAdvice()} Talk to us if you have a reason to use a different license.`);
|
|
295
|
-
}
|
|
296
|
-
/** The distribution and source licenses are declared in different files, and are reported independently */
|
|
297
|
-
function createProjectIssues(packageInfo) {
|
|
298
|
-
return [createDistributionLicenseIssue(packageInfo), createSourceLicenseIssue(packageInfo)].filter((projectIssue) => projectIssue !== undefined);
|
|
299
|
-
}
|
|
300
16
|
export function createLicensePolicyIssues(inventory) {
|
|
301
|
-
|
|
302
|
-
const seen = new Set();
|
|
303
|
-
const projectLicense = resolveProjectLicense(inventory).license ?? FALLBACK_PROJECT_LICENSE;
|
|
304
|
-
const policy = PROJECT_LICENSE_POLICIES[projectLicense];
|
|
305
|
-
const bundledLicenses = policy.allowedDependencyLicenses;
|
|
306
|
-
// Identical to the bundled set where the policy has nothing which only works as an external, so that no issue is
|
|
307
|
-
// ever reported as fixable by externalising it when externalising would not in fact help
|
|
308
|
-
const externalLicenses = policy.externalOnlyLicenses.size
|
|
309
|
-
? new Set([...bundledLicenses, ...policy.externalOnlyLicenses])
|
|
310
|
-
: bundledLicenses;
|
|
311
|
-
for (const packageInfo of [...inventory.packages].sort(packageSort)) {
|
|
312
|
-
const declaration = normalizedDeclaration(packageInfo);
|
|
313
|
-
const identity = packageInfo.kind === 'project'
|
|
314
|
-
? 'project'
|
|
315
|
-
: `dependency:${packageInfo.name}@${packageInfo.version ?? ''}:${declaration}`;
|
|
316
|
-
if (seen.has(identity))
|
|
317
|
-
continue;
|
|
318
|
-
seen.add(identity);
|
|
319
|
-
if (packageInfo.kind === 'project') {
|
|
320
|
-
result.push(...createProjectIssues(packageInfo));
|
|
321
|
-
continue;
|
|
322
|
-
}
|
|
323
|
-
if (!declaration) {
|
|
324
|
-
result.push(issue(packageInfo, `Dependency ${packageLabel(packageInfo)} has no declared license.`));
|
|
325
|
-
continue;
|
|
326
|
-
}
|
|
327
|
-
let parsed = parseDeclaration(declaration);
|
|
328
|
-
// Only ever replaces a declaration which is not valid SPDX, so a correction can never launder a real license
|
|
329
|
-
if (!parsed || !parsed.strict) {
|
|
330
|
-
const corrected = correctedPackageLicense(packageInfo.name, packageInfo.version);
|
|
331
|
-
if (corrected)
|
|
332
|
-
parsed = parseDeclaration(corrected) ?? parsed;
|
|
333
|
-
}
|
|
334
|
-
if (!parsed) {
|
|
335
|
-
result.push(issue(packageInfo, `Dependency ${packageLabel(packageInfo)} has unparseable license declaration ${displayDeclaration(declaration)}.`));
|
|
336
|
-
continue;
|
|
337
|
-
}
|
|
338
|
-
const allowedLicenses = packageInfo.kind === 'external' ? externalLicenses : bundledLicenses;
|
|
339
|
-
const evaluation = evaluate(parsed.node, allowedLicenses);
|
|
340
|
-
if (evaluation.allowed)
|
|
341
|
-
continue;
|
|
342
|
-
// Only reachable for a bundled package, externals are already evaluated against the wider set
|
|
343
|
-
if (evaluate(parsed.node, externalLicenses).allowed) {
|
|
344
|
-
result.push(issue(packageInfo, `Dependency ${packageLabel(packageInfo)} has license declaration ${displayDeclaration(declaration)} which may only be shipped as an external dependency, not built into the bundle. Add it to the externals in build-config.cjs so it is installed alongside the module and loaded at runtime.`));
|
|
345
|
-
continue;
|
|
346
|
-
}
|
|
347
|
-
if (!parsed.strict) {
|
|
348
|
-
// Reaching here means no branch we could recognise was allowed, so the unrecognised ones are the problem
|
|
349
|
-
result.push(issue(packageInfo, `Dependency ${packageLabel(packageInfo)} has license declaration ${displayDeclaration(declaration)} which is not a valid SPDX identifier, so it cannot be checked against the ${projectLicense} license policy. Ask the package author to declare a specific SPDX license.`));
|
|
350
|
-
continue;
|
|
351
|
-
}
|
|
352
|
-
result.push(issue(packageInfo, `Dependency ${packageLabel(packageInfo)} has license declaration ${displayDeclaration(declaration)} which is not compatible with the ${projectLicense} license policy.${ambiguitySuffix(evaluation)}`));
|
|
353
|
-
}
|
|
354
|
-
return result;
|
|
17
|
+
return createLicensePolicyIssuesWith(inventory, MODULE_LICENSE_POLICY, MODULE_LICENSE_OVERRIDES);
|
|
355
18
|
}
|
|
356
19
|
export function enforceLicensePolicy(inventory, options = {}) {
|
|
357
|
-
|
|
358
|
-
if (issues.length === 0)
|
|
359
|
-
return;
|
|
360
|
-
const stderr = options.stderr ?? process.stderr;
|
|
361
|
-
const prefix = options.ignoreLicenseRules ? 'LICENSE WARNING' : 'LICENSE ERROR';
|
|
362
|
-
for (const policyIssue of issues)
|
|
363
|
-
stderr.write(`${prefix}: ${policyIssue.message}\n`);
|
|
364
|
-
if (options.ignoreLicenseRules) {
|
|
365
|
-
stderr.write(`License validation ignored ${issues.length} error${issues.length === 1 ? '' : 's'} because --ignore-license-rules was provided.\n`);
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
stderr.write(`License validation failed with ${issues.length} error${issues.length === 1 ? '' : 's'}.\n`);
|
|
369
|
-
stderr.write(`${LICENSE_HELP_MESSAGE}\n`);
|
|
370
|
-
throw new LicensePolicyError(issues);
|
|
20
|
+
enforceLicensePolicyWith(inventory, MODULE_LICENSE_POLICY, MODULE_LICENSE_OVERRIDES, options);
|
|
371
21
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The platforms that every native addon in a module package must have a binary for.
|
|
3
|
+
* Note: darwin-x64 is expected to be dropped in the future
|
|
4
|
+
*/
|
|
5
|
+
export declare const REQUIRED_PREBUILD_TARGETS: readonly string[];
|
|
6
|
+
export type NativeAddonKind = 'prebuilds' | 'platform-package' | 'loose';
|
|
7
|
+
export interface NativeAddonReport {
|
|
8
|
+
/** Identifies the addon these binaries belong to */
|
|
9
|
+
key: string;
|
|
10
|
+
kind: NativeAddonKind;
|
|
11
|
+
/** The binaries found for this addon, relative to the package dir */
|
|
12
|
+
files: string[];
|
|
13
|
+
/** The platform-arch targets the binaries were compiled for */
|
|
14
|
+
targets: string[];
|
|
15
|
+
/** Required targets which no binary covers */
|
|
16
|
+
missing: string[];
|
|
17
|
+
/** Binaries whose platform could not be determined */
|
|
18
|
+
unrecognised: string[];
|
|
19
|
+
/** Advice on why this could be failing */
|
|
20
|
+
hint?: string;
|
|
21
|
+
/** Explains why some required targets are not needed */
|
|
22
|
+
note?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface PrebuildReport {
|
|
25
|
+
addons: NativeAddonReport[];
|
|
26
|
+
ok: boolean;
|
|
27
|
+
}
|
|
28
|
+
export declare class PrebuildCoverageError extends Error {
|
|
29
|
+
constructor(message: string);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Determine the platform(s) and arch(s) a native binary was compiled for, by inspecting its header.
|
|
33
|
+
* Returns targets in the form `platform-arch`, or null if the file is not a recognised binary format
|
|
34
|
+
*/
|
|
35
|
+
export declare function detectBinaryTargets(filePath: string): Promise<string[] | null>;
|
|
36
|
+
/**
|
|
37
|
+
* Find every native addon in a package and check that it has binaries for every required target
|
|
38
|
+
* @param packageDir The root of the package to check
|
|
39
|
+
* @param requiredTargets Override the targets which must be covered
|
|
40
|
+
*/
|
|
41
|
+
export declare function checkPrebuildCoverage(packageDir: string, requiredTargets?: readonly string[]): Promise<PrebuildReport>;
|
|
42
|
+
/**
|
|
43
|
+
* Format a report into human readable lines
|
|
44
|
+
*/
|
|
45
|
+
export declare function formatPrebuildReport(report: PrebuildReport): string[];
|
|
46
|
+
/**
|
|
47
|
+
* Print the result of a prebuild check, and throw if any required targets are missing
|
|
48
|
+
*/
|
|
49
|
+
export declare function enforcePrebuildCoverage(report: PrebuildReport, options?: {
|
|
50
|
+
ignorePrebuildRules?: boolean;
|
|
51
|
+
stdout?: Pick<NodeJS.WriteStream, 'write'>;
|
|
52
|
+
stderr?: Pick<NodeJS.WriteStream, 'write'>;
|
|
53
|
+
}): void;
|