diff --git a/CHANGELOG.md b/CHANGELOG.md index 5964c18bf..be9d8ebce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ #### 🚀 Updates -- Added an experimental command called `proto activate` that can be ran within your shell to "activate the proto environment", by setting necessary environment variables and paths when changing directories. +- Added an experimental command called `proto activate` that can be ran within your shell profile to "activate the proto environment", by setting necessary environment variables and paths when changing directories. - Globally installed packages will now be available automatically. This wasn't possible through shims alone. - Binaries that come pre-installed with a tool (and are not shims) will also be available automatically. - Added support for [murex](https://murex.rocks/) shells. @@ -27,8 +27,13 @@ #### 🧩 Plugins -- Updated `rust_plugin` to v0.10.4. +- Updated `node_plugin` and `node_depman_plugin` to v0.11.4. +- Updated `python_plugin` to v0.10.4. +- Updated `rust_plugin` to v0.10.5. - Respect `CARGO_HOME` during rustup installation. +- Updated `schema_plugin` (TOML) to v0.14.0. + - Added `platform.*.exes_dir`. + - Renamed `platform.*.bin_path` to `exe_path`. ## 0.37.2 diff --git a/Cargo.lock b/Cargo.lock index ce7f0fc5d..814706cf0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3237,9 +3237,9 @@ dependencies = [ [[package]] name = "starbase_shell" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960ed1ff84da880dc6eb7ae5262ac1468f4a6d82db59614adf13f5a62ed4b880" +checksum = "50174867b02331e50260305b647e91d323422ffcff3713d236931777c80608c3" dependencies = [ "miette", "regex", diff --git a/Cargo.toml b/Cargo.toml index 4b6d982b9..633174f23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ starbase_archive = { version = "0.8.0", features = [ ] } starbase_events = { version = "0.6.2" } starbase_sandbox = { version = "0.6.4" } -starbase_shell = { version = "0.4.0", features = ["miette"] } +starbase_shell = { version = "0.5.0", features = ["miette"] } starbase_styles = { version = "0.4.1" } starbase_utils = { version = "0.8.0", default-features = false, features = [ "json", diff --git a/README.md b/README.md index 618a035b7..984e1868d 100644 --- a/README.md +++ b/README.md @@ -30,3 +30,9 @@ proto is a pluggable next-generation version manager for multiple programming la - Python - Rust - ... [and more via plugins!](https://moonrepo.dev/docs/proto/tools) + +## Contributors + +Special thanks to the wonderful people who have contributed to this project: + +[![Contributors](https://contrib.rocks/image?repo=moonrepo/proto)](https://github.com/moonrepo/proto/graphs/contributors) diff --git a/crates/cli/src/app.rs b/crates/cli/src/app.rs index c74afc23f..d5a6d06de 100644 --- a/crates/cli/src/app.rs +++ b/crates/cli/src/app.rs @@ -114,7 +114,8 @@ impl App { pub enum Commands { #[command( name = "activate", - about = "Activate proto for the current shell session by prepending tool directories to PATH and setting environment variables." + about = "Activate proto for the current shell session by prepending tool directories to PATH and setting environment variables.", + long_about = "Activate proto for the current shell session by prepending tool directories to PATH and setting environment variables.\n\nThis should be ran within your shell profile.\nLearn more: https://moonrepo.dev/docs/proto/workflows" )] Activate(ActivateArgs), diff --git a/crates/cli/src/commands/activate.rs b/crates/cli/src/commands/activate.rs index 55e5df503..b8263172e 100644 --- a/crates/cli/src/commands/activate.rs +++ b/crates/cli/src/commands/activate.rs @@ -5,12 +5,11 @@ use miette::IntoDiagnostic; use proto_core::{detect_version, Id}; use serde::Serialize; use starbase::AppResult; -use starbase_shell::{Hook, ShellType}; -use starbase_styles::color; +use starbase_shell::{BoxedShell, Hook, ShellType, Statement}; use starbase_utils::json; -use std::path::PathBuf; +use std::env; +use std::path::{Path, PathBuf}; use tokio::task::{self, JoinHandle}; -use tracing::warn; #[derive(Default, Serialize)] struct ActivateItem { @@ -19,6 +18,15 @@ struct ActivateItem { pub paths: Vec, } +impl ActivateItem { + pub fn add_path(&mut self, path: &Path) { + // Only add paths that exist and are normalized + if let Ok(path) = path.canonicalize() { + self.paths.push(path); + } + } +} + #[derive(Default, Serialize)] struct ActivateInfo { pub env: IndexMap>, @@ -38,6 +46,34 @@ impl ActivateInfo { self.env.extend(item.env.clone()); self.tools.push(item); } + + pub fn export(self, shell: &BoxedShell) -> String { + let mut output = vec![]; + + for (key, value) in &self.env { + output.push(shell.format_env(key, value.as_deref())); + } + + let paths = self + .paths + .iter() + .filter_map(|path| path.to_str().map(|p| p.to_owned())) + .collect::>(); + + if !paths.is_empty() { + output.push(shell.format(Statement::PrependPath { + paths: &paths, + key: Some("PATH"), + orig_key: if env::var("__ORIG_PATH").is_ok() { + Some("__ORIG_PATH") + } else { + None + }, + })); + } + + output.join("\n") + } } #[derive(Args, Clone, Debug)] @@ -45,14 +81,26 @@ pub struct ActivateArgs { #[arg(help = "Shell to activate for")] shell: Option, - #[arg(long, help = "Print the info in JSON format")] + #[arg( + long, + help = "Print the activate instructions in shell specific-syntax" + )] + export: bool, + + #[arg(long, help = "Print the activate instructions in JSON format")] json: bool, + + #[arg(long, help = "Don't include ~/.proto/bin in path lookup")] + no_bin: bool, + + #[arg(long, help = "Don't include ~/.proto/shims in path lookup")] + no_shim: bool, } #[tracing::instrument(skip_all)] pub async fn activate(session: ProtoSession, args: ActivateArgs) -> AppResult { // Detect the shell that we need to activate for - let shell = match args.shell { + let shell_type = match args.shell { Some(value) => value, None => ShellType::try_detect()?, }; @@ -73,21 +121,25 @@ pub async fn activate(session: ProtoSession, args: ActivateArgs) -> AppResult { return Ok(item); }; - // This runs the resolve -> locate flow + // Resolve the version and locate executables if tool.is_setup(&version).await? { tool.locate_exes_dir().await?; tool.locate_globals_dirs().await?; + // Higher priority over globals if let Some(exe_dir) = tool.get_exes_dir() { - item.paths.push(exe_dir.to_owned()); + item.add_path(exe_dir); } - item.paths.extend(tool.get_globals_dirs().to_owned()); + for global_dir in tool.get_globals_dirs() { + item.add_path(global_dir); + } } - item.env - .extend(tool.proto.load_config()?.get_env_vars(Some(&tool.id))?); + // Inherit all environment variables for the config + let config = tool.proto.load_config()?; + item.env.extend(config.get_env_vars(Some(&tool.id))?); item.id = tool.id; Ok(item) @@ -97,15 +149,27 @@ pub async fn activate(session: ProtoSession, args: ActivateArgs) -> AppResult { // Aggregate our list of shell exports let mut info = ActivateInfo::default(); - info.paths.extend([ - session.env.store.shims_dir.clone(), - session.env.store.bin_dir.clone(), - ]); + // Put shims first so that they can detect newer versions + if !args.no_shim { + info.paths.push(session.env.store.shims_dir.clone()); + } for future in futures { - let item = future.await.into_diagnostic()??; + info.collect(future.await.into_diagnostic()??); + } - info.collect(item); + // Put bins last as a last resort lookup + if !args.no_bin { + info.paths.push(session.env.store.bin_dir.clone()); + } + + // Output/export the information for the chosen shell + let shell = shell_type.build(); + + if args.export { + println!("{}", info.export(&shell)); + + return Ok(()); } if args.json { @@ -114,25 +178,27 @@ pub async fn activate(session: ProtoSession, args: ActivateArgs) -> AppResult { return Ok(()); } - // Output in shell specific syntax - match shell.build().format_hook(Hook::OnChangeDir { - env: info.env.into_iter().collect(), - paths: info - .paths - .into_iter() - .map(|path| path.to_string_lossy().to_string()) - .collect(), + match shell.format_hook(Hook::OnChangeDir { + command: match shell_type { + // These operate on JSON + ShellType::Nu => format!("proto activate {} --json", shell_type), + // While these evaluate shell syntax + _ => format!("proto activate {} --export", shell_type), + }, prefix: "proto".into(), }) { Ok(output) => { println!("{output}"); } - Err(error) => { - warn!( - "Failed to run {}. Perhaps remove it for the time being?", - color::shell("proto activate") - ); - warn!("Reason: {}", color::muted_light(error.to_string())); + Err(_) => { + // Do nothing? This command is typically wrapped in `eval`, + // so these warnings would actually just trigger a syntax error. + + // warn!( + // "Failed to run {}. Perhaps remove it for the time being?", + // color::shell("proto activate") + // ); + // warn!("Reason: {}", color::muted_light(error.to_string())); } }; diff --git a/crates/cli/tests/snapshots/activate_test__activate__empty_output_if_no_tools.snap b/crates/cli/tests/snapshots/activate_test__activate__empty_output_if_no_tools.snap index d930fcb57..e8ccb9c3d 100644 --- a/crates/cli/tests/snapshots/activate_test__activate__empty_output_if_no_tools.snap +++ b/crates/cli/tests/snapshots/activate_test__activate__empty_output_if_no_tools.snap @@ -2,11 +2,12 @@ source: crates/cli/tests/activate_test.rs expression: "get_activate_output(&assert, &sandbox)" --- +export __ORIG_PATH="$PATH" + _proto_hook() { local previous_exit_status=$?; trap -- '' SIGINT; - - export PATH="/sandbox/.proto/shims:/sandbox/.proto/bin:$PATH"; + eval "$(proto activate bash --export)"; trap - SIGINT; return $previous_exit_status; }; diff --git a/crates/cli/tests/snapshots/activate_test__activate__supports_many_tools.snap b/crates/cli/tests/snapshots/activate_test__activate__supports_many_tools.snap index 2e96ed9fd..319f728bf 100644 --- a/crates/cli/tests/snapshots/activate_test__activate__supports_many_tools.snap +++ b/crates/cli/tests/snapshots/activate_test__activate__supports_many_tools.snap @@ -2,7 +2,8 @@ source: crates/cli/tests/activate_test.rs expression: "get_activate_output(&assert, &sandbox)" --- +set -gx __ORIG_PATH $PATH + function __proto_hook --on-variable PWD; - - set -gx PATH "/sandbox/.proto/shims:/sandbox/.proto/bin" $PATH; + proto activate fish --export | source end; diff --git a/crates/cli/tests/snapshots/activate_test__activate__supports_one_tool.snap b/crates/cli/tests/snapshots/activate_test__activate__supports_one_tool.snap index 710ceb393..79a4ad348 100644 --- a/crates/cli/tests/snapshots/activate_test__activate__supports_one_tool.snap +++ b/crates/cli/tests/snapshots/activate_test__activate__supports_one_tool.snap @@ -2,10 +2,11 @@ source: crates/cli/tests/activate_test.rs expression: "get_activate_output(&assert, &sandbox)" --- +export __ORIG_PATH="$PATH" + _proto_hook() { trap -- '' SIGINT - - export PATH="/sandbox/.proto/shims:/sandbox/.proto/bin:$PATH"; + eval "$(proto activate zsh --export)"; trap - SIGINT } typeset -ag precmd_functions diff --git a/crates/core/src/proto_config.rs b/crates/core/src/proto_config.rs index c2c574e1b..7a56edb31 100644 --- a/crates/core/src/proto_config.rs +++ b/crates/core/src/proto_config.rs @@ -208,7 +208,7 @@ impl ProtoConfig { self.plugins.insert( Id::raw("node"), PluginLocator::Url { - url: "https://github.com/moonrepo/node-plugin/releases/download/v0.11.3/node_plugin.wasm".into() + url: "https://github.com/moonrepo/node-plugin/releases/download/v0.11.4/node_plugin.wasm".into() } ); } @@ -218,7 +218,7 @@ impl ProtoConfig { self.plugins.insert( Id::raw(depman), PluginLocator::Url { - url: "https://github.com/moonrepo/node-plugin/releases/download/v0.11.3/node_depman_plugin.wasm".into() + url: "https://github.com/moonrepo/node-plugin/releases/download/v0.11.4/node_depman_plugin.wasm".into() } ); } @@ -228,7 +228,7 @@ impl ProtoConfig { self.plugins.insert( Id::raw("python"), PluginLocator::Url { - url: "https://github.com/moonrepo/python-plugin/releases/download/v0.10.3/python_plugin.wasm".into() + url: "https://github.com/moonrepo/python-plugin/releases/download/v0.10.4/python_plugin.wasm".into() } ); } @@ -237,7 +237,7 @@ impl ProtoConfig { self.plugins.insert( Id::raw("rust"), PluginLocator::Url { - url: "https://github.com/moonrepo/rust-plugin/releases/download/v0.10.4/rust_plugin.wasm".into() + url: "https://github.com/moonrepo/rust-plugin/releases/download/v0.10.5/rust_plugin.wasm".into() } ); } @@ -246,7 +246,7 @@ impl ProtoConfig { self.plugins.insert( Id::raw(SCHEMA_PLUGIN_KEY), PluginLocator::Url { - url: "https://github.com/moonrepo/schema-plugin/releases/download/v0.13.1/schema_plugin.wasm".into() + url: "https://github.com/moonrepo/schema-plugin/releases/download/v0.14.0/schema_plugin.wasm".into() } ); }