@@ -133,25 +133,40 @@ def create_sbom_output(self, diff: Diff) -> dict:
133133 @staticmethod
134134 def expand_brace_pattern (pattern : str ) -> List [str ]:
135135 """
136- Expands brace expressions (e.g., {a,b,c}) into separate patterns.
137- """
138- brace_regex = re .compile (r"\{([^{}]+)\}" )
139- 140- # Expand all brace groups
141- expanded_patterns = [pattern ]
142- while any ("{" in p for p in expanded_patterns ):
143- new_patterns = []
144- for pat in expanded_patterns :
145- match = brace_regex .search (pat )
146- if match :
147- options = match .group (1 ).split ("," ) # Extract values inside {}
148- prefix , suffix = pat [:match .start ()], pat [match .end ():]
149- new_patterns .extend ([prefix + opt + suffix for opt in options ])
150- else :
151- new_patterns .append (pat )
152- expanded_patterns = new_patterns
153- 154- return expanded_patterns
136+ Recursively expands brace expressions (e.g., {a,b,c}) into separate patterns, supporting nested braces.
137+ """
138+ def recursive_expand (pat : str ) -> List [str ]:
139+ stack = []
140+ for i , c in enumerate (pat ):
141+ if c == '{' :
142+ stack .append (i )
143+ elif c == '}' and stack :
144+ start = stack .pop ()
145+ if not stack :
146+ # Found the outermost pair
147+ before = pat [:start ]
148+ after = pat [i + 1 :]
149+ inner = pat [start + 1 :i ]
150+ # Split on commas not inside nested braces
151+ options = []
152+ depth = 0
153+ last = 0
154+ for j , ch in enumerate (inner ):
155+ if ch == '{' :
156+ depth += 1
157+ elif ch == '}' :
158+ depth -= 1
159+ elif ch == ',' and depth == 0 :
160+ options .append (inner [last :j ])
161+ last = j + 1
162+ options .append (inner [last :])
163+ results = []
164+ for opt in options :
165+ expanded = before + opt + after
166+ results .extend (recursive_expand (expanded ))
167+ return results
168+ return [pat ]
169+ return recursive_expand (pattern )
155170
156171 @staticmethod
157172 def is_excluded (file_path : str , excluded_dirs : Set [str ]) -> bool :
@@ -176,13 +191,7 @@ def find_files(self, path: str) -> List[str]:
176191 files : Set [str ] = set ()
177192
178193 # Get supported patterns from the API
179- try :
180- patterns = self .get_supported_patterns ()
181- except Exception as e :
182- log .error (f"Error getting supported patterns from API: { e } " )
183- log .warning ("Falling back to local patterns" )
184- from .utils import socket_globs as fallback_patterns
185- patterns = fallback_patterns
194+ patterns = self .get_supported_patterns ()
186195
187196 for ecosystem in patterns :
188197 if ecosystem in self .config .excluded_ecosystems :
@@ -425,7 +434,7 @@ def create_packages_dict(self, sbom_artifacts: list[SocketArtifact]) -> dict[str
425434 packages = {}
426435 top_level_count = {}
427436 for artifact in sbom_artifacts :
428- package = Package .from_diff_artifact ( artifact . __dict__ )
437+ package = Package .from_socket_artifact ( asdict ( artifact ) )
429438 if package .id in packages :
430439 print ("Duplicate package?" )
431440 else :
@@ -534,44 +543,22 @@ def update_package_values(pkg: Package) -> Package:
534543 pkg .url += f"/{ pkg .name } /overview/{ pkg .version } "
535544 return pkg
536545
537- def get_added_and_removed_packages (
538- self ,
539- head_full_scan_id : str ,
540- new_full_scan_id : str ,
541- merge : bool = False ,
542- external_href : str = None ,
543- ) -> Tuple [Dict [str , Package ], Dict [str , Package ], str , str ]:
546+ def get_added_and_removed_packages (self , head_full_scan_id : str , new_full_scan_id : str ) -> Tuple [Dict [str , Package ], Dict [str , Package ]]:
544547 """
545548 Get packages that were added and removed between scans.
546549
547550 Args:
548- head_full_scan_id: Previous scan
549- new_full_scan_id: New scan just created
550- merge: Whether the scan is merged into the default branch
551- external_href: External reference
551+ head_full_scan: Previous scan (may be None if first scan)
552+ head_full_scan_id: New scan just created
553+
552554 Returns:
553555 Tuple of (added_packages, removed_packages) dictionaries
554556 """
555557
556558 log .info (f"Comparing scans - Head scan ID: { head_full_scan_id } , New scan ID: { new_full_scan_id } " )
557559 diff_start = time .time ()
558560 try :
559- params = {
560- "before" : head_full_scan_id ,
561- "after" : new_full_scan_id ,
562- "description" : f"Diff scan between head { head_full_scan_id } and new { new_full_scan_id } scans" ,
563- "merge" : merge ,
564- }
565- if external_href :
566- params ["external_href" ] = external_href
567- new_diff_scan = self .sdk .diffscans .create_from_ids (self .config .org_slug , params )
568- data = new_diff_scan .get ("diff_scan" , {})
569- diff_scan_id = data .get ("id" )
570- if not diff_scan_id :
571- log .error (f"Failed to get diff scan ID for { new_full_scan_id } " )
572- log .error (new_diff_scan )
573- sys .exit (1 )
574- diff_report = self .sdk .diffscans .get (self .config .org_slug , diff_scan_id )
561+ diff_report = self .sdk .fullscans .stream_diff (self .config .org_slug , head_full_scan_id , new_full_scan_id , use_types = True ).data
575562 except APIFailure as e :
576563 log .error (f"API Error: { e } " )
577564 sys .exit (1 )
@@ -581,63 +568,44 @@ def get_added_and_removed_packages(
581568 log .error (f"Stack trace:\n { traceback .format_exc ()} " )
582569 raise
583570
584- diff_data = diff_report .get ("diff_scan" , {})
585571 diff_end = time .time ()
586- diff_url = diff_data .get ("html_url" )
587- after_data = diff_data .get ("after_full_scan" )
588- if after_data :
589- new_full_scan_url = after_data .get ("html_url" )
590- else :
591- new_full_scan_url = ""
592- artifacts = diff_data .get ("artifacts" , {})
593- added = artifacts .get ("added" , [])
594- removed = artifacts .get ("removed" , [])
595- unchanged = artifacts .get ("unchanged" , [])
596- replaced = artifacts .get ("replaced" , [])
597- updated = artifacts .get ("updated" , [])
598572 log .info (f"Diff Report Gathered in { diff_end - diff_start :.2f} seconds" )
599573 log .info ("Diff report artifact counts:" )
600- log .info (f"Added: { len (added )} " )
601- log .info (f"Removed: { len (removed )} " )
602- log .info (f"Unchanged: { len (unchanged )} " )
603- log .info (f"Replaced: { len (replaced )} " )
604- log .info (f"Updated: { len (updated )} " )
574+ log .info (f"Added: { len (diff_report . artifacts . added )} " )
575+ log .info (f"Removed: { len (diff_report . artifacts . removed )} " )
576+ log .info (f"Unchanged: { len (diff_report . artifacts . unchanged )} " )
577+ log .info (f"Replaced: { len (diff_report . artifacts . replaced )} " )
578+ log .info (f"Updated: { len (diff_report . artifacts . updated )} " )
605579
606- added_artifacts = added + updated
607- removed_artifacts = removed
580+ added_artifacts = diff_report . artifacts . added + diff_report . artifacts . updated
581+ removed_artifacts = diff_report . artifacts . removed + diff_report . artifacts . replaced
608582
609583 added_packages : Dict [str , Package ] = {}
610584 removed_packages : Dict [str , Package ] = {}
611585
612586 for artifact in added_artifacts :
613- artifact_id = artifact .get ("id" )
614- artifact_name = artifact .get ("name" )
615- artifact_version = artifact .get ("version" )
616587 try :
617- pkg = Package .from_diff_artifact (artifact )
588+ pkg = Package .from_diff_artifact (asdict ( artifact ) )
618589 pkg = Core .update_package_values (pkg )
619- added_packages [pkg .id ] = pkg
590+ added_packages [artifact .id ] = pkg
620591 except KeyError :
621- log .error (f"KeyError: Could not create package from added artifact { artifact_id } " )
622- log .error (f"Artifact details - name: { artifact_name } , version: { artifact_version } " )
592+ log .error (f"KeyError: Could not create package from added artifact { artifact . id } " )
593+ log .error (f"Artifact details - name: { artifact . name } , version: { artifact . version } " )
623594 log .error ("No matching packages found in new_full_scan" )
624595
625596 for artifact in removed_artifacts :
626- artifact_id = artifact .get ("id" )
627- artifact_name = artifact .get ("name" )
628- artifact_version = artifact .get ("version" )
629597 try :
630- pkg = Package .from_diff_artifact (artifact )
598+ pkg = Package .from_diff_artifact (asdict ( artifact ) )
631599 pkg = Core .update_package_values (pkg )
632600 if pkg .namespace :
633601 pkg .purl += f"{ pkg .namespace } /{ pkg .purl } "
634- removed_packages [pkg .id ] = pkg
602+ removed_packages [artifact .id ] = pkg
635603 except KeyError :
636- log .error (f"KeyError: Could not create package from removed artifact { artifact_id } " )
637- log .error (f"Artifact details - name: { artifact_name } , version: { artifact_version } " )
604+ log .error (f"KeyError: Could not create package from removed artifact { artifact . id } " )
605+ log .error (f"Artifact details - name: { artifact . name } , version: { artifact . version } " )
638606 log .error ("No matching packages found in head_full_scan" )
639607
640- return added_packages , removed_packages , diff_url , new_full_scan_url
608+ return added_packages , removed_packages
641609
642610 def create_new_diff (
643611 self ,
@@ -683,6 +651,7 @@ def create_new_diff(
683651 try :
684652 new_scan_start = time .time ()
685653 new_full_scan = self .create_full_scan (files_for_sending , params )
654+ new_full_scan .sbom_artifacts = self .get_sbom_data (new_full_scan .id )
686655 new_scan_end = time .time ()
687656 log .info (f"Total time to create new full scan: { new_scan_end - new_scan_start :.2f} " )
688657 except APIFailure as e :
@@ -694,15 +663,26 @@ def create_new_diff(
694663 log .error (f"Stack trace:\n { traceback .format_exc ()} " )
695664 raise
696665
697- added_packages , removed_packages , diff_url , report_url = self .get_added_and_removed_packages (
698- head_full_scan_id ,
699- new_full_scan . id
700- )
666+ scans_ready = self .check_full_scans_status ( head_full_scan_id , new_full_scan . id )
667+ if scans_ready is False :
668+ log . error ( f"Full scans did not complete within { self . config . timeout } seconds" )
669+ added_packages , removed_packages = self . get_added_and_removed_packages ( head_full_scan_id , new_full_scan . id )
701670
702671 diff = self .create_diff_report (added_packages , removed_packages )
672+ 673+ base_socket = "https://socket.dev/dashboard/org"
703674 diff .id = new_full_scan .id
675+ 676+ report_url = f"{ base_socket } /{ self .config .org_slug } /sbom/{ diff .id } "
677+ if not params .include_license_details :
678+ report_url += "?include_license_details=false"
704679 diff .report_url = report_url
705- diff .diff_url = diff_url
680+ 681+ if head_full_scan_id is not None :
682+ diff .diff_url = f"{ base_socket } /{ self .config .org_slug } /diff/{ head_full_scan_id } /{ diff .id } "
683+ else :
684+ diff .diff_url = diff .report_url
685+ 706686 return diff
707687
708688 def create_diff_report (
0 commit comments