diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 115fa518c..0dd8a601e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: fail-fast: false steps: - uses: actions/checkout@v3 - - uses: moonrepo/setup-rust@v0 + - uses: moonrepo/setup-rust@v1 with: components: rustfmt - run: cargo fmt --all --check @@ -31,7 +31,7 @@ jobs: fail-fast: false steps: - uses: actions/checkout@v3 - - uses: moonrepo/setup-rust@v0 + - uses: moonrepo/setup-rust@v1 with: components: clippy - run: cargo clippy --workspace --all-targets @@ -44,7 +44,7 @@ jobs: fail-fast: false steps: - uses: actions/checkout@v3 - - uses: moonrepo/setup-rust@v0 + - uses: moonrepo/setup-rust@v1 with: bins: cargo-wasi, cargo-nextest cache: false @@ -70,7 +70,7 @@ jobs: fail-fast: false steps: - uses: actions/checkout@v3 - - uses: moonrepo/setup-rust@v0 + - uses: moonrepo/setup-rust@v1 with: bins: cargo-wasi cache: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a1f3c74f2..b9721b471 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -51,7 +51,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v3 - - uses: moonrepo/setup-rust@v0 + - uses: moonrepo/setup-rust@v1 with: cache: false - name: Install cargo-dist @@ -101,7 +101,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v3 - - uses: moonrepo/setup-rust@v0 + - uses: moonrepo/setup-rust@v1 with: cache: false - name: Install cargo-dist diff --git a/CHANGELOG.md b/CHANGELOG.md index a8ca1b0c6..a99670c38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,13 @@ - [Node.js (and package managers)](https://github.com/moonrepo/node-plugin) and [Rust](https://github.com/moonrepo/rust-plugin) are now powered by WASM plugins, and have been removed from core. - Please report any issues you encounter or discrepancies from the previous release! - Yarn 2+ is now installed using `@yarnpkg/cli-dist`. We no longer downgrade to the latest v1. +- Improved version detection and requirement resolution. - WASM API - Renamed host function `trace` to `host_log`. - Added `host_log!` and `exec_command!` macros for working with host functions. - Added `default_version` field to `ToolMetadataOutput`. - Added `home_dir` field to `LocateBinsInput`. + - Added `globals_prefix` field to `LocateBinsOutput`. - Updated `exec_command` with stream/inherit support. - Updated `bin_path` in `LocateBinsOutput` and `ShimConfig` to a `PathBuf`. diff --git a/crates/cli/src/commands/global.rs b/crates/cli/src/commands/global.rs index eb5c3d657..de50985c6 100644 --- a/crates/cli/src/commands/global.rs +++ b/crates/cli/src/commands/global.rs @@ -11,6 +11,7 @@ pub async fn global(tool_id: String, version: AliasOrVersion) -> SystemResult { tool.manifest.save()?; debug!( + version = version.to_string(), manifest = ?tool.manifest.path, "Wrote the global version", ); diff --git a/crates/cli/src/commands/list_global.rs b/crates/cli/src/commands/list_global.rs index f9c00547c..8f244d54f 100644 --- a/crates/cli/src/commands/list_global.rs +++ b/crates/cli/src/commands/list_global.rs @@ -17,6 +17,7 @@ pub async fn list_global(tool_id: String) -> SystemResult { debug!(globals_dir = ?globals_dir, "Finding global packages"); let mut bins = vec![]; + let globals_prefix = tool.get_globals_prefix(); if globals_dir.exists() { for file in fs::read_dir(globals_dir)? { @@ -27,11 +28,10 @@ pub async fn list_global(tool_id: String) -> SystemResult { let file_path = file.path(); let mut file_name = fs::file_name(&file_path); - if tool_id == "rust" { - if let Some(cargo_bin) = file_name.strip_prefix("cargo-") { - file_name = cargo_bin.to_owned(); + if let Some(prefix) = globals_prefix { + if let Some(prefixless) = file_name.strip_prefix(prefix) { + file_name = prefixless.to_owned(); } else { - // Non-cargo binaries are in this directory continue; } } diff --git a/crates/cli/src/commands/local.rs b/crates/cli/src/commands/local.rs index 75a9debd7..05dee78c8 100644 --- a/crates/cli/src/commands/local.rs +++ b/crates/cli/src/commands/local.rs @@ -13,7 +13,11 @@ pub async fn local(tool_id: String, version: AliasOrVersion) -> SystemResult { config.tools.insert(tool_id, version.clone()); config.save()?; - debug!(config = ?config.path, "Wrote the local version"); + debug!( + version = version.to_string(), + config = ?config.path, + "Wrote the local version", + ); info!( "Set the local {} version to {}", diff --git a/crates/cli/src/commands/plugins.rs b/crates/cli/src/commands/plugins.rs index cfad3d34f..630af2d1e 100644 --- a/crates/cli/src/commands/plugins.rs +++ b/crates/cli/src/commands/plugins.rs @@ -45,7 +45,7 @@ pub async fn plugins(json: bool) -> SystemResult { items.push(PluginItem { id, - name: tool.get_name(), + name: tool.get_name().to_owned(), // version: String::new(), locator, }); diff --git a/crates/cli/src/commands/run.rs b/crates/cli/src/commands/run.rs index 4ae552dd7..cd9dbff70 100644 --- a/crates/cli/src/commands/run.rs +++ b/crates/cli/src/commands/run.rs @@ -23,7 +23,7 @@ pub async fn run( if !tool.is_setup(&version).await? { if !user_config.auto_install { return Err(ProtoError::MissingToolForRun { - tool: tool.get_name(), + tool: tool.get_name().to_owned(), version: version.to_string(), command: format!("proto install {} {}", tool.id, tool.get_resolved_version()), } diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index c144ab898..3e51386d9 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -8,6 +8,10 @@ pub enum ProtoError { #[error("{0}")] Message(String), + #[diagnostic(code(proto::tool::invalid_dir))] + #[error("Tool inventory directory has been overridden but is not an absolute path. Only absolute paths are supported.")] + AbsoluteInventoryDir, + #[diagnostic( code(proto::download::missing), help = "Please refer to the tool's official documentation." diff --git a/crates/core/src/tool.rs b/crates/core/src/tool.rs index 1933cc405..62eb40d29 100644 --- a/crates/core/src/tool.rs +++ b/crates/core/src/tool.rs @@ -25,30 +25,34 @@ use warpgate::PluginContainer; pub struct Tool { pub id: String, pub manifest: ToolManifest, + pub metadata: ToolMetadataOutput, pub plugin: PluginContainer<'static>, pub proto: ProtoEnvironment, pub version: Option, bin_path: Option, globals_dir: Option, + globals_prefix: Option, } impl Tool { pub fn load(id: &str, proto: &ProtoEnvironment, wasm: Wasm) -> miette::Result { let manifest = ToolManifest::load_from(proto.tools_dir.join(id))?; let plugin = Self::load_plugin(id, proto, wasm)?; - let tool = Tool { - id: id.to_owned(), + + let mut tool = Tool { bin_path: None, globals_dir: None, + globals_prefix: None, + id: id.to_owned(), manifest, + metadata: ToolMetadataOutput::default(), plugin, proto: proto.to_owned(), version: None, }; - // Load metadata on load and make available - tool.get_metadata()?; + tool.register_tool()?; Ok(tool) } @@ -98,15 +102,24 @@ impl Tool { self.globals_dir.as_deref() } + /// Return a string that all globals are prefixed with. Will be used for filtering and listing. + pub fn get_globals_prefix(&self) -> Option<&str> { + self.globals_prefix.as_deref() + } + /// Return an absolute path to the tool's inventory directory. The inventory houses /// installed versions, the manifest, and more. pub fn get_inventory_dir(&self) -> PathBuf { + if let Some(dir) = &self.metadata.inventory.override_dir { + return dir.to_owned(); + } + self.proto.tools_dir.join(&self.id) } /// Return a human readable name for the tool. - pub fn get_name(&self) -> String { - self.get_metadata().unwrap().name + pub fn get_name(&self) -> &str { + &self.metadata.name } /// Return the resolved version or "latest". @@ -127,12 +140,6 @@ impl Tool { return Some(local); } - // let global = self.proto.bin_dir.join(get_shim_file_name(&self.id, true)); - - // if global.exists() { - // return Some(global); - // } - None } @@ -143,8 +150,13 @@ impl Tool { /// Return an absolute path to the tool's install directory for the currently resolved version. pub fn get_tool_dir(&self) -> PathBuf { - self.get_inventory_dir() - .join(self.get_resolved_version().to_string()) + let mut version = self.get_resolved_version().to_string(); + + if let Some(suffix) = &self.metadata.inventory.version_suffix { + version = format!("{}{}", version, suffix); + } + + self.get_inventory_dir().join(version) } /// Explicitly set the version to use. @@ -154,7 +166,7 @@ impl Tool { /// Disable progress bars when installing or uninstalling the tool. pub fn disable_progress_bars(&self) -> bool { - false + self.metadata.inventory.disable_progress_bars } } @@ -162,13 +174,13 @@ impl Tool { impl Tool { /// Return environment information to pass to WASM plugin functions. - pub fn get_environment(&self) -> miette::Result { + pub fn create_environment(&self) -> miette::Result { Ok(Environment { arch: HostArch::from_str(consts::ARCH).into_diagnostic()?, id: self.id.clone(), os: HostOS::from_str(consts::OS).into_diagnostic()?, vars: self - .get_metadata()? + .metadata .env_vars .iter() .filter_map(|var| env::var(var).ok().map(|value| (var.to_owned(), value))) @@ -177,20 +189,30 @@ impl Tool { }) } - /// Return metadata about the tool and plugin - pub fn get_metadata(&self) -> miette::Result { - self.plugin.cache_func_with( + /// Register the tool by loading initial metadata and persisting it. + pub fn register_tool(&mut self) -> miette::Result<()> { + let mut metadata: ToolMetadataOutput = self.plugin.cache_func_with( "register_tool", ToolMetadataInput { id: self.id.clone(), - env: Environment { - arch: HostArch::from_str(consts::ARCH).into_diagnostic()?, - id: self.id.clone(), - os: HostOS::from_str(consts::OS).into_diagnostic()?, - ..Environment::default() - }, + env: self.create_environment()?, + home_dir: self.plugin.to_virtual_path(&self.proto.home), }, - ) + )?; + + if let Some(override_dir) = &metadata.inventory.override_dir { + let inventory_dir = self.plugin.from_virtual_path(override_dir); + + if inventory_dir.is_absolute() { + metadata.inventory.override_dir = Some(inventory_dir); + } else { + return Err(ProtoError::AbsoluteInventoryDir.into()); + } + } + + self.metadata = metadata; + + Ok(()) } } @@ -237,7 +259,7 @@ impl Tool { versions = Some(self.plugin.cache_func_with( "load_versions", LoadVersionsInput { - env: self.get_environment()?, + env: self.create_environment()?, initial: initial_version.to_string(), }, )?); @@ -248,7 +270,7 @@ impl Tool { json::write_file(cache_path, &versions, false)?; let mut resolver = VersionResolver::from_output(versions); - resolver.inherit_aliases(&self.manifest)?; + resolver.with_manifest(&self.manifest)?; Ok(resolver) } @@ -282,7 +304,7 @@ impl Tool { let result: ResolveVersionOutput = self.plugin.call_func_with( "resolve_version", ResolveVersionInput { - env: self.get_environment()?, + env: self.create_environment()?, initial: initial_version.to_string(), }, )?; @@ -356,14 +378,14 @@ impl Tool { continue; } - let content = fs::read_file(&file_path)?; + let content = fs::read_file(&file_path)?.trim().to_owned(); let version = if has_parser { let result: ParseVersionFileOutput = self.plugin.call_func_with( "parse_version_file", ParseVersionFileInput { content, - env: self.get_environment()?, + env: self.create_environment()?, file: file.clone(), }, )?; @@ -417,7 +439,7 @@ impl Tool { checksum, checksum_file: self.plugin.to_virtual_path(checksum_file), download_file: self.plugin.to_virtual_path(download_file), - env: self.get_environment()?, + env: self.create_environment()?, }, )?; @@ -463,7 +485,7 @@ impl Tool { let options: DownloadPrebuiltOutput = self.plugin.cache_func_with( "download_prebuilt", DownloadPrebuiltInput { - env: self.get_environment()?, + env: self.create_environment()?, }, )?; @@ -513,7 +535,7 @@ impl Tool { self.plugin.call_func_without_output( "unpack_archive", UnpackArchiveInput { - env: self.get_environment()?, + env: self.create_environment()?, input_file: self.plugin.to_virtual_path(&download_file), output_dir: self.plugin.to_virtual_path(install_dir), }, @@ -563,7 +585,7 @@ impl Tool { let result: NativeInstallOutput = self.plugin.call_func_with( "native_install", NativeInstallInput { - env: self.get_environment()?, + env: self.create_environment()?, home_dir: self.plugin.to_virtual_path(&self.proto.home), tool_dir: self.plugin.to_virtual_path(&install_dir), }, @@ -610,7 +632,7 @@ impl Tool { /// Find the absolute file path to the tool's binary that will be executed. pub async fn locate_bins(&mut self) -> miette::Result<()> { let mut options = LocateBinsOutput::default(); - let install_dir = self.get_tool_dir(); + let tool_dir = self.get_tool_dir(); debug!(tool = &self.id, "Locating binaries for tool"); @@ -618,9 +640,9 @@ impl Tool { options = self.plugin.cache_func_with( "locate_bins", LocateBinsInput { - env: self.get_environment()?, + env: self.create_environment()?, home_dir: self.plugin.to_virtual_path(&self.proto.home), - tool_dir: self.plugin.to_virtual_path(&install_dir), + tool_dir: self.plugin.to_virtual_path(&tool_dir), }, )?; } @@ -631,10 +653,10 @@ impl Tool { if bin.is_absolute() { bin } else { - install_dir.join(bin) + tool_dir.join(bin) } } else { - install_dir.join(&self.id) + tool_dir.join(&self.id) }; debug!(tool = &self.id, bin_path = ?bin_path, "Found a potential binary"); @@ -646,7 +668,7 @@ impl Tool { } Err(ProtoError::MissingToolBin { - tool: self.get_name(), + tool: self.get_name().to_owned(), bin: bin_path, } .into()) @@ -664,12 +686,14 @@ impl Tool { let options: LocateBinsOutput = self.plugin.cache_func_with( "locate_bins", LocateBinsInput { - env: self.get_environment()?, + env: self.create_environment()?, home_dir: self.plugin.to_virtual_path(&self.proto.home), tool_dir: self.plugin.to_virtual_path(&install_dir), }, )?; + self.globals_prefix = options.globals_prefix; + // Find a globals directory that packages are installed to let lookup_count = options.globals_lookup_dirs.len() - 1; @@ -748,7 +772,7 @@ impl Tool { let shim_configs: CreateShimsOutput = self.plugin.cache_func_with( "create_shims", CreateShimsInput { - env: self.get_environment()?, + env: self.create_environment()?, }, )?; @@ -841,10 +865,9 @@ impl Tool { // Only insert if a version if let AliasOrVersion::Version(version) = self.get_resolved_version() { - let options = self.get_metadata()?; let mut default = None; - if let Some(default_version) = options.default_version { + if let Some(default_version) = &self.metadata.default_version { default = Some(AliasOrVersion::parse(default_version)?); } diff --git a/crates/core/src/version_detector.rs b/crates/core/src/version_detector.rs index 34f4e4d76..9923e1df1 100644 --- a/crates/core/src/version_detector.rs +++ b/crates/core/src/version_detector.rs @@ -1,9 +1,7 @@ use crate::error::ProtoError; use crate::tool::Tool; -use crate::tool_manifest::ToolManifest; use crate::tools_config::ToolsConfig; -use crate::version::{AliasOrVersion, VersionType}; -use crate::version_resolver::resolve_version; +use crate::version::VersionType; use std::{env, path::Path}; use tracing::{debug, trace}; @@ -73,18 +71,14 @@ pub async fn detect_version( // Detect using the tool if let Some(detected_version) = tool.detect_version_from(dir).await? { - if let Some(eco_version) = - expand_detected_version(&detected_version, &tool.manifest)? - { - debug!( - tool = &tool.id, - version = ?eco_version, - "Detected version from tool's ecosystem" - ); - - candidate = Some(eco_version.to_implicit_type()); - break; - } + debug!( + tool = &tool.id, + version = ?detected_version, + "Detected version from tool's ecosystem" + ); + + candidate = Some(detected_version); + break; } current_dir = dir.parent(); @@ -118,22 +112,3 @@ pub async fn detect_version( .into() }) } - -pub fn expand_detected_version( - candidate: &VersionType, - manifest: &ToolManifest, -) -> miette::Result> { - // We don't resolve explicit aliases as some languages require them. - // For example, "stable" or "nightly" in Rust. - if let VersionType::Alias(alias) = candidate { - return Ok(Some(AliasOrVersion::Alias(alias.to_owned()))); - } - - let versions = manifest.installed_versions.iter().collect::>(); - - if let Ok(version) = resolve_version(candidate, &versions, &manifest.aliases) { - return Ok(Some(AliasOrVersion::Version(version))); - } - - Ok(None) -} diff --git a/crates/core/src/version_resolver.rs b/crates/core/src/version_resolver.rs index 9324df73f..ec6d43cca 100644 --- a/crates/core/src/version_resolver.rs +++ b/crates/core/src/version_resolver.rs @@ -1,16 +1,19 @@ +use crate::error::ProtoError; +use crate::tool_manifest::ToolManifest; use crate::version::VersionType; -use crate::{ProtoError, ToolManifest}; use proto_pdk_api::LoadVersionsOutput; use semver::{Version, VersionReq}; use std::collections::BTreeMap; #[derive(Debug, Default)] -pub struct VersionResolver { +pub struct VersionResolver<'tool> { pub aliases: BTreeMap, pub versions: Vec, + + manifest: Option<&'tool ToolManifest>, } -impl VersionResolver { +impl<'tool> VersionResolver<'tool> { pub fn from_output(output: LoadVersionsOutput) -> Self { let mut resolver = Self::default(); resolver.versions.extend(output.versions); @@ -34,34 +37,28 @@ impl VersionResolver { resolver } - pub fn inherit_aliases(&mut self, manifest: &ToolManifest) -> miette::Result<()> { - for (alias, version) in &manifest.aliases { - // Don't override existing aliases - self.aliases - .entry(alias.to_owned()) - .or_insert_with(|| version.to_owned()); - } + pub fn with_manifest(&mut self, manifest: &'tool ToolManifest) -> miette::Result<()> { + self.manifest = Some(manifest); Ok(()) } pub fn resolve(&self, candidate: &VersionType) -> miette::Result { - resolve_version( - candidate, - &self.versions.iter().collect::>(), - &self.aliases, - ) + resolve_version(candidate, &self.versions, &self.aliases, self.manifest) } } -pub fn match_highest_version(req: &VersionReq, versions: &[&Version]) -> Option { +pub fn match_highest_version<'l, I>(req: &'l VersionReq, versions: I) -> Option +where + I: IntoIterator, +{ let mut highest_match: Option = None; for version in versions { if req.matches(version) - && (highest_match.is_none() || highest_match.as_ref().is_some_and(|v| *version > v)) + && (highest_match.is_none() || highest_match.as_ref().is_some_and(|v| version > v)) { - highest_match = Some((**version).clone()); + highest_match = Some((*version).clone()); } } @@ -70,22 +67,38 @@ pub fn match_highest_version(req: &VersionReq, versions: &[&Version]) -> Option< pub fn resolve_version( candidate: &VersionType, - versions: &[&Version], + versions: &[Version], aliases: &BTreeMap, + manifest: Option<&ToolManifest>, ) -> miette::Result { match &candidate { VersionType::Alias(alias) => { if let Some(alias_type) = aliases.get(alias) { - return resolve_version(alias_type, versions, aliases); + return resolve_version(alias_type, versions, aliases, manifest); } } VersionType::ReqAll(req) => { + // Prefer installed versions + if let Some(manifest) = manifest { + if let Some(version) = match_highest_version(req, &manifest.installed_versions) { + return Ok(version); + } + } + if let Some(version) = match_highest_version(req, versions) { return Ok(version); } } VersionType::ReqAny(reqs) => { for req in reqs { + // Prefer installed versions + if let Some(manifest) = manifest { + if let Some(version) = match_highest_version(req, &manifest.installed_versions) + { + return Ok(version); + } + } + if let Some(version) = match_highest_version(req, versions) { return Ok(version); } @@ -93,7 +106,7 @@ pub fn resolve_version( } VersionType::Version(ver) => { for version in versions { - if ver == *version { + if ver == version { return Ok(ver.to_owned()); } } diff --git a/crates/core/tests/version_resolver_test.rs b/crates/core/tests/version_resolver_test.rs index 0938ad8fa..b0b49246f 100644 --- a/crates/core/tests/version_resolver_test.rs +++ b/crates/core/tests/version_resolver_test.rs @@ -42,8 +42,9 @@ mod version_resolver { assert_eq!( resolve_version( &VersionType::Alias("latest".into()), - &versions.iter().collect::>(), - &aliases + &versions, + &aliases, + None, ) .unwrap(), Version::new(10, 0, 0) @@ -52,8 +53,9 @@ mod version_resolver { assert_eq!( resolve_version( &VersionType::Alias("stable".into()), - &versions.iter().collect::>(), - &aliases + &versions, + &aliases, + None, ) .unwrap(), Version::new(10, 0, 0) @@ -68,8 +70,9 @@ mod version_resolver { resolve_version( &VersionType::Alias("unknown".into()), - &versions.iter().collect::>(), + &versions, &aliases, + None, ) .unwrap(); } @@ -82,8 +85,9 @@ mod version_resolver { resolve_version( &VersionType::Alias("no-alias".into()), - &versions.iter().collect::>(), + &versions, &aliases, + None, ) .unwrap(); } @@ -96,8 +100,9 @@ mod version_resolver { resolve_version( &VersionType::Alias("no-version".into()), - &versions.iter().collect::>(), + &versions, &aliases, + None, ) .unwrap(); } @@ -110,8 +115,9 @@ mod version_resolver { assert_eq!( resolve_version( &VersionType::Version(Version::new(1, 10, 5)), - &versions.iter().collect::>(), - &aliases + &versions, + &aliases, + None, ) .unwrap(), Version::new(1, 10, 5) @@ -120,8 +126,9 @@ mod version_resolver { assert_eq!( resolve_version( &VersionType::Version(Version::new(8, 0, 0)), - &versions.iter().collect::>(), - &aliases + &versions, + &aliases, + None, ) .unwrap(), Version::new(8, 0, 0) @@ -136,8 +143,9 @@ mod version_resolver { assert_eq!( resolve_version( &VersionType::parse("1.2").unwrap(), - &versions.iter().collect::>(), - &aliases + &versions, + &aliases, + None, ) .unwrap(), Version::new(1, 2, 3) @@ -146,20 +154,16 @@ mod version_resolver { assert_eq!( resolve_version( &VersionType::parse("1.0").unwrap(), - &versions.iter().collect::>(), - &aliases + &versions, + &aliases, + None, ) .unwrap(), Version::new(1, 0, 0) ); assert_eq!( - resolve_version( - &VersionType::parse("1").unwrap(), - &versions.iter().collect::>(), - &aliases - ) - .unwrap(), + resolve_version(&VersionType::parse("1").unwrap(), &versions, &aliases, None,).unwrap(), Version::new(1, 10, 5) ); } @@ -172,8 +176,9 @@ mod version_resolver { assert_eq!( resolve_version( &VersionType::parse("v8.0.0").unwrap(), - &versions.iter().collect::>(), - &aliases + &versions, + &aliases, + None, ) .unwrap(), Version::new(8, 0, 0) @@ -182,8 +187,9 @@ mod version_resolver { assert_eq!( resolve_version( &VersionType::parse("V8").unwrap(), - &versions.iter().collect::>(), - &aliases + &versions, + &aliases, + None, ) .unwrap(), Version::new(8, 0, 0) @@ -198,8 +204,9 @@ mod version_resolver { resolve_version( &VersionType::Version(Version::new(20, 0, 0)), - &versions.iter().collect::>(), + &versions, &aliases, + None, ) .unwrap(); } @@ -212,8 +219,9 @@ mod version_resolver { assert_eq!( resolve_version( &VersionType::parse("^8").unwrap(), - &versions.iter().collect::>(), - &aliases + &versions, + &aliases, + None, ) .unwrap(), Version::new(8, 0, 0) @@ -222,8 +230,9 @@ mod version_resolver { assert_eq!( resolve_version( &VersionType::parse("~1.1").unwrap(), - &versions.iter().collect::>(), - &aliases + &versions, + &aliases, + None, ) .unwrap(), Version::new(1, 1, 1) @@ -232,8 +241,9 @@ mod version_resolver { assert_eq!( resolve_version( &VersionType::parse(">1 <10").unwrap(), - &versions.iter().collect::>(), - &aliases + &versions, + &aliases, + None, ) .unwrap(), Version::new(8, 0, 0) @@ -242,8 +252,9 @@ mod version_resolver { assert_eq!( resolve_version( &VersionType::parse(">1, <10").unwrap(), - &versions.iter().collect::>(), - &aliases + &versions, + &aliases, + None, ) .unwrap(), Version::new(8, 0, 0) @@ -253,8 +264,9 @@ mod version_resolver { assert_eq!( resolve_version( &VersionType::parse("^1").unwrap(), - &versions.iter().collect::>(), - &aliases + &versions, + &aliases, + None, ) .unwrap(), Version::new(1, 10, 5) @@ -262,12 +274,7 @@ mod version_resolver { // Star (latest) assert_eq!( - resolve_version( - &VersionType::parse("*").unwrap(), - &versions.iter().collect::>(), - &aliases - ) - .unwrap(), + resolve_version(&VersionType::parse("*").unwrap(), &versions, &aliases, None,).unwrap(), Version::new(10, 0, 0) ); } @@ -280,8 +287,9 @@ mod version_resolver { resolve_version( &VersionType::parse("^20").unwrap(), - &versions.iter().collect::>(), + &versions, &aliases, + None, ) .unwrap(); } @@ -294,8 +302,9 @@ mod version_resolver { assert_eq!( resolve_version( &VersionType::parse("^1 || ^6 || ^8").unwrap(), - &versions.iter().collect::>(), - &aliases + &versions, + &aliases, + None, ) .unwrap(), Version::new(8, 0, 0) @@ -310,8 +319,9 @@ mod version_resolver { resolve_version( &VersionType::parse("^3 || ^5 || ^9").unwrap(), - &versions.iter().collect::>(), + &versions, &aliases, + None, ) .unwrap(); } @@ -322,12 +332,7 @@ mod version_resolver { let aliases = create_aliases(); for req in [">= 1.5.9", "> 1.5.0", ">= 1.2", "> 1.2", "< 1.2", "<= 1.2"] { - resolve_version( - &VersionType::parse(req).unwrap(), - &versions.iter().collect::>(), - &aliases, - ) - .unwrap(); + resolve_version(&VersionType::parse(req).unwrap(), &versions, &aliases, None).unwrap(); } } } diff --git a/crates/pdk-api/src/api.rs b/crates/pdk-api/src/api.rs index b0028f9a2..edbbc09e6 100644 --- a/crates/pdk-api/src/api.rs +++ b/crates/pdk-api/src/api.rs @@ -67,6 +67,26 @@ json_struct!( /// Current environment. pub env: Environment, + + /// Virtual path to the user's home directory. + pub home_dir: PathBuf, + } +); + +json_struct!( + /// Controls aspects of the tool inventory. + pub struct ToolInventoryMetadata { + /// Disable progress bars when installing or uninstalling tools. + pub disable_progress_bars: bool, + + /// Override the tool inventory directory (where all versions are installed). + /// This is an advanced feature and should only be used when absolutely necessary. + #[serde(skip_serializing_if = "Option::is_none")] + pub override_dir: Option, + + /// Suffix to append to all versions when labeling directories. + #[serde(skip_serializing_if = "Option::is_none")] + pub version_suffix: Option, } ); @@ -74,12 +94,16 @@ json_struct!( /// Output returned by the `register_tool` function. pub struct ToolMetadataOutput { /// Default alias or version to use as a fallback. + #[serde(skip_serializing_if = "Option::is_none")] pub default_version: Option, /// Environment variables that should be extracted /// and passed to other function call inputs. pub env_vars: Vec, + /// Controls aspects of the tool inventory. + pub inventory: ToolInventoryMetadata, + /// Human readable name of the tool. pub name: String, @@ -250,6 +274,11 @@ json_struct!( /// List of directory paths to find the globals installation directory. /// Each path supports environment variable expansion. pub globals_lookup_dirs: Vec, + + /// A string that all global binaries are prefixed with, and will be removed + /// when listing and filtering available globals. + #[serde(skip_serializing_if = "Option::is_none")] + pub globals_prefix: Option, } ); diff --git a/crates/pdk/src/macros.rs b/crates/pdk/src/macros.rs index 06c16129b..95bbdb6dd 100644 --- a/crates/pdk/src/macros.rs +++ b/crates/pdk/src/macros.rs @@ -20,7 +20,7 @@ macro_rules! permutations { macro_rules! exec_command { ($cmd:expr, [ $($arg:expr),* ]) => { unsafe { - exec_command(Json(ExecCommandInput::new($cmd, [ + exec_command(Json(ExecCommandInput::pipe($cmd, [ $($arg),* ])))?.0 }