From d019323d575dce73c785c4cbd0c5f5dac884e4f1 Mon Sep 17 00:00:00 2001 From: clawdeeo Date: Tue, 21 Apr 2026 22:04:31 +0000 Subject: [PATCH] feat: add parallel install support - Change Install command to accept multiple packages - Add handle_install_multiple function for concurrent installs - Use tokio::spawn and join_all for parallel execution - Report summary: X succeeded, Y failed - Single package installs still use sequential path (no overhead) Part of v0.3.0 phase 2 (parallel installs). --- src/cli.rs | 3 ++- src/install.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 10 +++++++-- 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index d960a94..da20182 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -14,7 +14,8 @@ pub struct Cli { #[derive(Subcommand)] pub enum Commands { Install { - package: String, + #[arg(num_args = 1..)] + packages: Vec, #[arg(short, long)] force: bool, #[arg(long)] diff --git a/src/install.rs b/src/install.rs index 9b8ba78..e604736 100644 --- a/src/install.rs +++ b/src/install.rs @@ -278,3 +278,62 @@ fn create_symlink(binary: &Path, name: &str, bin_dir: &Path) -> Result<()> { } Ok(()) } + +/// Install multiple packages in parallel +pub async fn handle_install_multiple( + packages: &[String], + force: bool, + dry_run: bool, + verify: bool, + config: &Config, +) -> Result<()> { + use futures::future::join_all; + + let total = packages.len(); + if !config.output.quiet { + println!("Installing {} packages...", total); + } + + let mut tasks = Vec::new(); + for pkg in packages { + let pkg = pkg.clone(); + let config = config.clone(); + let task = + tokio::spawn( + async move { handle_install(&pkg, force, dry_run, verify, &config).await }, + ); + tasks.push(task); + } + + let results = join_all(tasks).await; + + let mut success = 0; + let mut failed = 0; + for result in results { + match result { + Ok(Ok(())) => success += 1, + Ok(Err(e)) => { + if !config.output.quiet { + eprintln!("Error: {}", e); + } + failed += 1; + } + Err(e) => { + if !config.output.quiet { + eprintln!("Task failed: {}", e); + } + failed += 1; + } + } + } + + if !config.output.quiet { + println!("\nDone: {} succeeded, {} failed", success, failed); + } + + if failed > 0 { + bail!("{} package(s) failed to install", failed); + } + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 93b367d..95a30ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -63,11 +63,17 @@ fn apply_cli_overrides(mut config: Config, cli: &Cli) -> Config { async fn run(cli: Cli, config: Config) -> anyhow::Result<()> { match cli.command { Commands::Install { - package, + packages, force, dry_run, verify, - } => install::handle_install(&package, force, dry_run, verify, &config).await?, + } => { + if packages.len() == 1 { + install::handle_install(&packages[0], force, dry_run, verify, &config).await? + } else { + install::handle_install_multiple(&packages, force, dry_run, verify, &config).await? + } + } Commands::List { verbose } => registry::list_installed(verbose)?, Commands::Update { package } => install::handle_update(package.as_deref(), &config).await?, Commands::Uninstall { package } => registry::uninstall(&package, &config.install_dir)?,