@@ -700,6 +700,7 @@ interface LockPackageInfo {
700700interface DependencySnapshot {
701701 packageJson ?: PackageJsonInfo ;
702702 lockfile ?: any ;
703+ lockfileError ?: string ;
703704 packages : LockPackageInfo [ ] ;
704705}
705706
@@ -729,23 +730,151 @@ async function cmdDepsList(cwd: string) {
729730 const scan = await scanProject ( cwd ) ;
730731 const pm = scan . packageManager || "npm" ;
731732 console . log ( chalk . blue ( `Dependencies (${ pm } ):` ) ) ;
732- const result = await runCommand ( `${ pm } list --depth=0` , cwd ) ;
733+
734+ const listCommand = dependencyListCommand ( pm ) ;
735+ if ( ! listCommand ) {
736+ await printDeclaredDependencies ( cwd , scan , `No live dependency tree command is configured for ${ pm } .` ) ;
737+ return ;
738+ }
739+
740+ const result = await runCommand ( listCommand , cwd ) ;
733741 if ( result . exitCode !== 0 ) {
734- console . log ( chalk . yellow ( " Package manager dependency tree is unavailable; showing package.json declarations instead." ) ) ;
735- const snapshot = await loadDependencySnapshot ( cwd ) ;
736- const declared = declaredDependencyRows ( snapshot . packageJson ) ;
737- if ( declared . length === 0 ) {
738- printPlainError ( classifyCommandFailure ( { command : `${ pm } list --depth=0` , cwd, exitCode : result . exitCode , stdout : result . stdout , stderr : result . stderr } ) ) ;
742+ const shown = await printDeclaredDependencies ( cwd , scan , "Package manager dependency tree is unavailable; showing package.json declarations or ecosystem declarations instead." ) ;
743+ if ( ! shown ) {
744+ printPlainError ( classifyCommandFailure ( { command : listCommand , cwd, exitCode : result . exitCode , stdout : result . stdout , stderr : result . stderr } ) ) ;
739745 return ;
740746 }
741- for ( const row of declared ) {
742- console . log ( ` ${ row . kind . padEnd ( 22 ) } ${ row . name } @${ row . range } ` ) ;
743- }
744747 } else {
745748 console . log ( result . stdout || result . stderr ) ;
746749 }
747750}
748751
752+ function dependencyListCommand ( packageManager : string ) : string | null {
753+ switch ( packageManager ) {
754+ case "npm" :
755+ case "yarn" :
756+ case "pnpm" :
757+ case "bun" :
758+ return `${ packageManager } list --depth=0` ;
759+ case "pip" :
760+ return "python3 -m pip list || python -m pip list || pip list" ;
761+ case "poetry" :
762+ return "poetry show --no-dev" ;
763+ case "pipenv" :
764+ return "pipenv graph" ;
765+ case "go" :
766+ return "go list -m all" ;
767+ case "cargo" :
768+ return null ;
769+ default :
770+ return null ;
771+ }
772+ }
773+
774+ async function printDeclaredDependencies ( cwd : string , scan : Awaited < ReturnType < typeof scanProject > > , reason : string ) : Promise < boolean > {
775+ console . log ( chalk . yellow ( ` ${ reason } ` ) ) ;
776+ const rows = await declaredDependencyRowsForProject ( cwd , scan ) ;
777+ if ( rows . length === 0 ) return false ;
778+ for ( const row of rows ) {
779+ console . log ( ` ${ row . kind . padEnd ( 22 ) } ${ row . name } ${ row . range ? `@${ row . range } ` : "" } ` ) ;
780+ }
781+ return true ;
782+ }
783+
784+ async function declaredDependencyRowsForProject (
785+ cwd : string ,
786+ scan : Awaited < ReturnType < typeof scanProject > >
787+ ) : Promise < Array < { kind : string ; name : string ; range : string } > > {
788+ const snapshot = await loadDependencySnapshot ( cwd ) ;
789+ const nodeRows = declaredDependencyRows ( snapshot . packageJson ) ;
790+ if ( nodeRows . length > 0 ) return nodeRows ;
791+
792+ if ( scan . packageManager === "cargo" || scan . language === "Rust" ) {
793+ return parseCargoDependencies ( await readTextFile ( join ( cwd , "Cargo.toml" ) ) ) ;
794+ }
795+ if ( scan . packageManager === "go" || scan . language === "Go" ) {
796+ return parseGoDependencies ( await readTextFile ( join ( cwd , "go.mod" ) ) ) ;
797+ }
798+ if ( scan . language === "Python" ) {
799+ return [
800+ ...parseRequirementsDependencies ( await readTextFile ( join ( cwd , "requirements.txt" ) ) ) ,
801+ ...parsePyprojectDependencies ( await readTextFile ( join ( cwd , "pyproject.toml" ) ) ) ,
802+ ] ;
803+ }
804+ return [ ] ;
805+ }
806+
807+ async function readTextFile ( path : string ) : Promise < string > {
808+ try {
809+ return await readFile ( path , "utf-8" ) ;
810+ } catch {
811+ return "" ;
812+ }
813+ }
814+
815+ function parseCargoDependencies ( content : string ) : Array < { kind : string ; name : string ; range : string } > {
816+ const rows : Array < { kind : string ; name : string ; range : string } > = [ ] ;
817+ let section = "" ;
818+ for ( const line of content . split ( / \r ? \n / ) ) {
819+ const trimmed = line . trim ( ) ;
820+ if ( ! trimmed || trimmed . startsWith ( "#" ) ) continue ;
821+ if ( / ^ \[ .+ \] $ / . test ( trimmed ) ) {
822+ section = trimmed ;
823+ continue ;
824+ }
825+ if ( section !== "[dependencies]" && section !== "[dev-dependencies]" && section !== "[build-dependencies]" ) continue ;
826+ const match = trimmed . match ( / ^ ( [ A - Z a - z 0 - 9 _ . - ] + ) \s * = \s * ( .+ ) $ / ) ;
827+ if ( ! match ) continue ;
828+ rows . push ( { kind : section . slice ( 1 , - 1 ) , name : match [ 1 ] , range : match [ 2 ] . replace ( / ^ " | " $ / g, "" ) } ) ;
829+ }
830+ return rows . sort ( ( a , b ) => a . kind . localeCompare ( b . kind ) || a . name . localeCompare ( b . name ) ) ;
831+ }
832+
833+ function parseGoDependencies ( content : string ) : Array < { kind : string ; name : string ; range : string } > {
834+ const rows : Array < { kind : string ; name : string ; range : string } > = [ ] ;
835+ let inRequireBlock = false ;
836+ for ( const line of content . split ( / \r ? \n / ) ) {
837+ const trimmed = line . trim ( ) ;
838+ if ( ! trimmed || trimmed . startsWith ( "//" ) ) continue ;
839+ if ( trimmed . startsWith ( "require (" ) ) {
840+ inRequireBlock = true ;
841+ continue ;
842+ }
843+ if ( inRequireBlock && trimmed === ")" ) {
844+ inRequireBlock = false ;
845+ continue ;
846+ }
847+ const depLine = inRequireBlock ? trimmed : trimmed . startsWith ( "require " ) ? trimmed . slice ( "require " . length ) . trim ( ) : "" ;
848+ if ( ! depLine ) continue ;
849+ const [ name , range ] = depLine . split ( / \s + / , 2 ) ;
850+ if ( name && range ) rows . push ( { kind : "require" , name, range } ) ;
851+ }
852+ return rows . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ;
853+ }
854+
855+ function parseRequirementsDependencies ( content : string ) : Array < { kind : string ; name : string ; range : string } > {
856+ return content . split ( / \r ? \n / )
857+ . map ( ( line ) => line . trim ( ) )
858+ . filter ( ( line ) => line && ! line . startsWith ( "#" ) && ! line . startsWith ( "-" ) )
859+ . map ( ( line ) => {
860+ const match = line . match ( / ^ ( [ A - Z a - z 0 - 9 _ . - ] + ) \s * ( [ < > = ! ~ ] .* ) ? $ / ) ;
861+ return { kind : "requirements" , name : match ?. [ 1 ] || line , range : match ?. [ 2 ] || "" } ;
862+ } )
863+ . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ;
864+ }
865+
866+ function parsePyprojectDependencies ( content : string ) : Array < { kind : string ; name : string ; range : string } > {
867+ const rows : Array < { kind : string ; name : string ; range : string } > = [ ] ;
868+ const dependenciesBlock = content . match ( / d e p e n d e n c i e s \s * = \s * \[ ( [ \s \S ] * ?) \] / m) ?. [ 1 ] || "" ;
869+ for ( const raw of dependenciesBlock . split ( "," ) ) {
870+ const value = raw . trim ( ) . replace ( / ^ [ " ' ] | [ " ' ] $ / g, "" ) ;
871+ if ( ! value ) continue ;
872+ const match = value . match ( / ^ ( [ A - Z a - z 0 - 9 _ . - ] + ) \s * ( .* ) $ / ) ;
873+ rows . push ( { kind : "pyproject" , name : match ?. [ 1 ] || value , range : match ?. [ 2 ] || "" } ) ;
874+ }
875+ return rows . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ;
876+ }
877+
749878async function cmdDepsAudit ( cwd : string ) : Promise < void > {
750879 console . log ( chalk . blue . bold ( "\n Dependency Audit\n" ) ) ;
751880 const result = await runCommand ( "npm audit --json" , cwd ) ;
@@ -760,6 +889,13 @@ async function cmdDepsAudit(cwd: string): Promise<void> {
760889 }
761890 return ;
762891 }
892+ if ( audit . error ) {
893+ console . log ( chalk . yellow ( " Audit unavailable." ) ) ;
894+ console . log ( chalk . dim ( ` npm audit returned ${ audit . error . code || "an error" } ${ audit . error . summary ? `: ${ audit . error . summary } ` : "" } ` ) ) ;
895+ if ( audit . error . detail ) console . log ( chalk . dim ( ` ${ audit . error . detail } ` ) ) ;
896+ process . exitCode = result . exitCode || 1 ;
897+ return ;
898+ }
763899
764900 const counts = auditCounts ( audit ) ;
765901 const total = counts . total ?? Object . entries ( counts )
@@ -824,6 +960,8 @@ async function cmdDepsWhy(cwd: string, packageName: string | undefined): Promise
824960 for ( const pkg of locked ) {
825961 console . log ( ` - ${ pkg . name } ${ pkg . version ? `@${ pkg . version } ` : "" } (${ pkg . path || "root" } )` ) ;
826962 }
963+ } else if ( snapshot . lockfileError ) {
964+ console . log ( chalk . yellow ( `\n package-lock.json could not be parsed; transitive signal is limited. ${ snapshot . lockfileError } ` ) ) ;
827965 } else if ( ! snapshot . lockfile ) {
828966 console . log ( chalk . yellow ( "\n No package-lock.json found; transitive signal is limited." ) ) ;
829967 } else {
@@ -845,7 +983,9 @@ async function cmdDepsLicenses(cwd: string): Promise<void> {
845983 const snapshot = await loadDependencySnapshot ( cwd ) ;
846984 console . log ( chalk . blue . bold ( "\n Dependency Licenses\n" ) ) ;
847985
848- if ( ! snapshot . lockfile && snapshot . packages . length === 0 ) {
986+ if ( snapshot . lockfileError ) {
987+ console . log ( chalk . yellow ( ` package-lock.json could not be parsed; license signal is limited. ${ snapshot . lockfileError } ` ) ) ;
988+ } else if ( ! snapshot . lockfile && snapshot . packages . length === 0 ) {
849989 console . log ( chalk . yellow ( " No package-lock.json found; license signal is limited." ) ) ;
850990 }
851991
@@ -872,10 +1012,12 @@ async function cmdDepsLicenses(cwd: string): Promise<void> {
8721012
8731013async function loadDependencySnapshot ( cwd : string ) : Promise < DependencySnapshot > {
8741014 const packageJson = await readJsonFile < PackageJsonInfo > ( join ( cwd , "package.json" ) ) ;
875- const lockfile = await readJsonFile < any > ( join ( cwd , "package-lock.json" ) ) ;
1015+ const lockfileResult = await readJsonFileWithError < any > ( join ( cwd , "package-lock.json" ) ) ;
1016+ const lockfile = lockfileResult . value ;
8761017 return {
8771018 packageJson,
8781019 lockfile,
1020+ lockfileError : lockfileResult . exists && ! lockfileResult . ok ? lockfileResult . error : undefined ,
8791021 packages : collectLockPackages ( lockfile ) ,
8801022 } ;
8811023}
@@ -888,6 +1030,20 @@ async function readJsonFile<T>(path: string): Promise<T | undefined> {
8881030 }
8891031}
8901032
1033+ async function readJsonFileWithError < T > ( path : string ) : Promise < { ok : boolean ; exists : boolean ; value ?: T ; error ?: string } > {
1034+ try {
1035+ return { ok : true , exists : true , value : JSON . parse ( await readFile ( path , "utf-8" ) ) as T } ;
1036+ } catch ( err ) {
1037+ const code = ( err as { code ?: string } | undefined ) ?. code ;
1038+ if ( code === "ENOENT" ) return { ok : false , exists : false } ;
1039+ return {
1040+ ok : false ,
1041+ exists : true ,
1042+ error : err instanceof Error ? err . message : String ( err ) ,
1043+ } ;
1044+ }
1045+ }
1046+
8911047function collectLockPackages ( lockfile : any ) : LockPackageInfo [ ] {
8921048 if ( ! lockfile ) return [ ] ;
8931049
0 commit comments