diff --git a/CHANGELOG.md b/CHANGELOG.md index 38a667f53..90f11ef39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,12 @@ - [Rust](https://github.com/moonrepo/rust-plugin/blob/master/CHANGELOG.md) - [TOML schema](https://github.com/moonrepo/schema-plugin/blob/master/CHANGELOG.md) +## Unreleased + +#### 🚀 Updates + +- Merged `proto use` and `proto install` commands. If no arguments are provided to `proto install`, it will install all configured tools. + ## 0.38.4 #### 🐞 Fixes diff --git a/crates/cli/src/app.rs b/crates/cli/src/app.rs index d5a6d06de..6d01796cb 100644 --- a/crates/cli/src/app.rs +++ b/crates/cli/src/app.rs @@ -1,6 +1,5 @@ use crate::commands::{ debug::DebugConfigArgs, - install_all::InstallAllArgs, plugin::{AddPluginArgs, InfoPluginArgs, ListPluginsArgs, RemovePluginArgs, SearchPluginArgs}, ActivateArgs, AliasArgs, BinArgs, CleanArgs, CompletionsArgs, DiagnoseArgs, InstallArgs, ListArgs, ListRemoteArgs, MigrateArgs, OutdatedArgs, PinArgs, RegenArgs, RunArgs, SetupArgs, @@ -160,10 +159,10 @@ pub enum Commands { Diagnose(DiagnoseArgs), #[command( - alias = "i", + aliases = ["i", "u", "use"], name = "install", - about = "Download and install a tool.", - long_about = "Download and install a tool by version into ~/.proto/tools." + about = "Download and install one or many tools.", + long_about = "Download and install one or many tools by version into ~/.proto/tools.\n\nIf no arguments are provided, will install all tools configured in .prototools.\n\nIf a name argument is provided, will install a single tool by version." )] Install(InstallArgs), @@ -260,13 +259,6 @@ pub enum Commands { about = "Upgrade proto to the latest version." )] Upgrade, - - #[command( - alias = "u", - name = "use", - about = "Download and install all tools from loaded .prototools." - )] - Use(InstallAllArgs), } #[derive(Clone, Debug, Subcommand)] diff --git a/crates/cli/src/commands/clean.rs b/crates/cli/src/commands/clean.rs index 75a021c7b..b85d55eeb 100644 --- a/crates/cli/src/commands/clean.rs +++ b/crates/cli/src/commands/clean.rs @@ -277,7 +277,7 @@ pub async fn purge_plugins(session: &ProtoSession, yes: bool) -> AppResult { Ok(()) } -pub async fn internal_clean(session: &ProtoSession, args: &CleanArgs, yes: bool) -> AppResult { +pub async fn internal_clean(session: &ProtoSession, args: CleanArgs, yes: bool) -> AppResult { let days = args.days.unwrap_or(30); let now = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) @@ -334,7 +334,7 @@ pub async fn clean(session: ProtoSession, args: CleanArgs) -> AppResult { return Ok(()); } - internal_clean(&session, &args, force_yes).await?; + internal_clean(&session, args, force_yes).await?; Ok(()) } diff --git a/crates/cli/src/commands/install.rs b/crates/cli/src/commands/install.rs index 26ca55694..a0c2f5928 100644 --- a/crates/cli/src/commands/install.rs +++ b/crates/cli/src/commands/install.rs @@ -1,16 +1,19 @@ use super::clean::clean_plugins; use super::pin::internal_pin; -use crate::helpers::{create_progress_bar, disable_progress_bars}; +use crate::commands::clean::{internal_clean, CleanArgs}; +use crate::helpers::{create_progress_bar, disable_progress_bars, enable_progress_bars}; use crate::session::ProtoSession; use crate::shell::{self, Export}; use crate::telemetry::{track_usage, Metric}; use clap::{Args, ValueEnum}; +use miette::IntoDiagnostic; use proto_core::{Id, PinType, Tool, UnresolvedVersionSpec}; use proto_pdk_api::{InstallHook, SyncShellProfileInput, SyncShellProfileOutput}; use starbase::AppResult; use starbase_shell::ShellType; use starbase_styles::color; use std::env; +use std::process; use tracing::debug; #[derive(Clone, Debug, ValueEnum)] @@ -19,30 +22,44 @@ pub enum PinOption { Local, } -#[derive(Args, Clone, Debug)] +#[derive(Args, Clone, Debug, Default)] pub struct InstallArgs { - #[arg(required = true, help = "ID of tool")] - pub id: Id, + // ONE + #[arg(help = "ID of a single tool to install")] + pub id: Option, #[arg( default_value = "latest", - help = "Version or alias of tool", + help = "When installing one, the version or alias to install", group = "version-type" )] pub spec: Option, #[arg( long, - help = "Install a canary (nightly, etc) version", + help = "When installing one, use a canary (nightly, etc) version", group = "version-type" )] pub canary: bool, - #[arg(long, help = "Pin the resolved version to .prototools")] + #[arg( + long, + help = "When installing one, pin the resolved version to .prototools" + )] pub pin: Option>, + // ALL + #[arg( + long, + help = "When installing all, include versions configured in global ~/.proto/.prototools" + )] + pub include_global: bool, + // Passthrough args (after --) - #[arg(last = true, help = "Unique arguments to pass to each tool")] + #[arg( + last = true, + help = "When installing one, additional arguments to pass to the tool" + )] pub passthrough: Vec, } @@ -82,14 +99,93 @@ async fn pin_version( Ok(pin) } -pub async fn internal_install( +fn update_shell(tool: &Tool, passthrough_args: Vec) -> miette::Result<()> { + if !tool.plugin.has_func("sync_shell_profile") { + return Ok(()); + } + + let output: SyncShellProfileOutput = tool.plugin.call_func_with( + "sync_shell_profile", + SyncShellProfileInput { + context: tool.create_context(), + passthrough_args, + }, + )?; + + if output.skip_sync { + return Ok(()); + } + + let shell_type = ShellType::try_detect()?; + let shell = shell_type.build(); + + debug!( + shell = ?shell_type, + env_vars = ?output.export_vars, + paths = ?output.extend_path, + "Updating shell profile", + ); + + let mut exports = vec![]; + + if let Some(export_vars) = output.export_vars { + for (key, value) in export_vars { + exports.push(Export::Var(key, value)); + } + } + + if let Some(extend_path) = output.extend_path { + exports.push(Export::Path(extend_path)); + } + + if exports.is_empty() { + return Ok(()); + } + + let profile_path = tool.proto.store.load_preferred_profile()?.and_then(|file| { + if file.exists() { + Some(file) + } else { + debug!( + profile = ?file, + "Configured shell profile path does not exist, will not use", + ); + + None + } + }); + + if let Some(updated_profile) = profile_path { + let exported_content = shell::format_exports(&shell, &tool.id, exports); + + if shell::update_profile_if_not_setup( + &updated_profile, + &exported_content, + &output.check_var, + )? { + println!( + "Added {} to shell profile {}", + color::property(output.check_var), + color::path(updated_profile) + ); + } + } + + Ok(()) +} + +#[tracing::instrument(skip(session, args, tool))] +pub async fn install_one( session: &ProtoSession, args: InstallArgs, + id: &Id, tool: Option, ) -> miette::Result { + debug!(id = id.as_str(), "Loading tool"); + let mut tool = match tool { Some(tool) => tool, - None => session.load_tool(&args.id).await?, + None => session.load_tool(id).await?, }; let version = if args.canary { @@ -135,7 +231,7 @@ pub async fn internal_install( resolved_version.to_string(), ); - env::set_var("PROTO_INSTALL", args.id.to_string()); + env::set_var("PROTO_INSTALL", id.to_string()); // Run before hook if tool.plugin.has_func("pre_install") { @@ -220,76 +316,91 @@ pub async fn internal_install( Ok(tool) } -fn update_shell(tool: &Tool, passthrough_args: Vec) -> miette::Result<()> { - if !tool.plugin.has_func("sync_shell_profile") { - return Ok(()); - } +#[tracing::instrument(skip_all)] +pub async fn install_all(session: &ProtoSession, args: InstallArgs) -> AppResult { + debug!("Loading all tools"); - let output: SyncShellProfileOutput = tool.plugin.call_func_with( - "sync_shell_profile", - SyncShellProfileInput { - context: tool.create_context(), - passthrough_args, - }, - )?; + let tools = session.load_tools().await?; - if output.skip_sync { - return Ok(()); - } + debug!("Detecting tool versions to install"); - let shell_type = ShellType::try_detect()?; - let shell = shell_type.build(); + let config = if args.include_global { + session.env.load_config_manager()?.get_merged_config()? + } else { + session + .env + .load_config_manager()? + .get_merged_config_without_global()? + }; + let mut versions = config.versions.to_owned(); - debug!( - shell = ?shell_type, - env_vars = ?output.export_vars, - paths = ?output.extend_path, - "Updating shell profile", - ); + for tool in &tools { + if versions.contains_key(&tool.id) { + continue; + } - let mut exports = vec![]; + if let Some((candidate, _)) = tool.detect_version_from(&session.env.cwd).await? { + debug!("Detected version {} for {}", candidate, tool.get_name()); - if let Some(export_vars) = output.export_vars { - for (key, value) in export_vars { - exports.push(Export::Var(key, value)); + versions.insert(tool.id.clone(), candidate); } } - if let Some(extend_path) = output.extend_path { - exports.push(Export::Path(extend_path)); - } + if versions.is_empty() { + eprintln!("No versions have been configured, nothing to install!"); - if exports.is_empty() { - return Ok(()); + process::exit(1); } - let profile_path = tool.proto.store.load_preferred_profile()?.and_then(|file| { - if file.exists() { - Some(file) - } else { - debug!( - profile = ?file, - "Configured shell profile path does not exist, will not use", - ); + let pb = create_progress_bar(format!( + "Installing {} tools: {}", + versions.len(), + versions + .keys() + .map(color::id) + .collect::>() + .join(", ") + )); - None + disable_progress_bars(); + + // Then install each tool in parallel! + let mut futures = vec![]; + + for tool in tools { + if let Some(version) = versions.remove(&tool.id) { + let proto_clone = session.clone(); + let id = tool.id.clone(); + + futures.push(tokio::spawn(async move { + install_one( + &proto_clone, + InstallArgs { + spec: Some(version), + ..Default::default() + }, + &id, + Some(tool), + ) + .await + })); } - }); + } - if let Some(updated_profile) = profile_path { - let exported_content = shell::format_exports(&shell, &tool.id, exports); + for future in futures { + future.await.into_diagnostic()??; + } - if shell::update_profile_if_not_setup( - &updated_profile, - &exported_content, - &output.check_var, - )? { - println!( - "Added {} to shell profile {}", - color::property(output.check_var), - color::path(updated_profile) - ); - } + enable_progress_bars(); + + pb.finish_and_clear(); + + println!("Successfully installed tools!"); + + if config.settings.auto_clean { + debug!("Auto-clean enabled, starting clean"); + + internal_clean(session, CleanArgs::default(), true).await?; } Ok(()) @@ -297,7 +408,14 @@ fn update_shell(tool: &Tool, passthrough_args: Vec) -> miette::Result<() #[tracing::instrument(skip_all)] pub async fn install(session: ProtoSession, args: InstallArgs) -> AppResult { - internal_install(&session, args, None).await?; + match args.id.clone() { + Some(id) => { + install_one(&session, args, &id, None).await?; + } + None => { + install_all(&session, args).await?; + } + }; Ok(()) } diff --git a/crates/cli/src/commands/install_all.rs b/crates/cli/src/commands/install_all.rs deleted file mode 100644 index b529f0948..000000000 --- a/crates/cli/src/commands/install_all.rs +++ /dev/null @@ -1,109 +0,0 @@ -use crate::helpers::{create_progress_bar, disable_progress_bars, enable_progress_bars}; -use crate::session::ProtoSession; -use crate::{ - commands::clean::{internal_clean, CleanArgs}, - commands::install::{internal_install, InstallArgs}, -}; -use clap::Args; -use miette::IntoDiagnostic; -use starbase::AppResult; -use starbase_styles::color; -use std::process; -use tracing::debug; - -#[derive(Args, Clone, Debug)] -pub struct InstallAllArgs { - #[arg(long, help = "Include versions from global ~/.proto/.prototools")] - include_global: bool, -} - -#[tracing::instrument(skip_all)] -pub async fn install_all(session: ProtoSession, args: InstallAllArgs) -> AppResult { - debug!("Loading tools and plugins from .prototools"); - - let tools = session.load_tools().await?; - - debug!("Detecting tool versions to install"); - - let config = if args.include_global { - session.env.load_config_manager()?.get_merged_config()? // Including global configurations - } else { - session - .env - .load_config_manager()? - .get_merged_config_without_global()? // Excluding global configurations - }; - let mut versions = config.versions.to_owned(); - - for tool in &tools { - if versions.contains_key(&tool.id) { - continue; - } - - if let Some((candidate, _)) = tool.detect_version_from(&session.env.cwd).await? { - debug!("Detected version {} for {}", candidate, tool.get_name()); - - versions.insert(tool.id.clone(), candidate); - } - } - - if versions.is_empty() { - eprintln!("No versions have been configured, nothing to install!"); - - process::exit(1); - } - - let pb = create_progress_bar(format!( - "Installing {} tools: {}", - versions.len(), - versions - .keys() - .map(color::id) - .collect::>() - .join(", ") - )); - - disable_progress_bars(); - - // Then install each tool in parallel! - let mut futures = vec![]; - - for tool in tools { - if let Some(version) = versions.remove(&tool.id) { - let proto_clone = session.clone(); - - futures.push(tokio::spawn(async move { - internal_install( - &proto_clone, - InstallArgs { - canary: false, - id: tool.id.clone(), - pin: None, - passthrough: vec![], - spec: Some(version), - }, - Some(tool), - ) - .await - })); - } - } - - for future in futures { - future.await.into_diagnostic()??; - } - - enable_progress_bars(); - - pb.finish_and_clear(); - - println!("Successfully installed tools!"); - - if config.settings.auto_clean { - debug!("Auto-clean enabled, starting clean"); - - internal_clean(&session, &CleanArgs::default(), true).await?; - } - - Ok(()) -} diff --git a/crates/cli/src/commands/mod.rs b/crates/cli/src/commands/mod.rs index 42b37a759..c89f519f1 100644 --- a/crates/cli/src/commands/mod.rs +++ b/crates/cli/src/commands/mod.rs @@ -6,7 +6,6 @@ mod completions; pub mod debug; mod diagnose; mod install; -pub mod install_all; mod list; mod list_remote; mod migrate; @@ -29,7 +28,6 @@ pub use clean::*; pub use completions::*; pub use diagnose::*; pub use install::*; -pub use install_all::*; pub use list::*; pub use list_remote::*; pub use migrate::*; diff --git a/crates/cli/src/commands/run.rs b/crates/cli/src/commands/run.rs index 595c67153..15f5e8b90 100644 --- a/crates/cli/src/commands/run.rs +++ b/crates/cli/src/commands/run.rs @@ -1,4 +1,4 @@ -use crate::commands::install::{internal_install, InstallArgs}; +use crate::commands::install::{install_one, InstallArgs}; use crate::error::ProtoCliError; use crate::session::ProtoSession; use clap::Args; @@ -176,15 +176,13 @@ pub async fn run(session: ProtoSession, args: RunArgs) -> AppResult { // Install the tool debug!("Auto-install setting is configured, attempting to install"); - tool = internal_install( + tool = install_one( &session, InstallArgs { - canary: false, - id: args.id.clone(), - pin: None, - passthrough: vec![], spec: Some(tool.get_resolved_version().to_unresolved_spec()), + ..Default::default() }, + &args.id, Some(tool), ) .await?; diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index b2657dab9..751d3f8e3 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -102,7 +102,6 @@ async fn main() -> MainResult { Commands::Uninstall(args) => commands::uninstall(session, args).await, Commands::Unpin(args) => commands::unpin(session, args).await, Commands::Upgrade => commands::upgrade(session).await, - Commands::Use(args) => commands::install_all(session, args).await, } }) .await?; diff --git a/crates/cli/tests/use_test.rs b/crates/cli/tests/install_all_test.rs similarity index 94% rename from crates/cli/tests/use_test.rs rename to crates/cli/tests/install_all_test.rs index 1030bfeb5..c29435012 100644 --- a/crates/cli/tests/use_test.rs +++ b/crates/cli/tests/install_all_test.rs @@ -26,7 +26,7 @@ deno = "1.30.0" sandbox .run_bin(|cmd| { - cmd.arg("use"); + cmd.arg("install"); // use }) .success(); @@ -46,7 +46,7 @@ deno = "1.30.0" sandbox .run_bin(|cmd| { - cmd.arg("use"); + cmd.arg("use"); // install }) .success(); @@ -89,7 +89,7 @@ deno = "1.30.0" sandbox .run_bin(|cmd| { - cmd.arg("use").arg("--include-global"); + cmd.arg("install").arg("--include-global"); }) .success();