licenseguard-cli 2.0.0 → 2.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 +45 -9
- package/bin/licenseguard.js +1 -0
- package/lib/commands/init-fast.js +2 -10
- package/lib/commands/init.js +3 -11
- package/lib/commands/scan.js +121 -0
- package/lib/scanner/compat-checker.js +369 -50
- package/lib/scanner/index.js +67 -115
- package/lib/scanner/license-compatibility-matrix.json +338 -0
- package/lib/scanner/license-detector.js +847 -0
- package/lib/scanner/license-normalizer.js +357 -0
- package/lib/scanner/plugins/cpp.js +267 -0
- package/lib/scanner/plugins/go.js +421 -0
- package/lib/scanner/plugins/node.js +149 -0
- package/lib/scanner/plugins/python-license-scanner.py +173 -0
- package/lib/scanner/plugins/python.js +336 -0
- package/lib/scanner/plugins/rust.js +196 -0
- package/lib/utils/license-mapper.js +28 -0
- package/package.json +2 -2
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* License compatibility checker
|
|
3
3
|
* Uses SPDX libraries for standard licenses + custom rules for non-SPDX
|
|
4
|
+
* Enhanced with authoritative compatibility matrix (FSF, Mozilla, Apache sources)
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
const satisfies = require('spdx-satisfies')
|
|
7
7
|
const parse = require('spdx-expression-parse')
|
|
8
|
+
const { normalize, areSameLicense } = require('./license-normalizer')
|
|
9
|
+
const COMPAT_MATRIX = require('./license-compatibility-matrix.json')
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Custom compatibility rules for non-SPDX licenses
|
|
@@ -19,31 +21,337 @@ const CUSTOM_COMPAT = {
|
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
|
-
*
|
|
24
|
+
* Known copyleft license patterns that impose restrictions
|
|
25
|
+
* These require derivative works to use the same license
|
|
23
26
|
*/
|
|
24
|
-
const
|
|
27
|
+
const COPYLEFT_PATTERNS = [
|
|
28
|
+
'GPL', // GNU General Public License (GPL-2.0, GPL-3.0, etc.)
|
|
29
|
+
'AGPL', // GNU Affero General Public License
|
|
30
|
+
'LGPL', // GNU Lesser General Public License
|
|
31
|
+
'MPL', // Mozilla Public License
|
|
32
|
+
'EPL', // Eclipse Public License
|
|
33
|
+
'EUPL', // European Union Public License
|
|
34
|
+
'CDDL', // Common Development and Distribution License
|
|
35
|
+
'CPL', // Common Public License
|
|
36
|
+
'APSL', // Apple Public Source License
|
|
37
|
+
'OSL', // Open Software License
|
|
38
|
+
'QPL', // Q Public License
|
|
39
|
+
'RPSL', // RealNetworks Public Source License
|
|
40
|
+
'SISSL', // Sun Industry Standards Source License
|
|
41
|
+
'SPL', // Sun Public License
|
|
42
|
+
'Watcom' // Sybase Open Watcom Public License
|
|
43
|
+
]
|
|
25
44
|
|
|
26
45
|
/**
|
|
27
|
-
*
|
|
46
|
+
* Permissive license exceptions that might contain misleading patterns
|
|
47
|
+
* These are ultra-permissive and should ALWAYS be allowed
|
|
28
48
|
*/
|
|
29
|
-
const
|
|
49
|
+
const PERMISSIVE_EXCEPTIONS = [
|
|
50
|
+
'WTFPL', // Do What The F*ck You Want To Public License
|
|
51
|
+
'Unlicense', // Public domain dedication
|
|
52
|
+
'CC0', // Creative Commons Zero (public domain)
|
|
53
|
+
'0BSD' // BSD Zero Clause (public domain equivalent)
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Detect if a license is copyleft (requires derivative works to use same license)
|
|
58
|
+
* Uses smart pattern matching instead of hardcoded whitelist
|
|
59
|
+
*
|
|
60
|
+
* @param {string} license - SPDX license identifier
|
|
61
|
+
* @returns {boolean} True if copyleft, false if permissive
|
|
62
|
+
*
|
|
63
|
+
* Algorithm:
|
|
64
|
+
* 1. Check if license is ultra-permissive exception → return false
|
|
65
|
+
* 2. Check if license contains copyleft pattern → return true
|
|
66
|
+
* 3. Default to permissive (safe assumption for ~95% of licenses)
|
|
67
|
+
*/
|
|
68
|
+
function isCopyleft(license) {
|
|
69
|
+
if (!license) return false
|
|
70
|
+
|
|
71
|
+
const upper = license.toUpperCase()
|
|
72
|
+
|
|
73
|
+
// Tier 1: Ultra-permissive exceptions (always allow)
|
|
74
|
+
if (PERMISSIVE_EXCEPTIONS.some(p => upper.includes(p.toUpperCase()))) {
|
|
75
|
+
return false
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Tier 2: Known copyleft patterns (block these)
|
|
79
|
+
if (COPYLEFT_PATTERNS.some(pattern => upper.includes(pattern))) {
|
|
80
|
+
return true
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Tier 3: Default to permissive (safe assumption)
|
|
84
|
+
// Most licenses are permissive (MIT-like, BSD-like, Apache-like)
|
|
85
|
+
return false
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Check license compatibility using authoritative compatibility matrix
|
|
90
|
+
* Handles SPDX normalization, upgrade paths, and explicit compatibility rules
|
|
91
|
+
*
|
|
92
|
+
* @param {string} projectLicense - The project's license (will be normalized)
|
|
93
|
+
* @param {string} depLicense - The dependency's license (will be normalized)
|
|
94
|
+
* @returns {{compatible: boolean, reason: string, severity: string, source: object|null}} Enhanced compatibility result
|
|
95
|
+
*
|
|
96
|
+
* Algorithm:
|
|
97
|
+
* 1. Normalize both licenses to canonical SPDX form
|
|
98
|
+
* 2. Check if same license (compatible)
|
|
99
|
+
* 3. Lookup project license in matrix
|
|
100
|
+
* 4. Check explicit compatibility/incompatibility rules
|
|
101
|
+
* 5. Check upgrade paths (LGPL→GPL, MPL→GPL)
|
|
102
|
+
* 6. Expand wildcards (*permissive*, *copyleft*)
|
|
103
|
+
* 7. Return result with severity and source citations
|
|
104
|
+
*/
|
|
105
|
+
function checkWithMatrix(projectLicense, depLicense) {
|
|
106
|
+
// Normalize licenses to canonical SPDX form
|
|
107
|
+
const normalizedProject = normalize(projectLicense)
|
|
108
|
+
const normalizedDep = normalize(depLicense)
|
|
109
|
+
|
|
110
|
+
// Same license is always compatible
|
|
111
|
+
if (areSameLicense(normalizedProject, normalizedDep)) {
|
|
112
|
+
return {
|
|
113
|
+
compatible: true,
|
|
114
|
+
reason: `Same license (${normalizedDep})`,
|
|
115
|
+
severity: 'PASS',
|
|
116
|
+
source: null
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Lookup project license in matrix
|
|
121
|
+
const projectEntry = COMPAT_MATRIX.licenses[normalizedProject]
|
|
122
|
+
|
|
123
|
+
if (!projectEntry) {
|
|
124
|
+
// Project license not in matrix - fall back to conservative check
|
|
125
|
+
return {
|
|
126
|
+
compatible: true, // Conservative: allow unknown combinations with warning
|
|
127
|
+
reason: `Unknown project license (${normalizedProject}) - unable to verify compatibility`,
|
|
128
|
+
severity: 'WARNING',
|
|
129
|
+
source: null
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Check explicit incompatibility first
|
|
134
|
+
if (projectEntry.incompatible_with) {
|
|
135
|
+
// Direct match
|
|
136
|
+
if (projectEntry.incompatible_with.includes(normalizedDep)) {
|
|
137
|
+
return {
|
|
138
|
+
compatible: false,
|
|
139
|
+
reason: `${normalizedDep} explicitly incompatible with ${normalizedProject}`,
|
|
140
|
+
severity: 'ERROR',
|
|
141
|
+
source: projectEntry.sources
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Wildcard match (*copyleft*, *permissive*)
|
|
146
|
+
for (const incompatRule of projectEntry.incompatible_with) {
|
|
147
|
+
if (incompatRule.startsWith('*') && incompatRule.endsWith('*')) {
|
|
148
|
+
const wildcardKey = incompatRule
|
|
149
|
+
if (COMPAT_MATRIX.wildcards[wildcardKey] && COMPAT_MATRIX.wildcards[wildcardKey].includes(normalizedDep)) {
|
|
150
|
+
return {
|
|
151
|
+
compatible: false,
|
|
152
|
+
reason: `${normalizedDep} (${wildcardKey.replace(/\*/g, '')}) incompatible with ${normalizedProject}`,
|
|
153
|
+
severity: 'ERROR',
|
|
154
|
+
source: projectEntry.sources
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Check explicit compatibility
|
|
162
|
+
if (projectEntry.compatible_with) {
|
|
163
|
+
// Direct match
|
|
164
|
+
if (projectEntry.compatible_with.includes(normalizedDep)) {
|
|
165
|
+
return {
|
|
166
|
+
compatible: true,
|
|
167
|
+
reason: `${normalizedDep} explicitly compatible with ${normalizedProject}`,
|
|
168
|
+
severity: 'PASS',
|
|
169
|
+
source: projectEntry.sources
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Upgrade path (LGPL→GPL, MPL→GPL)
|
|
174
|
+
if (projectEntry.can_upgrade_from && projectEntry.can_upgrade_from.includes(normalizedDep)) {
|
|
175
|
+
return {
|
|
176
|
+
compatible: true,
|
|
177
|
+
reason: `${normalizedDep} can upgrade to ${normalizedProject} (Section 3 upgrade path)`,
|
|
178
|
+
severity: 'PASS',
|
|
179
|
+
source: projectEntry.sources
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Wildcard match (*permissive*, *copyleft*, *)
|
|
184
|
+
for (const compatRule of projectEntry.compatible_with) {
|
|
185
|
+
if (compatRule === '*') {
|
|
186
|
+
// Universal compatibility (public domain)
|
|
187
|
+
return {
|
|
188
|
+
compatible: true,
|
|
189
|
+
reason: `${normalizedDep} compatible with ${normalizedProject} (public domain)`,
|
|
190
|
+
severity: 'PASS',
|
|
191
|
+
source: projectEntry.sources
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (compatRule.startsWith('*') && compatRule.endsWith('*')) {
|
|
196
|
+
const wildcardKey = compatRule
|
|
197
|
+
if (COMPAT_MATRIX.wildcards[wildcardKey] && COMPAT_MATRIX.wildcards[wildcardKey].includes(normalizedDep)) {
|
|
198
|
+
return {
|
|
199
|
+
compatible: true,
|
|
200
|
+
reason: `${normalizedDep} (${wildcardKey.replace(/\*/g, '')}) compatible with ${normalizedProject}`,
|
|
201
|
+
severity: 'PASS',
|
|
202
|
+
source: projectEntry.sources
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// No explicit rule found - conservative default
|
|
210
|
+
return {
|
|
211
|
+
compatible: true, // Conservative: allow with warning
|
|
212
|
+
reason: `No explicit compatibility rule for ${normalizedDep} + ${normalizedProject} - verify manually`,
|
|
213
|
+
severity: 'WARNING',
|
|
214
|
+
source: null
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Check compatibility of a single (atomic) license identifier
|
|
220
|
+
* Uses authoritative compatibility matrix for copyleft combinations
|
|
221
|
+
*
|
|
222
|
+
* @param {string} projectLicense - The project's license
|
|
223
|
+
* @param {string} depLicense - Single license identifier (not expression)
|
|
224
|
+
* @returns {{compatible: boolean, reason: string}} Compatibility result
|
|
225
|
+
*/
|
|
226
|
+
function checkSingleCompatibility(projectLicense, depLicense) {
|
|
227
|
+
// Normalize licenses first for accurate same-license detection
|
|
228
|
+
const normalizedProject = normalize(projectLicense)
|
|
229
|
+
const normalizedDep = normalize(depLicense)
|
|
230
|
+
|
|
231
|
+
// Same license is always compatible (handles GPL-3.0 vs GPL-3.0-only)
|
|
232
|
+
if (areSameLicense(normalizedProject, normalizedDep)) {
|
|
233
|
+
return { compatible: true, reason: `Same license (${normalizedDep})` }
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Smart copyleft detection
|
|
237
|
+
const projectIsCopyleft = isCopyleft(normalizedProject)
|
|
238
|
+
const depIsCopyleft = isCopyleft(normalizedDep)
|
|
239
|
+
|
|
240
|
+
// Case 1: Permissive project + copyleft dependency = INCOMPATIBLE
|
|
241
|
+
if (!projectIsCopyleft && depIsCopyleft) {
|
|
242
|
+
return {
|
|
243
|
+
compatible: false,
|
|
244
|
+
reason: `Copyleft license ${normalizedDep} incompatible with permissive ${normalizedProject}`
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Case 2: Copyleft project + permissive dependency
|
|
249
|
+
// IMPORTANT: Still check matrix because some permissive licenses have
|
|
250
|
+
// specific incompatibilities (e.g., Apache-2.0 + GPL-2.0 patent conflict)
|
|
251
|
+
if (projectIsCopyleft && !depIsCopyleft) {
|
|
252
|
+
const matrixResult = checkWithMatrix(normalizedProject, normalizedDep)
|
|
253
|
+
// If matrix says incompatible, trust it (handles Apache-2.0 + GPL-2.0)
|
|
254
|
+
if (!matrixResult.compatible) {
|
|
255
|
+
return {
|
|
256
|
+
compatible: false,
|
|
257
|
+
reason: matrixResult.reason
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// Otherwise, permissive dep is compatible with copyleft project
|
|
261
|
+
return {
|
|
262
|
+
compatible: true,
|
|
263
|
+
reason: 'Permissive dependency compatible with copyleft project'
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Case 3: Both permissive = COMPATIBLE
|
|
268
|
+
if (!projectIsCopyleft && !depIsCopyleft) {
|
|
269
|
+
return {
|
|
270
|
+
compatible: true,
|
|
271
|
+
reason: 'Both permissive licenses'
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Case 4: Both copyleft - USE MATRIX INSTEAD OF NAIVE LOGIC
|
|
276
|
+
// CRITICAL FIX: This replaces the naive "different copyleft = incompatible" logic
|
|
277
|
+
if (projectIsCopyleft && depIsCopyleft) {
|
|
278
|
+
const matrixResult = checkWithMatrix(normalizedProject, normalizedDep)
|
|
279
|
+
// Return simplified result (remove severity/source for backward compatibility)
|
|
280
|
+
return {
|
|
281
|
+
compatible: matrixResult.compatible,
|
|
282
|
+
reason: matrixResult.reason
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Fallback
|
|
287
|
+
return { compatible: true, reason: 'No known incompatibility' }
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Recursively evaluate SPDX expression AST for compatibility
|
|
292
|
+
* Handles OR (disjunctive) and AND (conjunctive) expressions
|
|
293
|
+
*
|
|
294
|
+
* @param {string} projectLicense - The project's license
|
|
295
|
+
* @param {Object} licenseNode - AST node from spdx-expression-parse
|
|
296
|
+
* @returns {{compatible: boolean, reason: string}} Compatibility result
|
|
297
|
+
*/
|
|
298
|
+
function isCompatibleRecursive(projectLicense, licenseNode) {
|
|
299
|
+
// Base Case: Leaf node with 'license' property
|
|
300
|
+
if (licenseNode.license) {
|
|
301
|
+
return checkSingleCompatibility(projectLicense, licenseNode.license)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Handle "OR" (Disjunctive) - User can choose either
|
|
305
|
+
if (licenseNode.conjunction === 'or') {
|
|
306
|
+
const left = isCompatibleRecursive(projectLicense, licenseNode.left)
|
|
307
|
+
const right = isCompatibleRecursive(projectLicense, licenseNode.right)
|
|
308
|
+
|
|
309
|
+
// If either is compatible, the whole expression is compatible
|
|
310
|
+
if (left.compatible) return left
|
|
311
|
+
if (right.compatible) return right
|
|
312
|
+
|
|
313
|
+
// Both incompatible - return left's reason
|
|
314
|
+
return { compatible: false, reason: left.reason }
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Handle "AND" (Conjunctive) - Must satisfy both
|
|
318
|
+
if (licenseNode.conjunction === 'and') {
|
|
319
|
+
const left = isCompatibleRecursive(projectLicense, licenseNode.left)
|
|
320
|
+
const right = isCompatibleRecursive(projectLicense, licenseNode.right)
|
|
321
|
+
|
|
322
|
+
// Both must be compatible
|
|
323
|
+
if (!left.compatible) {
|
|
324
|
+
return { compatible: false, reason: `Part of AND expression failed: ${left.reason}` }
|
|
325
|
+
}
|
|
326
|
+
if (!right.compatible) {
|
|
327
|
+
return { compatible: false, reason: `Part of AND expression failed: ${right.reason}` }
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return { compatible: true, reason: 'All licenses in AND expression are compatible' }
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Unknown structure
|
|
334
|
+
return { compatible: false, reason: 'Unknown license expression structure' }
|
|
335
|
+
}
|
|
30
336
|
|
|
31
337
|
/**
|
|
32
338
|
* Check if two licenses are compatible
|
|
339
|
+
* Handles complex SPDX expressions with OR/AND
|
|
340
|
+
*
|
|
33
341
|
* @param {string} projectLicense - The project's license
|
|
34
|
-
* @param {string} depLicense - The dependency's license
|
|
342
|
+
* @param {string} depLicense - The dependency's license (can be SPDX expression)
|
|
35
343
|
* @returns {{compatible: boolean, reason: string}} Compatibility result
|
|
36
344
|
*/
|
|
37
345
|
function checkCompatibility(projectLicense, depLicense) {
|
|
38
346
|
// Handle unknown licenses
|
|
39
347
|
if (depLicense === 'UNKNOWN' || !depLicense) {
|
|
40
348
|
return {
|
|
41
|
-
compatible: false,
|
|
349
|
+
compatible: false,
|
|
42
350
|
reason: 'No license field found'
|
|
43
351
|
}
|
|
44
352
|
}
|
|
45
353
|
|
|
46
|
-
// Handle custom licenses (WTFPL,
|
|
354
|
+
// Handle custom licenses (WTFPL, etc.)
|
|
47
355
|
const depLowerCase = depLicense.toLowerCase()
|
|
48
356
|
if (CUSTOM_COMPAT[depLowerCase]) {
|
|
49
357
|
if (CUSTOM_COMPAT[depLowerCase].compatibleWith === '*') {
|
|
@@ -51,64 +359,75 @@ function checkCompatibility(projectLicense, depLicense) {
|
|
|
51
359
|
}
|
|
52
360
|
}
|
|
53
361
|
|
|
54
|
-
//
|
|
362
|
+
// Parse SPDX expression into AST
|
|
363
|
+
let ast
|
|
55
364
|
try {
|
|
56
|
-
parse(
|
|
57
|
-
parse(depLicense)
|
|
365
|
+
ast = parse(depLicense)
|
|
58
366
|
} catch (error) {
|
|
59
|
-
// Invalid SPDX expression
|
|
60
367
|
return {
|
|
61
368
|
compatible: false,
|
|
62
369
|
reason: `Invalid SPDX expression: ${depLicense}`
|
|
63
370
|
}
|
|
64
371
|
}
|
|
65
372
|
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Check if both are permissive licenses
|
|
72
|
-
const projectIsPermissive = PERMISSIVE_LICENSES.includes(projectLicense)
|
|
73
|
-
const depIsPermissive = PERMISSIVE_LICENSES.includes(depLicense)
|
|
373
|
+
// Evaluate AST recursively
|
|
374
|
+
return isCompatibleRecursive(projectLicense, ast)
|
|
375
|
+
}
|
|
74
376
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
377
|
+
/**
|
|
378
|
+
* Format compatibility result with source citations for --explain flag
|
|
379
|
+
* Shows authoritative sources (FSF, Mozilla, Apache) and URLs
|
|
380
|
+
*
|
|
381
|
+
* @param {string} projectLicense - The project's license
|
|
382
|
+
* @param {string} depLicense - The dependency's license
|
|
383
|
+
* @returns {string} Formatted explanation with sources
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* explainCompatibility('GPL-3.0', 'LGPL-2.1-or-later')
|
|
387
|
+
* // Returns:
|
|
388
|
+
* // "✅ Compatible: LGPL-2.1-or-later can upgrade to GPL-3.0-only (Section 3 upgrade path)
|
|
389
|
+
* //
|
|
390
|
+
* // Source: LGPL Section 3: Can upgrade to corresponding GPL version
|
|
391
|
+
* // URL: https://www.gnu.org/licenses/lgpl-3.0.html#section3"
|
|
392
|
+
*/
|
|
393
|
+
function explainCompatibility(projectLicense, depLicense) {
|
|
394
|
+
const result = checkWithMatrix(projectLicense, depLicense)
|
|
79
395
|
|
|
80
|
-
|
|
81
|
-
const depIsCopyleft = COPYLEFT_LICENSES.some(lic => depLicense.includes(lic))
|
|
396
|
+
let explanation = ''
|
|
82
397
|
|
|
83
|
-
//
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
398
|
+
// Status emoji
|
|
399
|
+
if (result.compatible && result.severity === 'PASS') {
|
|
400
|
+
explanation += '✅ Compatible: '
|
|
401
|
+
} else if (result.compatible && result.severity === 'WARNING') {
|
|
402
|
+
explanation += '⚠️ Warning: '
|
|
403
|
+
} else {
|
|
404
|
+
explanation += '❌ Incompatible: '
|
|
89
405
|
}
|
|
90
406
|
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
if (projectIsCopyleft && depIsPermissive) {
|
|
94
|
-
return { compatible: true, reason: 'Compatible' }
|
|
95
|
-
}
|
|
407
|
+
// Reason
|
|
408
|
+
explanation += result.reason
|
|
96
409
|
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
410
|
+
// Source citations (if available)
|
|
411
|
+
if (result.source && result.source.citation) {
|
|
412
|
+
explanation += '\n\n'
|
|
413
|
+
explanation += `📚 Source: ${result.source.citation}`
|
|
414
|
+
if (result.source.url) {
|
|
415
|
+
explanation += `\n🔗 URL: ${result.source.url}`
|
|
102
416
|
}
|
|
103
|
-
} catch (error) {
|
|
104
|
-
// Fall through to default incompatible
|
|
105
417
|
}
|
|
106
418
|
|
|
107
|
-
|
|
108
|
-
return {
|
|
109
|
-
compatible: false,
|
|
110
|
-
reason: `License ${depLicense} incompatible with ${projectLicense}`
|
|
111
|
-
}
|
|
419
|
+
return explanation
|
|
112
420
|
}
|
|
113
421
|
|
|
114
|
-
module.exports = {
|
|
422
|
+
module.exports = {
|
|
423
|
+
checkCompatibility,
|
|
424
|
+
checkSingleCompatibility,
|
|
425
|
+
isCompatibleRecursive,
|
|
426
|
+
isCopyleft,
|
|
427
|
+
checkWithMatrix,
|
|
428
|
+
explainCompatibility,
|
|
429
|
+
CUSTOM_COMPAT,
|
|
430
|
+
COPYLEFT_PATTERNS,
|
|
431
|
+
PERMISSIVE_EXCEPTIONS,
|
|
432
|
+
COMPAT_MATRIX
|
|
433
|
+
}
|