@@ -4,15 +4,17 @@ use socket_patch_core::api::blob_fetcher::{
44} ;
55use socket_patch_core:: api:: client:: get_api_client_from_env;
66use socket_patch_core:: constants:: DEFAULT_PATCH_MANIFEST_PATH ;
7- use socket_patch_core:: crawlers:: { CrawlerOptions , NpmCrawler , PythonCrawler } ;
7+ use socket_patch_core:: crawlers:: { CrawlerOptions , Ecosystem } ;
88use socket_patch_core:: manifest:: operations:: read_manifest;
99use socket_patch_core:: patch:: apply:: { apply_package_patch, verify_file_patch, ApplyResult } ;
1010use socket_patch_core:: utils:: cleanup_blobs:: { cleanup_unused_blobs, format_cleanup_result} ;
11- use socket_patch_core:: utils:: purl:: { is_npm_purl , is_pypi_purl , strip_purl_qualifiers} ;
11+ use socket_patch_core:: utils:: purl:: strip_purl_qualifiers;
1212use socket_patch_core:: utils:: telemetry:: { track_patch_applied, track_patch_apply_failed} ;
1313use std:: collections:: { HashMap , HashSet } ;
1414use std:: path:: { Path , PathBuf } ;
1515
16+ use crate :: ecosystem_dispatch:: { find_packages_for_purls, partition_purls} ;
17+
1618#[ derive( Args ) ]
1719pub struct ApplyArgs {
1820 /// Working directory
@@ -174,18 +176,8 @@ async fn apply_patches_inner(
174176
175177 // Partition manifest PURLs by ecosystem
176178 let manifest_purls: Vec < String > = manifest. patches . keys ( ) . cloned ( ) . collect ( ) ;
177- let mut npm_purls: Vec < String > = manifest_purls. iter ( ) . filter ( |p| is_npm_purl ( p) ) . cloned ( ) . collect ( ) ;
178- let mut pypi_purls: Vec < String > = manifest_purls. iter ( ) . filter ( |p| is_pypi_purl ( p) ) . cloned ( ) . collect ( ) ;
179-
180- // Filter by ecosystem if specified
181- if let Some ( ref ecosystems) = args. ecosystems {
182- if !ecosystems. iter ( ) . any ( |e| e == "npm" ) {
183- npm_purls. clear ( ) ;
184- }
185- if !ecosystems. iter ( ) . any ( |e| e == "pypi" ) {
186- pypi_purls. clear ( ) ;
187- }
188- }
179+ let partitioned =
180+ partition_purls ( & manifest_purls, args. ecosystems . as_deref ( ) ) ;
189181
190182 let crawler_options = CrawlerOptions {
191183 cwd : args. cwd . clone ( ) ,
@@ -194,63 +186,12 @@ async fn apply_patches_inner(
194186 batch_size : 100 ,
195187 } ;
196188
197- let mut all_packages: HashMap < String , PathBuf > = HashMap :: new ( ) ;
189+ let all_packages =
190+ find_packages_for_purls ( & partitioned, & crawler_options, args. silent ) . await ;
198191
199- // Find npm packages
200- if !npm_purls. is_empty ( ) {
201- let npm_crawler = NpmCrawler ;
202- match npm_crawler. get_node_modules_paths ( & crawler_options) . await {
203- Ok ( nm_paths) => {
204- if ( args. global || args. global_prefix . is_some ( ) ) && !args. silent {
205- if let Some ( first) = nm_paths. first ( ) {
206- println ! ( "Using global npm packages at: {}" , first. display( ) ) ;
207- }
208- }
209- for nm_path in & nm_paths {
210- if let Ok ( packages) = npm_crawler. find_by_purls ( nm_path, & npm_purls) . await {
211- for ( purl, pkg) in packages {
212- all_packages. entry ( purl) . or_insert ( pkg. path ) ;
213- }
214- }
215- }
216- }
217- Err ( e) => {
218- if !args. silent {
219- eprintln ! ( "Failed to find npm packages: {e}" ) ;
220- }
221- }
222- }
223- }
192+ let has_any_purls = !partitioned. is_empty ( ) ;
224193
225- // Find Python packages
226- if !pypi_purls. is_empty ( ) {
227- let python_crawler = PythonCrawler ;
228- let base_pypi_purls: Vec < String > = pypi_purls
229- . iter ( )
230- . map ( |p| strip_purl_qualifiers ( p) . to_string ( ) )
231- . collect :: < HashSet < _ > > ( )
232- . into_iter ( )
233- . collect ( ) ;
234-
235- match python_crawler. get_site_packages_paths ( & crawler_options) . await {
236- Ok ( sp_paths) => {
237- for sp_path in & sp_paths {
238- if let Ok ( packages) = python_crawler. find_by_purls ( sp_path, & base_pypi_purls) . await {
239- for ( purl, pkg) in packages {
240- all_packages. entry ( purl) . or_insert ( pkg. path ) ;
241- }
242- }
243- }
244- }
245- Err ( e) => {
246- if !args. silent {
247- eprintln ! ( "Failed to find Python packages: {e}" ) ;
248- }
249- }
250- }
251- }
252-
253- if all_packages. is_empty ( ) && npm_purls. is_empty ( ) && pypi_purls. is_empty ( ) {
194+ if all_packages. is_empty ( ) && !has_any_purls {
254195 if !args. silent {
255196 if args. global || args. global_prefix . is_some ( ) {
256197 eprintln ! ( "No global packages found" ) ;
@@ -272,20 +213,22 @@ async fn apply_patches_inner(
272213 let mut results: Vec < ApplyResult > = Vec :: new ( ) ;
273214 let mut has_errors = false ;
274215
275- // Group pypi PURLs by base
216+ // Group pypi PURLs by base (for variant matching with qualifiers)
276217 let mut pypi_qualified_groups: HashMap < String , Vec < String > > = HashMap :: new ( ) ;
277- for purl in & pypi_purls {
278- let base = strip_purl_qualifiers ( purl) . to_string ( ) ;
279- pypi_qualified_groups
280- . entry ( base)
281- . or_default ( )
282- . push ( purl. clone ( ) ) ;
218+ if let Some ( pypi_purls) = partitioned. get ( & Ecosystem :: Pypi ) {
219+ for purl in pypi_purls {
220+ let base = strip_purl_qualifiers ( purl) . to_string ( ) ;
221+ pypi_qualified_groups
222+ . entry ( base)
223+ . or_default ( )
224+ . push ( purl. clone ( ) ) ;
225+ }
283226 }
284227
285228 let mut applied_base_purls: HashSet < String > = HashSet :: new ( ) ;
286229
287230 for ( purl, pkg_path) in & all_packages {
288- if is_pypi_purl ( purl) {
231+ if Ecosystem :: from_purl ( purl) == Some ( Ecosystem :: Pypi ) {
289232 let base_purl = strip_purl_qualifiers ( purl) . to_string ( ) ;
290233 if applied_base_purls. contains ( & base_purl) {
291234 continue ;
0 commit comments