@@ -5,12 +5,13 @@ use std::fs;
55use std:: path:: Path ;
66
77use crate :: {
8+ helper:: lock:: path_to_pkg_name,
89 service:: dependency_graph:: { DependencyGraphService , DependencyType , PackageNode } ,
910 util:: logger:: log_verbose,
1011} ;
1112
1213/// Represents package information in package-lock.json
13- #[ derive( Debug , Clone , Serialize , Deserialize ) ]
14+ #[ derive( Debug , Clone , Serialize , Deserialize , Default ) ]
1415pub struct LockPackage {
1516 #[ serde( skip_serializing_if = "Option::is_none" ) ]
1617 pub name : Option < String > ,
@@ -51,6 +52,13 @@ pub struct LockPackage {
5152 pub has_install_script : Option < bool > ,
5253 #[ serde( skip_serializing_if = "Option::is_none" ) ]
5354 pub workspaces : Option < Vec < String > > ,
55+ // Additional fields from helper/lock.rs Package
56+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
57+ pub link : Option < bool > ,
58+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
59+ pub cpu : Option < serde_json:: Value > ,
60+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
61+ pub os : Option < serde_json:: Value > ,
5462}
5563
5664impl LockPackage {
@@ -61,8 +69,10 @@ impl LockPackage {
6169 } else if path. is_empty ( ) {
6270 "root" . to_string ( )
6371 } else {
64- // Extract package name from path
65- path. split ( '/' ) . next_back ( ) . unwrap_or ( "unknown" ) . to_string ( )
72+ // Use existing path_to_pkg_name helper function
73+ path_to_pkg_name ( path)
74+ . map ( |s| s. to_string ( ) )
75+ . unwrap_or_else ( || "unknown" . to_string ( ) )
6676 }
6777 }
6878
@@ -73,6 +83,23 @@ impl LockPackage {
7383 . unwrap_or_else ( || "unknown" . to_string ( ) )
7484 }
7585
86+ /// Parse and cache bin files from the bin field
87+ pub fn parse_bin_files ( & self , package_name : & str ) -> Vec < ( String , String ) > {
88+ match & self . bin {
89+ Some ( serde_json:: Value :: Object ( obj) ) => obj
90+ . iter ( )
91+ . map ( |( k, v) | ( k. clone ( ) , v. as_str ( ) . unwrap_or_default ( ) . to_string ( ) ) )
92+ . collect ( ) ,
93+ Some ( serde_json:: Value :: String ( s) ) => vec ! [ ( package_name. to_string( ) , s. clone( ) ) ] ,
94+ _ => Vec :: new ( ) ,
95+ }
96+ }
97+
98+ /// Check if package has install scripts
99+ pub fn has_install_scripts ( & self ) -> bool {
100+ self . has_install_script . unwrap_or ( false )
101+ }
102+
76103 /// Convert to PackageNode
77104 pub fn to_package_node ( & self , path : & str ) -> PackageNode {
78105 let name = self . get_name ( path) ;
@@ -257,4 +284,103 @@ mod tests {
257284 assert_eq ! ( package_lock. lockfile_version, 3 ) ;
258285 assert_eq ! ( package_lock. packages. len( ) , 3 ) ;
259286 }
287+
288+ #[ test]
289+ fn test_lock_package_parse_bin_files ( ) {
290+ // Test with object-style bin
291+ let mut package = LockPackage {
292+ name : Some ( "test-package" . to_string ( ) ) ,
293+ version : Some ( "1.0.0" . to_string ( ) ) ,
294+ bin : Some ( serde_json:: json!( { "cli" : "bin/cli.js" , "tool" : "bin/tool.js" } ) ) ,
295+ ..LockPackage :: default ( )
296+ } ;
297+
298+ let bin_files = package. parse_bin_files ( "test-package" ) ;
299+ assert_eq ! ( bin_files. len( ) , 2 ) ;
300+ assert ! ( bin_files. contains( & ( "cli" . to_string( ) , "bin/cli.js" . to_string( ) ) ) ) ;
301+ assert ! ( bin_files. contains( & ( "tool" . to_string( ) , "bin/tool.js" . to_string( ) ) ) ) ;
302+
303+ // Test with string-style bin
304+ package. bin = Some ( serde_json:: json!( "index.js" ) ) ;
305+ let bin_files = package. parse_bin_files ( "test-package" ) ;
306+ assert_eq ! ( bin_files. len( ) , 1 ) ;
307+ assert_eq ! (
308+ bin_files[ 0 ] ,
309+ ( "test-package" . to_string( ) , "index.js" . to_string( ) )
310+ ) ;
311+
312+ // Test with no bin
313+ package. bin = None ;
314+ let bin_files = package. parse_bin_files ( "test-package" ) ;
315+ assert_eq ! ( bin_files. len( ) , 0 ) ;
316+
317+ // Test with invalid bin value
318+ package. bin = Some ( serde_json:: json!( 123 ) ) ;
319+ let bin_files = package. parse_bin_files ( "test-package" ) ;
320+ assert_eq ! ( bin_files. len( ) , 0 ) ;
321+ }
322+
323+ #[ test]
324+ fn test_lock_package_has_install_scripts ( ) {
325+ let mut package = LockPackage {
326+ name : Some ( "test-package" . to_string ( ) ) ,
327+ version : Some ( "1.0.0" . to_string ( ) ) ,
328+ has_install_script : Some ( true ) ,
329+ ..LockPackage :: default ( )
330+ } ;
331+
332+ assert ! ( package. has_install_scripts( ) ) ;
333+
334+ package. has_install_script = Some ( false ) ;
335+ assert ! ( !package. has_install_scripts( ) ) ;
336+
337+ package. has_install_script = None ;
338+ assert ! ( !package. has_install_scripts( ) ) ;
339+ }
340+
341+ #[ test]
342+ fn test_lock_package_get_name ( ) {
343+ let mut package = LockPackage {
344+ name : Some ( "test-package" . to_string ( ) ) ,
345+ version : Some ( "1.0.0" . to_string ( ) ) ,
346+ ..LockPackage :: default ( )
347+ } ;
348+
349+ // Test with explicit name
350+ assert_eq ! (
351+ package. get_name( "node_modules/some-package" ) ,
352+ "test-package"
353+ ) ;
354+
355+ // Test without name (infer from path)
356+ package. name = None ;
357+ assert_eq ! ( package. get_name( "node_modules/lodash" ) , "lodash" ) ;
358+ assert_eq ! (
359+ package. get_name( "node_modules/@scope/package" ) ,
360+ "@scope/package"
361+ ) ;
362+
363+ // Test with empty path
364+ assert_eq ! ( package. get_name( "" ) , "root" ) ;
365+
366+ // Test with complex path
367+ assert_eq ! (
368+ package. get_name( "node_modules/parent/node_modules/child" ) ,
369+ "child"
370+ ) ;
371+ }
372+
373+ #[ test]
374+ fn test_lock_package_get_version ( ) {
375+ let mut package = LockPackage {
376+ name : Some ( "test-package" . to_string ( ) ) ,
377+ version : Some ( "1.2.3" . to_string ( ) ) ,
378+ ..LockPackage :: default ( )
379+ } ;
380+
381+ assert_eq ! ( package. get_version( ) , "1.2.3" ) ;
382+
383+ package. version = None ;
384+ assert_eq ! ( package. get_version( ) , "unknown" ) ;
385+ }
260386}
0 commit comments