diff --git a/docs/reference/cli/pixi/tree.md b/docs/reference/cli/pixi/tree.md
index fad0424641..54a44bc420 100644
--- a/docs/reference/cli/pixi/tree.md
+++ b/docs/reference/cli/pixi/tree.md
@@ -22,6 +22,8 @@ pixi tree [OPTIONS] [REGEX]
: The environment to list packages for. Defaults to the default environment
- `--invert (-i)`
: Invert tree and show what depends on given package in the regex argument
+- `--json`
+: Show a JSON representation of the dependency tree
## Update Options
- `--no-lockfile-update`
diff --git a/src/cli/tree.rs b/src/cli/tree.rs
index 8ccf220fdc..01cf55a7f3 100644
--- a/src/cli/tree.rs
+++ b/src/cli/tree.rs
@@ -13,6 +13,7 @@ use pixi_manifest::FeaturesExt;
use rattler_conda_types::Platform;
use rattler_lock::LockedPackageRef;
use regex::Regex;
+use serde::{Deserialize, Serialize};
use crate::{
WorkspaceLocator, cli::cli_config::WorkspaceConfig, lock_file::UpdateLockFileOptions,
@@ -36,7 +37,7 @@ use super::cli_config::LockFileUpdateConfig;
))]
pub struct Args {
/// List only packages matching a regular expression
- #[arg()]
+ #[arg(conflicts_with = "json")]
pub regex: Option,
/// The platform to list packages for. Defaults to the current platform.
@@ -55,8 +56,12 @@ pub struct Args {
pub lock_file_update_config: LockFileUpdateConfig,
/// Invert tree and show what depends on given package in the regex argument
- #[arg(short, long, requires = "regex")]
+ #[arg(short, long, requires = "regex", conflicts_with = "json")]
pub invert: bool,
+
+ /// Show a JSON representation of the dependency tree
+ #[arg(long)]
+ pub json: bool,
}
struct Symbols {
@@ -108,7 +113,9 @@ pub async fn execute(args: Args) -> miette::Result<()> {
let stdout = std::io::stdout();
let mut handle = stdout.lock();
- if args.invert {
+ if args.json {
+ print_json_dependency_tree(&mut handle, &dep_map, &direct_deps)?;
+ } else if args.invert {
print_inverted_dependency_tree(
&mut handle,
&invert_dep_map(&dep_map),
@@ -123,6 +130,60 @@ pub async fn execute(args: Args) -> miette::Result<()> {
Ok(())
}
+#[derive(Debug, Clone, Serialize, Deserialize)]
+struct DependencyTreeNode {
+ name: String,
+ version: String,
+ tags: Vec,
+ dependencies: Vec,
+}
+
+fn package_to_tree_node(
+ package: &Package,
+ dep_map: &HashMap,
+) -> DependencyTreeNode {
+ let dependencies = package
+ .dependencies
+ .iter()
+ .filter_map(|dep_name| dep_map.get(dep_name))
+ .map(|dep| package_to_tree_node(dep, dep_map))
+ .collect();
+
+ DependencyTreeNode {
+ name: package.name.clone(),
+ version: package.version.clone(),
+ tags: Vec::new(),
+ dependencies,
+ }
+}
+
+/// Print a JSON representation of the dependency tree
+fn print_json_dependency_tree(
+ handle: &mut StdoutLock,
+ dep_map: &HashMap,
+ direct_deps: &HashSet,
+) -> miette::Result<()> {
+ let dependencies: Vec = direct_deps
+ .iter()
+ .filter_map(|pkg_name| dep_map.get(pkg_name))
+ .map(|pkg| package_to_tree_node(pkg, dep_map))
+ .collect();
+ let json = serde_json::to_string_pretty(&dependencies)
+ .into_diagnostic()
+ .wrap_err("Failed to serialize dependency tree to JSON")?;
+ writeln!(handle, "{}", json)
+ .map_err(|e| {
+ if e.kind() == std::io::ErrorKind::BrokenPipe {
+ // Exit gracefully
+ std::process::exit(0);
+ } else {
+ e
+ }
+ })
+ .into_diagnostic()
+ .wrap_err("Failed to serialize dependency tree to JSON")
+}
+
/// Filter and print an inverted dependency tree
fn print_inverted_dependency_tree(
handle: &mut StdoutLock,