diff --git a/crates/cli/src/commands/outdated.rs b/crates/cli/src/commands/outdated.rs index d5ebb8f0..cf7ce58e 100644 --- a/crates/cli/src/commands/outdated.rs +++ b/crates/cli/src/commands/outdated.rs @@ -1,7 +1,7 @@ use crate::error::ProtoCliError; use clap::Args; use miette::IntoDiagnostic; -use proto_core::{load_tool, UnresolvedVersionSpec, VersionSpec}; +use proto_core::{load_tool_with_proto, ProtoEnvironment, UnresolvedVersionSpec, VersionSpec}; use serde::Serialize; use starbase::system; use starbase_styles::color::{self, OwoStyle}; @@ -34,26 +34,23 @@ pub struct OutdatedItem { #[system] pub async fn outdated(args: ArgsRef) { - let mut tools_config = ToolsConfig::load_closest()?; - let initial_version = UnresolvedVersionSpec::default(); // latest + let proto = ProtoEnvironment::new()?; + let config = proto.load_config()?; - if tools_config.tools.is_empty() { - return Err(ProtoCliError::NoConfiguredTools { - path: tools_config.path, - } - .into()); + if config.versions.is_empty() { + return Err(ProtoCliError::NoConfiguredTools.into()); } if !args.json { info!("Checking for newer versions..."); - info!("Loading {}", color::path(&tools_config.path)); } let mut items = HashMap::new(); let mut tool_versions = HashMap::new(); + let initial_version = UnresolvedVersionSpec::default(); // latest - for (tool_id, config_version) in &tools_config.tools { - let mut tool = load_tool(tool_id).await?; + for (tool_id, config_version) in &config.versions { + let mut tool = load_tool_with_proto(tool_id, &proto).await?; tool.disable_caching(); debug!("Checking {}", tool.get_name()); @@ -119,8 +116,9 @@ pub async fn outdated(args: ArgsRef) { } if args.update { - tools_config.tools.extend(tool_versions); - tools_config.save()?; + // TODO update + // tools_config.tools.extend(tool_versions); + // tools_config.save()?; } if args.json { diff --git a/crates/cli/src/error.rs b/crates/cli/src/error.rs index 08ffff3f..bb41a6e6 100644 --- a/crates/cli/src/error.rs +++ b/crates/cli/src/error.rs @@ -31,8 +31,8 @@ pub enum ProtoCliError { MissingRunAltBin { bin: String, path: PathBuf }, #[diagnostic(code(proto::cli::no_configured_tools))] - #[error("No tools have been configured in {}.", .path.style(Style::Path))] - NoConfiguredTools { path: PathBuf }, + #[error("No tools have been configured in {}.", PROTO_CONFIG_NAME.style(Style::File),)] + NoConfiguredTools, #[diagnostic(code(proto::cli::no_installed_tools))] #[error("No tools have been installed.")] diff --git a/crates/cli/src/helpers.rs b/crates/cli/src/helpers.rs index 53e706c9..1f2f60b2 100644 --- a/crates/cli/src/helpers.rs +++ b/crates/cli/src/helpers.rs @@ -93,10 +93,6 @@ pub async fn load_configured_tools() -> miette::Result> { ToolsLoader::new()?.load_tools().await } -pub async fn load_configured_tools_with_filters(filter: HashSet<&Id>) -> miette::Result> { - ToolsLoader::new()?.load_tools_with_filters(filter).await -} - pub struct ToolsLoader { pub proto: Arc, } diff --git a/crates/core/src/proto.rs b/crates/core/src/proto.rs index b029a6ba..5900486a 100644 --- a/crates/core/src/proto.rs +++ b/crates/core/src/proto.rs @@ -1,7 +1,5 @@ use crate::helpers::{get_home_dir, get_proto_home, is_offline}; -use crate::proto_config::ProtoConfigManager; -use crate::user_config::UserConfig; -use crate::ProtoConfig; +use crate::proto_config::{ProtoConfig, ProtoConfigManager, PROTO_CONFIG_ROOT_NAME}; use once_cell::sync::OnceCell; use std::collections::BTreeMap; use std::env; @@ -24,7 +22,6 @@ pub struct ProtoEnvironment { http_client: Arc>, plugin_loader: Arc>, test_mode: bool, - user_config: Arc>, } impl ProtoEnvironment { @@ -56,7 +53,6 @@ impl ProtoEnvironment { http_client: Arc::new(OnceCell::new()), plugin_loader: Arc::new(OnceCell::new()), test_mode: false, - user_config: Arc::new(OnceCell::new()), }) } @@ -92,34 +88,22 @@ impl ProtoEnvironment { self.config_manager.get_or_try_init(|| { // Don't traverse passed the home directory, // but only if working directory is within it! - let end = if self.cwd.starts_with(&self.home) { + let end_dir = if self.cwd.starts_with(&self.home) { Some(self.home.as_path()) } else { None }; - ProtoConfigManager::load(&self.cwd, end) - }) - } + let mut manager = ProtoConfigManager::load(&self.cwd, end_dir)?; - #[deprecated] - pub fn load_user_config(&self) -> miette::Result<&UserConfig> { - self.user_config.get_or_try_init(|| { - // if self.test_mode || env::var("PROTO_TEST_USER_CONFIG").is_ok() { - // Ok(UserConfig::default()) - // } else { - UserConfig::load_from(&self.root) - // } - }) - } + // Always load the proto home/root config + manager.files.insert( + PathBuf::from(PROTO_CONFIG_ROOT_NAME), + ProtoConfigManager::load_from(&self.root)?, + ); - pub fn take_user_config(&mut self) -> UserConfig { - // This is safe since we only ever have 1 instance of the struct, - // and this method requires &mut. - Arc::get_mut(&mut self.user_config) - .unwrap() - .take() - .expect("User config has not been loaded!") + Ok(manager) + }) } } diff --git a/crates/core/src/proto_config.rs b/crates/core/src/proto_config.rs index 092064b2..c8140f91 100644 --- a/crates/core/src/proto_config.rs +++ b/crates/core/src/proto_config.rs @@ -12,6 +12,7 @@ use version_spec::*; use warpgate::{HttpOptions, Id, PluginLocator}; pub const PROTO_CONFIG_NAME: &str = ".prototools"; +pub const PROTO_CONFIG_ROOT_NAME: &str = "/"; pub const SCHEMA_PLUGIN_KEY: &str = "internal-schema"; derive_enum!( @@ -31,7 +32,7 @@ derive_enum!( } ); -#[derive(Config, Serialize)] +#[derive(Config, Debug, Serialize)] #[config(allow_unknown_fields, rename_all = "kebab-case")] pub struct ProtoToolConfig { pub aliases: BTreeMap, @@ -41,7 +42,7 @@ pub struct ProtoToolConfig { pub config: BTreeMap, } -#[derive(Config)] +#[derive(Config, Debug)] #[config(rename_all = "kebab-case")] pub struct ProtoSettingsConfig { #[setting(env = "PROTO_AUTO_CLEAN", parse_env = env::parse_bool)] @@ -59,7 +60,7 @@ pub struct ProtoSettingsConfig { pub http: HttpOptions, } -#[derive(Config)] +#[derive(Config, Debug)] #[config(allow_unknown_fields, rename_all = "kebab-case")] pub struct ProtoConfig { pub plugins: BTreeMap, @@ -158,7 +159,13 @@ impl ProtoConfig { } } +#[derive(Debug)] pub struct ProtoConfigManager { + // Paths are sorted from smallest to largest components, + // so we need to traverse in reverse order. Furthermore, + // the special config at `~/.proto/.prototools` is mapped + // "/" to give it the lowest precedence. We also don't + // expect users to put configs in the actual root... pub files: BTreeMap, merged_config: Arc>, diff --git a/crates/core/src/tool_loader.rs b/crates/core/src/tool_loader.rs index 682d6040..125b538b 100644 --- a/crates/core/src/tool_loader.rs +++ b/crates/core/src/tool_loader.rs @@ -50,7 +50,7 @@ pub fn locate_tool( debug!(tool = id.as_str(), "Finding a configured plugin"); // Check config files for plugins - for (file, config) in &configs.files { + for (file, config) in configs.files.iter().rev() { if let Some(plugins) = &config.plugins { if let Some(maybe_locator) = plugins.get(id) { debug!(file = ?file, plugin = maybe_locator.to_string(), "Found a plugin"); @@ -146,9 +146,14 @@ pub async fn load_tool_from_locator( Ok(tool) } -pub async fn load_tool(id: &Id) -> miette::Result { - let proto = ProtoEnvironment::new()?; +pub async fn load_tool_with_proto(id: &Id, proto: &ProtoEnvironment) -> miette::Result { let locator = locate_tool(id, &proto, false)?; load_tool_from_locator(id, proto, locator).await } + +pub async fn load_tool(id: &Id) -> miette::Result { + let proto = ProtoEnvironment::new()?; + + load_tool_with_proto(id, &proto).await +} diff --git a/crates/core/src/version_detector.rs b/crates/core/src/version_detector.rs index d6febda7..90d77d58 100644 --- a/crates/core/src/version_detector.rs +++ b/crates/core/src/version_detector.rs @@ -9,7 +9,7 @@ pub async fn detect_version_first_available( tool: &Tool, config_manager: &ProtoConfigManager, ) -> miette::Result> { - for (file, local_config) in &config_manager.files { + for (file, local_config) in config_manager.files.iter().rev() { if let Some(versions) = &local_config.versions { if let Some(version) = versions.get(&tool.id) { debug!( @@ -45,7 +45,7 @@ pub async fn detect_version_prefer_prototools( config_manager: &ProtoConfigManager, ) -> miette::Result> { // Check config files first - for (file, local_config) in &config_manager.files { + for (file, local_config) in config_manager.files.iter().rev() { if let Some(versions) = &local_config.versions { if let Some(version) = versions.get(&tool.id) { debug!( @@ -61,7 +61,7 @@ pub async fn detect_version_prefer_prototools( } // Then check the ecosystem - for file in config_manager.files.keys() { + for (file, _) in config_manager.files.iter().rev() { let dir = file.parent().unwrap(); if let Some(version) = tool.detect_version_from(dir).await? {