@@ -69,8 +69,148 @@ export async function analyze({ ctx, itemIndex }: { ctx: IExecuteFunctions; item
6969 } ;
7070
7171 try {
72- const res = await authedRequest ( ctx , credentialName , options ) ;
73- return [ { json : res } as INodeExecutionData ] ;
72+ const res = ( await authedRequest ( ctx , credentialName , options ) ) as any ;
73+
74+ const rules : SortRule [ ] = readSortRules ( ctx , itemIndex , 'vulnerability' ) ;
75+ const mode = ctx . getNodeParameter ( 'outputMode' , itemIndex , 'simplified' ) as
76+ | 'simplified'
77+ | 'raw'
78+ | 'selected' ;
79+
80+ // Build enriched advisories with vulnerability fields and originating package (purl)
81+ const enrichedAdvisories : any [ ] = [ ] ;
82+
83+ const isPlainObject = ( v : any ) => v && typeof v === 'object' && ! Array . isArray ( v ) ;
84+ if ( isPlainObject ( res ) ) {
85+ // Prefer the map keyed by PURL shape: { [purl]: { details: [...] } }
86+ for ( const [ pkgPurl , data ] of Object . entries ( res as Record < string , any > ) ) {
87+ // Support both shapes:
88+ // 1) { details: [...] }
89+ // 2) [ { ...vulnerabilityDetail... }, ... ]
90+ const details = Array . isArray ( data )
91+ ? ( data as any [ ] )
92+ : Array . isArray ( ( data as any ) ?. details )
93+ ? ( data as any ) . details
94+ : [ ] ;
95+ if ( details . length === 0 && Array . isArray ( ( data as any ) ?. advisories ) ) {
96+ // Fallback: direct advisories array
97+ for ( const adv of ( data as any ) . advisories as any [ ] ) {
98+ enrichedAdvisories . push ( { ...adv , package : pkgPurl } ) ;
99+ }
100+ }
101+ for ( const d of details ) {
102+ const affected = ( d as any ) ?. status ?. affected ;
103+ if ( Array . isArray ( affected ) ) {
104+ for ( const adv of affected ) {
105+ // Merge advisory with vulnerability-level fields and the originating package
106+ enrichedAdvisories . push ( {
107+ ...adv ,
108+ package : pkgPurl ,
109+ normative : ( d as any ) ?. normative ?? ( d as any ) ?. status ?. normative ?? null ,
110+ identifier : ( d as any ) ?. identifier ?? adv ?. identifier ?? null ,
111+ title : ( d as any ) ?. title ?? adv ?. title ?? null ,
112+ description : ( d as any ) ?. description ?? null ,
113+ reserved : ( d as any ) ?. reserved ?? null ,
114+ published : ( d as any ) ?. published ?? adv ?. published ?? null ,
115+ modified : ( d as any ) ?. modified ?? adv ?. modified ?? null ,
116+ withdrawn : ( d as any ) ?. withdrawn ?? adv ?. withdrawn ?? null ,
117+ discovered : ( d as any ) ?. discovered ?? null ,
118+ released : ( d as any ) ?. released ?? null ,
119+ cwes : ( d as any ) ?. cwes ?? adv ?. cwes ?? null ,
120+ status : ( d as any ) ?. status ?? null ,
121+ } ) ;
122+ }
123+ }
124+ }
125+ }
126+ } else {
127+ // Fallback to previous generic flattening while trying to retain package
128+ const buckets : any [ ] = Array . isArray ( res )
129+ ? res
130+ : Array . isArray ( res ?. items )
131+ ? res . items
132+ : Array . isArray ( res ?. vulnerabilities )
133+ ? res . vulnerabilities
134+ : [ res ] ;
135+
136+ for ( const bucket of buckets ) {
137+ if ( bucket && typeof bucket === 'object' ) {
138+ if ( Array . isArray ( ( bucket as any ) . advisories ) ) {
139+ for ( const adv of ( bucket as any ) . advisories as any [ ] ) {
140+ const pkg = ( adv as any ) ?. packages ?. [ 0 ] ?. purl ?? null ;
141+ enrichedAdvisories . push ( { ...adv , package : pkg } ) ;
142+ }
143+ continue ;
144+ }
145+ const candidates : any [ ] = [ ] ;
146+ if ( Array . isArray ( ( bucket as any ) . details ) ) candidates . push ( bucket ) ;
147+ for ( const v of Object . values ( bucket ) ) {
148+ if ( v && typeof v === 'object' ) candidates . push ( v ) ;
149+ }
150+ for ( const cand of candidates ) {
151+ const details = Array . isArray ( ( cand as any ) ?. details ) ? ( cand as any ) . details : [ ] ;
152+ for ( const d of details ) {
153+ const affected = ( d as any ) ?. status ?. affected ;
154+ if ( Array . isArray ( affected ) ) {
155+ for ( const adv of affected ) {
156+ const pkg =
157+ ( d as any ) ?. status ?. packages ?. [ 0 ] ?. purl ??
158+ ( adv as any ) ?. packages ?. [ 0 ] ?. purl ??
159+ null ;
160+ enrichedAdvisories . push ( {
161+ ...adv ,
162+ package : pkg ,
163+ normative : ( d as any ) ?. normative ?? ( d as any ) ?. status ?. normative ?? null ,
164+ identifier : ( d as any ) ?. identifier ?? adv ?. identifier ?? null ,
165+ title : ( d as any ) ?. title ?? adv ?. title ?? null ,
166+ description : ( d as any ) ?. description ?? null ,
167+ reserved : ( d as any ) ?. reserved ?? null ,
168+ published : ( d as any ) ?. published ?? adv ?. published ?? null ,
169+ modified : ( d as any ) ?. modified ?? adv ?. modified ?? null ,
170+ withdrawn : ( d as any ) ?. withdrawn ?? adv ?. withdrawn ?? null ,
171+ discovered : ( d as any ) ?. discovered ?? null ,
172+ released : ( d as any ) ?. released ?? null ,
173+ cwes : ( d as any ) ?. cwes ?? adv ?. cwes ?? null ,
174+ status : ( d as any ) ?. status ?? null ,
175+ } ) ;
176+ }
177+ }
178+ }
179+ }
180+ }
181+ }
182+ }
183+
184+ let out = enrichedAdvisories ;
185+
186+ if ( rules . length ) out = [ ...out ] . sort ( ( a , b ) => multiCmp ( a , b , rules ) ) ;
187+
188+ const limit = ( ctx . getNodeParameter ( 'limit' , itemIndex , 50 ) as number ) || 50 ;
189+ out = out . slice ( 0 , limit ) ;
190+
191+ // RAW mode: return flattened advisories under a consistent key
192+ if ( mode === 'raw' ) {
193+ return [ { json : { advisories : out } } as INodeExecutionData ] ;
194+ }
195+
196+ // Simplified: project minimal advisory fields locally to avoid advisory-specific simplifier
197+ if ( mode === 'simplified' ) {
198+ const simplified = out . map ( ( a : any ) => ( {
199+ uuid : a ?. uuid ?? null ,
200+ normative : a ?. normative ?? null ,
201+ identifier : a ?. identifier ?? null ,
202+ document_id : a ?. document_id ?? null ,
203+ package : a ?. package ?? null ,
204+ title : a ?. title ?? null ,
205+ description : a ?. description ?? null ,
206+ score : a ?. score ?? null ,
207+ } ) ) ;
208+ return [ { json : { advisories : simplified } } as INodeExecutionData ] ;
209+ }
210+
211+ // Selected: use generic advisory shaper
212+ const finalItems = out . map ( ( it ) => shapeOutput ( ctx , itemIndex , 'advisory' , it ) ) ;
213+ return [ { json : { advisories : finalItems } } as INodeExecutionData ] ;
74214 } catch ( err : any ) {
75215 if ( ctx . continueOnFail ( ) ) {
76216 return [ { json : { message : err . message , request : { purls } } } as INodeExecutionData ] ;
@@ -103,7 +243,65 @@ export async function analyze({ ctx, itemIndex }: { ctx: IExecuteFunctions; item
103243 returnFullResponse : false ,
104244 } ;
105245
106- const advisories = await authedRequest ( ctx , credentialName , advOpts ) ;
246+ const advisories = ( await authedRequest ( ctx , credentialName , advOpts ) ) as any ;
247+
248+ const rules : SortRule [ ] = readSortRules ( ctx , itemIndex , 'vulnerability' ) ;
249+ let out = advisories ;
250+ if ( Array . isArray ( advisories ) && rules . length ) {
251+ out = [ ...advisories ] . sort ( ( a , b ) => multiCmp ( a , b , rules ) ) ;
252+ }
253+
254+ const limit = ( ctx . getNodeParameter ( 'limit' , itemIndex , 50 ) as number ) || 50 ;
255+ if ( Array . isArray ( out ) ) out = out . slice ( 0 , limit ) ;
256+
257+ const mode = ctx . getNodeParameter ( 'outputMode' , itemIndex , 'simplified' ) as
258+ | 'simplified'
259+ | 'raw'
260+ | 'selected' ;
261+
262+ // RAW mode: return advisories as-is under a consistent key
263+ if ( mode === 'raw' ) {
264+ return [
265+ { json : { sbomId, advisories : Array . isArray ( out ) ? out : [ out ] } } as INodeExecutionData ,
266+ ] ;
267+ }
268+
269+ // Simplified mode: custom sbom-sha advisory projection per user requirements
270+ if ( mode === 'simplified' ) {
271+ const simplified = ( Array . isArray ( out ) ? out : [ out ] ) . map ( ( item : any ) => {
272+ const toScores = ( scores : any ) => ( Array . isArray ( scores ) ? scores : null ) ;
273+ const statusArr = Array . isArray ( item ?. status )
274+ ? item . status . map ( ( s : any ) => ( {
275+ normative : s ?. normative ?? null ,
276+ identifier : item ?. identifier ?? null ,
277+ title : s ?. title ?? null ,
278+ description : s ?. description ?? null ,
279+ averageSeverity : s ?. average_severity ?? null ,
280+ averageScore : s ?. average_score ?? null ,
281+ status : s ?. status ?? null ,
282+ packages : s ?. packages ?? null ,
283+ score : toScores ( item ?. scores ) ?? toScores ( s ?. scores ) ,
284+ } ) )
285+ : [ ] ;
286+ return {
287+ uuid : item ?. uuid ?? null ,
288+ identifier : item ?. identifier ?? null ,
289+ title : item ?. title ?? null ,
290+ status : statusArr ,
291+ } ;
292+ } ) ;
293+
294+ return [ { json : { sbomId, advisories : simplified } } as INodeExecutionData ] ;
295+ }
296+
297+ // Selected fields mode: reuse normal advisory shaping
298+ const shaped = Array . isArray ( out )
299+ ? out . map ( ( it : any ) => shapeOutput ( ctx , itemIndex , 'advisory' , it ) )
300+ : shapeOutput ( ctx , itemIndex , 'advisory' , out ) ;
107301
108- return [ { json : { sbomId, advisories } } as INodeExecutionData ] ;
302+ return [
303+ {
304+ json : { sbomId, advisories : Array . isArray ( shaped ) ? shaped : [ shaped ] } ,
305+ } as INodeExecutionData ,
306+ ] ;
109307}
0 commit comments