@@ -13,6 +13,7 @@ use pixi_manifest::FeaturesExt;
1313use rattler_conda_types:: Platform ;
1414use rattler_lock:: LockedPackageRef ;
1515use regex:: Regex ;
16+ use serde:: { Deserialize , Serialize } ;
1617
1718use crate :: {
1819 WorkspaceLocator , cli:: cli_config:: WorkspaceConfig , lock_file:: UpdateLockFileOptions ,
@@ -36,7 +37,7 @@ use super::cli_config::LockFileUpdateConfig;
3637) ) ]
3738pub struct Args {
3839 /// List only packages matching a regular expression
39- #[ arg( ) ]
40+ #[ arg( conflicts_with = "json" ) ]
4041 pub regex : Option < String > ,
4142
4243 /// The platform to list packages for. Defaults to the current platform.
@@ -55,8 +56,12 @@ pub struct Args {
5556 pub lock_file_update_config : LockFileUpdateConfig ,
5657
5758 /// Invert tree and show what depends on given package in the regex argument
58- #[ arg( short, long, requires = "regex" ) ]
59+ #[ arg( short, long, requires = "regex" , conflicts_with = "json" ) ]
5960 pub invert : bool ,
61+
62+ /// Show a JSON representation of the dependency tree
63+ #[ arg( long) ]
64+ pub json : bool ,
6065}
6166
6267struct Symbols {
@@ -108,7 +113,9 @@ pub async fn execute(args: Args) -> miette::Result<()> {
108113
109114 let stdout = std:: io:: stdout ( ) ;
110115 let mut handle = stdout. lock ( ) ;
111- if args. invert {
116+ if args. json {
117+ print_json_dependency_tree ( & mut handle, & dep_map, & direct_deps) ?;
118+ } else if args. invert {
112119 print_inverted_dependency_tree (
113120 & mut handle,
114121 & invert_dep_map ( & dep_map) ,
@@ -123,6 +130,60 @@ pub async fn execute(args: Args) -> miette::Result<()> {
123130 Ok ( ( ) )
124131}
125132
133+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
134+ struct DependencyTreeNode {
135+ name : String ,
136+ version : String ,
137+ tags : Vec < String > ,
138+ dependencies : Vec < DependencyTreeNode > ,
139+ }
140+
141+ fn package_to_tree_node (
142+ package : & Package ,
143+ dep_map : & HashMap < String , Package > ,
144+ ) -> DependencyTreeNode {
145+ let dependencies = package
146+ . dependencies
147+ . iter ( )
148+ . filter_map ( |dep_name| dep_map. get ( dep_name) )
149+ . map ( |dep| package_to_tree_node ( dep, dep_map) )
150+ . collect ( ) ;
151+
152+ DependencyTreeNode {
153+ name : package. name . clone ( ) ,
154+ version : package. version . clone ( ) ,
155+ tags : Vec :: new ( ) ,
156+ dependencies,
157+ }
158+ }
159+
160+ /// Print a JSON representation of the dependency tree
161+ fn print_json_dependency_tree (
162+ handle : & mut StdoutLock ,
163+ dep_map : & HashMap < String , Package > ,
164+ direct_deps : & HashSet < String > ,
165+ ) -> miette:: Result < ( ) > {
166+ let dependencies: Vec < DependencyTreeNode > = direct_deps
167+ . iter ( )
168+ . filter_map ( |pkg_name| dep_map. get ( pkg_name) )
169+ . map ( |pkg| package_to_tree_node ( pkg, dep_map) )
170+ . collect ( ) ;
171+ let json = serde_json:: to_string_pretty ( & dependencies)
172+ . into_diagnostic ( )
173+ . wrap_err ( "Failed to serialize dependency tree to JSON" ) ?;
174+ writeln ! ( handle, "{}" , json)
175+ . map_err ( |e| {
176+ if e. kind ( ) == std:: io:: ErrorKind :: BrokenPipe {
177+ // Exit gracefully
178+ std:: process:: exit ( 0 ) ;
179+ } else {
180+ e
181+ }
182+ } )
183+ . into_diagnostic ( )
184+ . wrap_err ( "Failed to serialize dependency tree to JSON" )
185+ }
186+
126187/// Filter and print an inverted dependency tree
127188fn print_inverted_dependency_tree (
128189 handle : & mut StdoutLock ,
0 commit comments