@@ -10,34 +10,6 @@ export { getProjectLicense, getProjectLicenseFromManifest, findLicenseFilePath,
1010export { licenseMapFromAnalysisReport , normalizeLicensesResponse , getLicensesByPurl , getLicenseDetails } from './licenses_api.js' ;
1111export { getCompatibility } from './compatibility.js' ;
1212
13- /**
14- * Extract all component purls from a CycloneDX SBOM JSON string (excluding root if desired).
15- * @param {string } sbomContent - CycloneDX JSON string
16- * @param {boolean } [excludeRoot=true] - if true, exclude the root component's purl from the list
17- * @returns {string[] }
18- */
19- export function extractPurlsFromSbom ( sbomContent , excludeRoot = true ) {
20- let obj ;
21- try {
22- obj = typeof sbomContent === 'string' ? JSON . parse ( sbomContent ) : sbomContent ;
23- } catch {
24- return [ ] ;
25- }
26- const components = obj ?. components ;
27- if ( ! Array . isArray ( components ) ) return [ ] ;
28-
29- const rootRef = obj ?. metadata ?. component ?. [ "bom-ref" ] ?? obj ?. metadata ?. component ?. purl ;
30- const purls = components
31- . map ( c => c . purl || c [ "bom-ref" ] || c . bomRef )
32- . filter ( Boolean ) ;
33-
34- if ( excludeRoot && rootRef ) {
35- const rootPurl = typeof rootRef === 'string' ? rootRef : rootRef ?. purl ;
36- return purls . filter ( p => p !== rootPurl ) ;
37- }
38- return purls ;
39- }
40-
4113/**
4214 * Run full license check: resolve project license (with backend identification and details),
4315 * get dependency licenses from analysis report, and compute incompatibilities.
@@ -50,80 +22,77 @@ export function extractPurlsFromSbom(sbomContent, excludeRoot = true) {
5022 * @returns {Promise<{ projectLicense: { manifest: Object|null, file: Object|null, mismatch: boolean }, incompatibleDependencies: Array<{ purl: string, licenses: string[], category?: string, reason: string }>, error?: string }> }
5123 */
5224export async function runLicenseCheck ( sbomContent , manifestPath , backendUrl , opts = { } , analysisResult = null ) {
53- // Get project license from manifest (always available)
25+ // Resolve project license from manifest and LICENSE file
5426 const projectLicense = getProjectLicense ( manifestPath , opts ) ;
5527
56- // Try to get more accurate LICENSE file identification from backend
57- let projectLicenseFromFileBackend = null ;
28+ // Try backend identification for LICENSE file (more accurate than local pattern matching)
5829 const licenseFilePath = findLicenseFilePath ( manifestPath ) ;
30+ let backendFileId = null ;
5931 if ( licenseFilePath && backendUrl ) {
6032 try {
61- projectLicenseFromFileBackend = await identifyLicense ( licenseFilePath , backendUrl , opts ) ;
33+ backendFileId = await identifyLicense ( licenseFilePath , backendUrl , opts ) ;
6234 } catch {
63- // Fall back to local detection (already in projectLicense.fromFile)
35+ // Fall back to local detection
6436 }
6537 }
6638
67- // Use backend identification if available, otherwise use local detection
68- const finalFromFile = projectLicenseFromFileBackend || projectLicense . fromFile ;
69- const finalMismatch = Boolean (
70- projectLicense . fromManifest && finalFromFile &&
71- projectLicense . fromManifest . toLowerCase ( ) !== finalFromFile . toLowerCase ( )
72- ) ;
39+ // Determine final license identifiers
40+ const manifestSpdx = projectLicense . fromManifest ;
41+ const fileSpdx = backendFileId || projectLicense . fromFile ;
42+ const mismatch = Boolean ( manifestSpdx && fileSpdx && manifestSpdx . toLowerCase ( ) !== fileSpdx . toLowerCase ( ) ) ;
7343
74- // Fetch detailed license info from backend for both manifest and file licenses
75- let manifestLicenseInfo = null ;
76- let fileLicenseInfo = null ;
44+ // Fetch detailed license info from backend (avoid duplicate calls if same license)
45+ const licenseDetailsCache = new Map ( ) ;
7746
78- if ( projectLicense . fromManifest && backendUrl ) {
79- try {
80- manifestLicenseInfo = await getLicenseDetails ( projectLicense . fromManifest , backendUrl , opts ) ;
81- } catch {
82- // Backend might not have this license; keep as null
83- }
84- }
47+ async function getDetails ( spdxId ) {
48+ if ( ! spdxId || ! backendUrl ) return null ;
49+ if ( licenseDetailsCache . has ( spdxId ) ) return licenseDetailsCache . get ( spdxId ) ;
8550
86- if ( finalFromFile && backendUrl ) {
8751 try {
88- fileLicenseInfo = await getLicenseDetails ( finalFromFile , backendUrl , opts ) ;
52+ const details = await getLicenseDetails ( spdxId , backendUrl , opts ) ;
53+ licenseDetailsCache . set ( spdxId , details ) ;
54+ return details ;
8955 } catch {
90- // Backend might not have this license; keep as null
56+ return null ;
9157 }
9258 }
9359
94- const purls = extractPurlsFromSbom ( sbomContent , true ) ;
60+ const manifestLicenseInfo = await getDetails ( manifestSpdx ) ;
61+ const fileLicenseInfo = await getDetails ( fileSpdx ) ;
62+
63+ // Extract dependency purls from SBOM (exclude root component)
64+ const sbomObj = typeof sbomContent === 'string' ? JSON . parse ( sbomContent ) : sbomContent ;
65+ const rootRef = sbomObj ?. metadata ?. component ?. [ "bom-ref" ] || sbomObj ?. metadata ?. component ?. purl ;
66+ const purls = ( sbomObj ?. components || [ ] )
67+ . map ( c => c . purl || c [ "bom-ref" ] )
68+ . filter ( Boolean )
69+ . filter ( purl => ! rootRef || purl !== rootRef ) ;
70+
9571 if ( purls . length === 0 ) {
9672 return {
97- projectLicense : {
98- manifest : manifestLicenseInfo ,
99- file : fileLicenseInfo ,
100- mismatch : finalMismatch
101- } ,
73+ projectLicense : { manifest : manifestLicenseInfo , file : fileLicenseInfo , mismatch } ,
10274 incompatibleDependencies : [ ]
10375 } ;
10476 }
10577
106- // Get dependency licenses from analysis report (backend already provides this)
78+ // Get dependency licenses from analysis report
10779 const licenseByPurl = licenseMapFromAnalysisReport ( analysisResult , purls ) ;
10880 if ( licenseByPurl . size === 0 && analysisResult ) {
109- // No license data in analysis report - this might be expected for some backends
11081 return {
111- projectLicense : {
112- manifest : manifestLicenseInfo ,
113- file : fileLicenseInfo ,
114- mismatch : finalMismatch
115- } ,
82+ projectLicense : { manifest : manifestLicenseInfo , file : fileLicenseInfo , mismatch } ,
11683 incompatibleDependencies : [ ] ,
11784 error : 'No license data available in analysis report'
11885 } ;
11986 }
12087
121- // Use backend category from project license details (prefer manifest, fallback to file)
122- const projectCategory = manifestLicenseInfo ?. category || fileLicenseInfo ?. category || null ;
88+ // Check compatibility for each dependency
89+ const projectCategory = manifestLicenseInfo ?. category || fileLicenseInfo ?. category ;
12390 const incompatibleDependencies = [ ] ;
12491
12592 for ( const purl of purls ) {
126- const entry = licenseByPurl . get ( purl ) || { licenses : [ ] , category : undefined } ;
93+ const entry = licenseByPurl . get ( purl ) ;
94+ if ( ! entry ) continue ;
95+
12796 const status = getCompatibility ( projectCategory , entry . category ) ;
12897 if ( status === 'incompatible' ) {
12998 incompatibleDependencies . push ( {
@@ -136,11 +105,7 @@ export async function runLicenseCheck(sbomContent, manifestPath, backendUrl, opt
136105 }
137106
138107 return {
139- projectLicense : {
140- manifest : manifestLicenseInfo ,
141- file : fileLicenseInfo ,
142- mismatch : finalMismatch
143- } ,
108+ projectLicense : { manifest : manifestLicenseInfo , file : fileLicenseInfo , mismatch } ,
144109 incompatibleDependencies
145110 } ;
146111}
0 commit comments