diff --git a/CHANGELOG.md b/CHANGELOG.md index cf65ad402..a4a7aec73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,18 @@ #### 🚀 Updates - Added colors to command line `--help` menus. +- Updated the following locations to support partial versions and aliases: + - Tool versions in `.prototools`. + - Pinning a default version with `proto install --pin`. + - Setting global version with `proto global`. + - Setting local version with `proto local`. - WASM API - - Added `is_musl` and `get_target_triple` helper functions. + - Added `command_exists`, `is_musl`, and `get_target_triple` helper functions. #### ⚙️ Internal - Now supports `.zst` (or `.zstd`) archive formats. +- Improved version, alias, and requirement handling. ## 0.16.1 diff --git a/Cargo.lock b/Cargo.lock index db38bcf1c..db8b1b9ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3026,9 +3026,9 @@ dependencies = [ [[package]] name = "starbase_archive" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13f717bc785efa5d43255b3418f1a3c82c1506a78e7849bee8e04c96c688bf8e" +checksum = "f85e565b134761413513c0091ed002ffb31ba448ca9542c4c41ca5968a29a9bb" dependencies = [ "flate2", "miette", @@ -3069,9 +3069,9 @@ dependencies = [ [[package]] name = "starbase_sandbox" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8715fbb4e5ea309784161fddca9b5abfc2425ad079ef624c1fc01c8eea8f7163" +checksum = "62ec8301c49429d3da1105475f4e32ce8fa7c8cf1028c4e4535b0c010be2952a" dependencies = [ "assert_cmd", "assert_fs", @@ -3085,9 +3085,9 @@ dependencies = [ [[package]] name = "starbase_styles" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6930eace291b6c6dc243074efc7a4c8bdebe0ffd94bcee0176472148f76205d" +checksum = "251a45da918b41577ab62fb764c9683a0f50fb2b8999a629dbd6c25476a9c411" dependencies = [ "dirs 5.0.1", "miette", @@ -3097,9 +3097,9 @@ dependencies = [ [[package]] name = "starbase_utils" -version = "0.2.22" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bf684116f2b02df3926d3412c97c1d8caa4b9edc49c6b806ed6d8e33cdb8be" +checksum = "032493983d1aaa0e6107219e8b17e2b315452f902f0f512c8bd5af6df6704b44" dependencies = [ "dirs 5.0.1", "fs4", @@ -3112,6 +3112,7 @@ dependencies = [ "serde_json", "starbase_styles", "thiserror", + "tokio", "toml 0.7.6", "tracing", "wax", diff --git a/Cargo.toml b/Cargo.toml index c7170540c..aea1338b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,16 +22,16 @@ semver = "1.0.18" serde = { version = "1.0.188", features = ["derive"] } serde_json = "1.0.105" sha2 = "0.10.7" -starbase_archive = { version = "0.2.1", features = [ +starbase_archive = { version = "0.2.2", features = [ "tar-gz", "tar-xz", "tar-zstd", "zip", "zip-deflate", ] } -starbase_sandbox = { version = "0.1.9" } -starbase_styles = "0.1.14" -starbase_utils = { version = "0.2.22", default-features = false, features = [ +starbase_sandbox = { version = "0.1.10" } +starbase_styles = "0.1.15" +starbase_utils = { version = "0.3.1", default-features = false, features = [ "json", "toml", ] } diff --git a/crates/cli/src/commands/alias.rs b/crates/cli/src/commands/alias.rs index 2458217da..dc87a7087 100644 --- a/crates/cli/src/commands/alias.rs +++ b/crates/cli/src/commands/alias.rs @@ -1,5 +1,5 @@ use clap::Args; -use proto_core::{is_alias_name, load_tool, Id, ProtoError, VersionType}; +use proto_core::{is_alias_name, load_tool, Id, ProtoError, UnresolvedVersionSpec}; use starbase::system; use starbase_styles::color; use tracing::info; @@ -13,12 +13,12 @@ pub struct AliasArgs { alias: String, #[arg(required = true, help = "Version or alias to associate with")] - semver: VersionType, + spec: UnresolvedVersionSpec, } #[system] pub async fn alias(args: ArgsRef) { - if let VersionType::Alias(inner_alias) = &args.semver { + if let UnresolvedVersionSpec::Alias(inner_alias) = &args.spec { if &args.alias == inner_alias { return Err(ProtoError::Message("Cannot map an alias to itself.".into()))?; } @@ -34,13 +34,13 @@ pub async fn alias(args: ArgsRef) { tool.manifest .aliases - .insert(args.alias.clone(), args.semver.clone()); + .insert(args.alias.clone(), args.spec.clone()); tool.manifest.save()?; info!( "Added alias {} ({}) for {}", color::id(&args.alias), - color::muted_light(args.semver.to_string()), + color::muted_light(args.spec.to_string()), tool.get_name(), ); } diff --git a/crates/cli/src/commands/bin.rs b/crates/cli/src/commands/bin.rs index 70ae64a39..f3f3028a4 100644 --- a/crates/cli/src/commands/bin.rs +++ b/crates/cli/src/commands/bin.rs @@ -1,5 +1,5 @@ use clap::Args; -use proto_core::{detect_version, load_tool, Id, VersionType}; +use proto_core::{detect_version, load_tool, Id, UnresolvedVersionSpec}; use starbase::system; #[derive(Args, Clone, Debug)] @@ -8,7 +8,7 @@ pub struct BinArgs { id: Id, #[arg(help = "Version or alias of tool")] - semver: Option, + spec: Option, #[arg(long, help = "Display shim path when available")] shim: bool, @@ -17,7 +17,7 @@ pub struct BinArgs { #[system] pub async fn bin(args: ArgsRef) { let mut tool = load_tool(&args.id).await?; - let version = detect_version(&tool, args.semver.clone()).await?; + let version = detect_version(&tool, args.spec.clone()).await?; tool.resolve_version(&version).await?; tool.locate_bins().await?; diff --git a/crates/cli/src/commands/clean.rs b/crates/cli/src/commands/clean.rs index fca369a85..ed538a85e 100644 --- a/crates/cli/src/commands/clean.rs +++ b/crates/cli/src/commands/clean.rs @@ -1,10 +1,9 @@ use clap::Args; use dialoguer::Confirm; use proto_core::{ - get_plugins_dir, get_shim_file_name, load_tool, AliasOrVersion, Id, Tool, ToolsConfig, + get_plugins_dir, get_shim_file_name, load_tool, Id, Tool, ToolsConfig, VersionSpec, }; use proto_pdk_api::{CreateShimsInput, CreateShimsOutput}; -use semver::Version; use starbase::diagnostics::IntoDiagnostic; use starbase::{system, SystemResult}; use starbase_styles::color; @@ -53,7 +52,7 @@ pub async fn clean_tool(mut tool: Tool, now: u128, days: u8, yes: bool) -> miett return Ok(0); } - let mut versions_to_clean = HashSet::::new(); + let mut versions_to_clean = HashSet::::new(); debug!("Scanning file system for stale and untracked versions"); @@ -71,7 +70,7 @@ pub async fn clean_tool(mut tool: Tool, now: u128, days: u8, yes: bool) -> miett continue; } - let version = Version::parse(&dir_name).into_diagnostic()?; + let version = VersionSpec::parse(&dir_name)?; if !tool.manifest.versions.contains_key(&version) { debug!( @@ -141,7 +140,7 @@ pub async fn clean_tool(mut tool: Tool, now: u128, days: u8, yes: bool) -> miett .into_diagnostic()? { for version in versions_to_clean { - tool.set_version(AliasOrVersion::Version(version)); + tool.set_version(version); tool.teardown().await?; } diff --git a/crates/cli/src/commands/global.rs b/crates/cli/src/commands/global.rs index c1f9bb46b..758a65db9 100644 --- a/crates/cli/src/commands/global.rs +++ b/crates/cli/src/commands/global.rs @@ -1,5 +1,5 @@ use clap::Args; -use proto_core::{load_tool, AliasOrVersion, Id}; +use proto_core::{load_tool, Id, UnresolvedVersionSpec}; use starbase::system; use starbase_styles::color; use tracing::{debug, info}; @@ -10,18 +10,18 @@ pub struct GlobalArgs { id: Id, #[arg(required = true, help = "Version or alias of tool")] - semver: AliasOrVersion, + spec: UnresolvedVersionSpec, } #[system] pub async fn global(args: ArgsRef) -> SystemResult { let mut tool = load_tool(&args.id).await?; - tool.manifest.default_version = Some(args.semver.clone()); + tool.manifest.default_version = Some(args.spec.clone()); tool.manifest.save()?; debug!( - version = args.semver.to_string(), + version = args.spec.to_string(), manifest = ?tool.manifest.path, "Wrote the global version", ); @@ -29,6 +29,6 @@ pub async fn global(args: ArgsRef) -> SystemResult { info!( "Set the global {} version to {}", tool.get_name(), - color::hash(args.semver.to_string()) + color::hash(args.spec.to_string()) ); } diff --git a/crates/cli/src/commands/install.rs b/crates/cli/src/commands/install.rs index 38b682682..84e361eba 100644 --- a/crates/cli/src/commands/install.rs +++ b/crates/cli/src/commands/install.rs @@ -2,7 +2,7 @@ use crate::helpers::{create_progress_bar, disable_progress_bars}; use crate::shell; use clap::Args; use miette::IntoDiagnostic; -use proto_core::{load_tool, Id, Tool, VersionType}; +use proto_core::{load_tool, Id, Tool, UnresolvedVersionSpec}; use proto_pdk_api::{InstallHook, SyncShellProfileInput, SyncShellProfileOutput}; use starbase::{system, SystemResult}; use starbase_styles::color; @@ -15,7 +15,7 @@ pub struct InstallArgs { pub id: Id, #[arg(default_value = "latest", help = "Version or alias of tool")] - pub semver: Option, + pub spec: Option, #[arg(long, help = "Pin version as the global default")] pub pin: bool, @@ -26,7 +26,7 @@ pub struct InstallArgs { } pub async fn internal_install(args: InstallArgs) -> SystemResult { - let version = args.semver.clone().unwrap_or_default(); + let version = args.spec.clone().unwrap_or_default(); let mut tool = load_tool(&args.id).await?; if tool.is_setup(&version).await? { @@ -76,7 +76,7 @@ pub async fn internal_install(args: InstallArgs) -> SystemResult { tool.cleanup().await?; if args.pin { - tool.manifest.default_version = Some(tool.get_resolved_version()); + tool.manifest.default_version = Some(tool.get_resolved_version().to_unresolved_spec()); tool.manifest.save()?; } diff --git a/crates/cli/src/commands/install_all.rs b/crates/cli/src/commands/install_all.rs index 3324bf95a..b883bd7e1 100644 --- a/crates/cli/src/commands/install_all.rs +++ b/crates/cli/src/commands/install_all.rs @@ -5,9 +5,7 @@ use crate::{ helpers::create_progress_bar, }; use futures::future::try_join_all; -use proto_core::{ - load_tool_from_locator, AliasOrVersion, ProtoEnvironment, ToolsConfig, UserConfig, -}; +use proto_core::{load_tool_from_locator, ProtoEnvironment, ToolsConfig, UserConfig}; use starbase::system; use starbase_styles::color; use std::env; @@ -40,7 +38,7 @@ pub async fn install_all() { debug!("Detected version {} for {}", version, tool.get_name()); - config.tools.insert(name, AliasOrVersion::Version(version)); + config.tools.insert(name, version.to_unresolved_spec()); } } @@ -65,9 +63,9 @@ pub async fn install_all() { for (id, version) in config.tools { futures.push(internal_install(InstallArgs { id, - semver: Some(version.to_implicit_type()), pin: false, passthrough: vec![], + spec: Some(version), })); } diff --git a/crates/cli/src/commands/list_remote.rs b/crates/cli/src/commands/list_remote.rs index 864aa66f1..be00c5e67 100644 --- a/crates/cli/src/commands/list_remote.rs +++ b/crates/cli/src/commands/list_remote.rs @@ -1,5 +1,5 @@ use clap::Args; -use proto_core::{load_tool, Id, VersionType}; +use proto_core::{load_tool, Id, UnresolvedVersionSpec}; use starbase::system; use std::process; use tracing::debug; @@ -10,14 +10,15 @@ pub struct ListRemoteArgs { id: Id, } -// TODO: only show LTS, dont show pre-releases? #[system] pub async fn list_remote(args: ArgsRef) { let tool = load_tool(&args.id).await?; debug!("Loading versions"); - let resolver = tool.load_version_resolver(&VersionType::default()).await?; + let resolver = tool + .load_version_resolver(&UnresolvedVersionSpec::default()) + .await?; let mut versions = resolver.versions; if versions.is_empty() { diff --git a/crates/cli/src/commands/local.rs b/crates/cli/src/commands/local.rs index 517c612ff..74051c2ae 100644 --- a/crates/cli/src/commands/local.rs +++ b/crates/cli/src/commands/local.rs @@ -1,5 +1,5 @@ use clap::Args; -use proto_core::{load_tool, AliasOrVersion, Id, ToolsConfig}; +use proto_core::{load_tool, Id, ToolsConfig, UnresolvedVersionSpec}; use starbase::system; use starbase_styles::color; use std::{env, path::PathBuf}; @@ -11,7 +11,7 @@ pub struct LocalArgs { id: Id, #[arg(required = true, help = "Version or alias of tool")] - semver: AliasOrVersion, + spec: UnresolvedVersionSpec, } #[system] @@ -20,11 +20,11 @@ pub async fn local(args: ArgsRef) { let local_path = env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); let mut config = ToolsConfig::load_from(local_path)?; - config.tools.insert(args.id.clone(), args.semver.clone()); + config.tools.insert(args.id.clone(), args.spec.clone()); config.save()?; debug!( - version = args.semver.to_string(), + version = args.spec.to_string(), config = ?config.path, "Wrote the local version", ); @@ -32,6 +32,6 @@ pub async fn local(args: ArgsRef) { info!( "Set the local {} version to {}", tool.get_name(), - color::hash(args.semver.to_string()) + color::hash(args.spec.to_string()) ); } diff --git a/crates/cli/src/commands/run.rs b/crates/cli/src/commands/run.rs index b3eb1e33b..812600c0b 100644 --- a/crates/cli/src/commands/run.rs +++ b/crates/cli/src/commands/run.rs @@ -1,9 +1,7 @@ use crate::commands::install::{internal_install, InstallArgs}; use clap::Args; use miette::IntoDiagnostic; -use proto_core::{ - detect_version, load_tool, AliasOrVersion, Id, ProtoError, UserConfig, VersionType, -}; +use proto_core::{detect_version, load_tool, Id, ProtoError, UnresolvedVersionSpec, UserConfig}; use proto_pdk_api::RunHook; use starbase::system; use starbase_styles::color; @@ -18,7 +16,7 @@ pub struct RunArgs { id: Id, #[arg(help = "Version or alias of tool")] - semver: Option, + spec: Option, #[arg(long, help = "Path to an alternate binary to run")] bin: Option, @@ -34,7 +32,7 @@ pub struct RunArgs { #[system] pub async fn run(args: ArgsRef) -> SystemResult { let mut tool = load_tool(&args.id).await?; - let version = detect_version(&tool, args.semver.clone()).await?; + let version = detect_version(&tool, args.spec.clone()).await?; let user_config = UserConfig::load()?; // Check if installed or install @@ -53,7 +51,7 @@ pub async fn run(args: ArgsRef) -> SystemResult { internal_install(InstallArgs { id: args.id.clone(), - semver: Some(tool.get_resolved_version().to_implicit_type()), + spec: Some(tool.get_resolved_version().to_unresolved_spec()), pin: false, passthrough: vec![], }) @@ -67,13 +65,11 @@ pub async fn run(args: ArgsRef) -> SystemResult { // Update the last used timestamp if env::var("PROTO_SKIP_USED_AT").is_err() { - if let AliasOrVersion::Version(version) = &resolved_version { - tool.manifest.track_used_at(version); + tool.manifest.track_used_at(&resolved_version); - // Ignore errors in case of race conditions... - // this timestamp isn't *super* important - let _ = tool.manifest.save(); - } + // Ignore errors in case of race conditions... + // this timestamp isn't *super* important + let _ = tool.manifest.save(); } // Determine the binary path to execute diff --git a/crates/cli/src/commands/uninstall.rs b/crates/cli/src/commands/uninstall.rs index 354a903cc..8a95ee4ec 100644 --- a/crates/cli/src/commands/uninstall.rs +++ b/crates/cli/src/commands/uninstall.rs @@ -1,6 +1,6 @@ use crate::helpers::{create_progress_bar, disable_progress_bars}; use clap::Args; -use proto_core::{load_tool, Id, VersionType}; +use proto_core::{load_tool, Id, UnresolvedVersionSpec}; use starbase::system; use tracing::{debug, info}; @@ -10,7 +10,7 @@ pub struct UninstallArgs { id: Id, #[arg(required = true, help = "Version or alias of tool")] - semver: VersionType, + semver: UnresolvedVersionSpec, } #[system] diff --git a/crates/cli/tests/alias_test.rs b/crates/cli/tests/alias_test.rs index ff3dfb81c..2bd56e153 100644 --- a/crates/cli/tests/alias_test.rs +++ b/crates/cli/tests/alias_test.rs @@ -1,6 +1,6 @@ mod utils; -use proto_core::{ToolManifest, VersionType}; +use proto_core::{ToolManifest, UnresolvedVersionSpec}; use starbase_sandbox::predicates::prelude::*; use std::collections::BTreeMap; use utils::*; @@ -44,7 +44,10 @@ mod alias { assert_eq!( manifest.aliases, - BTreeMap::from_iter([("example".into(), VersionType::parse("19.0.0").unwrap())]) + BTreeMap::from_iter([( + "example".into(), + UnresolvedVersionSpec::parse("19.0.0").unwrap() + )]) ); } @@ -54,9 +57,10 @@ mod alias { let manifest_file = sandbox.path().join("tools/node/manifest.json"); let mut manifest = ToolManifest::load(&manifest_file).unwrap(); - manifest - .aliases - .insert("example".into(), VersionType::parse("19.0.0").unwrap()); + manifest.aliases.insert( + "example".into(), + UnresolvedVersionSpec::parse("19.0.0").unwrap(), + ); manifest.save().unwrap(); let mut cmd = create_proto_command(sandbox.path()); @@ -71,7 +75,10 @@ mod alias { assert_eq!( manifest.aliases, - BTreeMap::from_iter([("example".into(), VersionType::parse("20.0.0").unwrap())]) + BTreeMap::from_iter([( + "example".into(), + UnresolvedVersionSpec::parse("20.0.0").unwrap() + )]) ); } diff --git a/crates/cli/tests/global_test.rs b/crates/cli/tests/global_test.rs index f83892ecb..3a76c100d 100644 --- a/crates/cli/tests/global_test.rs +++ b/crates/cli/tests/global_test.rs @@ -1,6 +1,6 @@ mod utils; -use proto_core::{AliasOrVersion, ToolManifest}; +use proto_core::{ToolManifest, UnresolvedVersionSpec}; use utils::*; mod global { @@ -26,7 +26,7 @@ mod global { assert_eq!( manifest.default_version, - Some(AliasOrVersion::parse("19.0.0").unwrap()) + Some(UnresolvedVersionSpec::parse("19.0.0").unwrap()) ); } @@ -50,7 +50,27 @@ mod global { assert_eq!( manifest.default_version, - Some(AliasOrVersion::Alias("bundled".into())) + Some(UnresolvedVersionSpec::Alias("bundled".into())) + ); + } + + #[test] + fn can_set_partial_version_as_default() { + let temp = create_empty_sandbox(); + let manifest_file = temp.path().join("tools/npm/manifest.json"); + + assert!(!manifest_file.exists()); + + let mut cmd = create_proto_command(temp.path()); + cmd.arg("global").arg("npm").arg("1.2").assert().success(); + + assert!(manifest_file.exists()); + + let manifest = ToolManifest::load(manifest_file).unwrap(); + + assert_eq!( + manifest.default_version, + Some(UnresolvedVersionSpec::parse("1.2").unwrap()) ); } } diff --git a/crates/cli/tests/install_uninstall_test.rs b/crates/cli/tests/install_uninstall_test.rs index 3eda889ea..4dc5598cf 100644 --- a/crates/cli/tests/install_uninstall_test.rs +++ b/crates/cli/tests/install_uninstall_test.rs @@ -1,6 +1,6 @@ mod utils; -use proto_core::{AliasOrVersion, ToolManifest, Version}; +use proto_core::{ToolManifest, UnresolvedVersionSpec, VersionSpec}; use starbase_sandbox::predicates::prelude::*; use std::collections::HashSet; use utils::*; @@ -134,15 +134,15 @@ mod install_uninstall { assert_eq!( manifest.default_version, - Some(AliasOrVersion::parse("19.0.0").unwrap()) + Some(UnresolvedVersionSpec::parse("19.0.0").unwrap()) ); assert_eq!( manifest.installed_versions, - HashSet::from_iter([Version::parse("19.0.0").unwrap()]) + HashSet::from_iter([VersionSpec::parse("19.0.0").unwrap()]) ); assert!(manifest .versions - .contains_key(&Version::parse("19.0.0").unwrap())); + .contains_key(&VersionSpec::parse("19.0.0").unwrap())); // Uninstall let mut cmd = create_proto_command(temp.path()); @@ -158,7 +158,7 @@ mod install_uninstall { assert_eq!(manifest.installed_versions, HashSet::default()); assert!(!manifest .versions - .contains_key(&Version::parse("19.0.0").unwrap())); + .contains_key(&VersionSpec::parse("19.0.0").unwrap())); } #[test] @@ -167,10 +167,10 @@ mod install_uninstall { let manifest_file = temp.path().join("tools/node/manifest.json"); let mut manifest = ToolManifest::load(&manifest_file).unwrap(); - manifest.default_version = Some(AliasOrVersion::parse("18.0.0").unwrap()); + manifest.default_version = Some(UnresolvedVersionSpec::parse("18.0.0").unwrap()); manifest .installed_versions - .insert(Version::parse("18.0.0").unwrap()); + .insert(VersionSpec::parse("18.0.0").unwrap()); manifest.save().unwrap(); let mut cmd = create_proto_command(temp.path()); @@ -186,13 +186,13 @@ mod install_uninstall { assert_eq!( manifest.default_version, - Some(AliasOrVersion::parse("19.0.0").unwrap()) + Some(UnresolvedVersionSpec::parse("19.0.0").unwrap()) ); assert_eq!( manifest.installed_versions, HashSet::from_iter([ - Version::parse("18.0.0").unwrap(), - Version::parse("19.0.0").unwrap(), + VersionSpec::parse("18.0.0").unwrap(), + VersionSpec::parse("19.0.0").unwrap(), ]) ); } diff --git a/crates/cli/tests/list_test.rs b/crates/cli/tests/list_test.rs index 630b45e0c..ea4df4021 100644 --- a/crates/cli/tests/list_test.rs +++ b/crates/cli/tests/list_test.rs @@ -1,6 +1,6 @@ mod utils; -use proto_core::{AliasOrVersion, ToolManifest, Version}; +use proto_core::{ToolManifest, UnresolvedVersionSpec, VersionSpec}; use utils::*; mod list { @@ -12,16 +12,16 @@ mod list { let mut manifest = ToolManifest::load(temp.path().join("tools/node/manifest.json")).unwrap(); - manifest.default_version = Some(AliasOrVersion::parse("19.0.0").unwrap()); + manifest.default_version = Some(UnresolvedVersionSpec::parse("19.0.0").unwrap()); manifest .installed_versions - .insert(Version::parse("19.0.0").unwrap()); + .insert(VersionSpec::parse("19.0.0").unwrap()); manifest .installed_versions - .insert(Version::parse("18.0.0").unwrap()); + .insert(VersionSpec::parse("18.0.0").unwrap()); manifest .installed_versions - .insert(Version::parse("17.0.0").unwrap()); + .insert(VersionSpec::parse("17.0.0").unwrap()); manifest.save().unwrap(); let mut cmd = create_proto_command(temp.path()); diff --git a/crates/cli/tests/local_test.rs b/crates/cli/tests/local_test.rs index c73fc5d16..3f2408e0a 100644 --- a/crates/cli/tests/local_test.rs +++ b/crates/cli/tests/local_test.rs @@ -63,15 +63,11 @@ npm = "9.0.0" ); let mut cmd = create_proto_command(temp.path()); - cmd.arg("local") - .arg("node") - .arg("19.0.0") - .assert() - .success(); + cmd.arg("local").arg("node").arg("19").assert().success(); assert_eq!( fs::read_to_string(version_file).unwrap(), - r#"node = "19.0.0" + r#"node = "~19" npm = "9.0.0" "# ) @@ -97,4 +93,21 @@ npm = "9.0.0" "npm = \"bundled\"\n" ) } + + #[test] + fn can_set_partial_version() { + let temp = create_empty_sandbox(); + let version_file = temp.path().join(".prototools"); + + assert!(!version_file.exists()); + + let mut cmd = create_proto_command(temp.path()); + cmd.arg("local").arg("npm").arg("1.2").assert().success(); + + assert!(version_file.exists()); + assert_eq!( + fs::read_to_string(version_file).unwrap(), + "npm = \"~1.2\"\n" + ) + } } diff --git a/crates/cli/tests/plugins_test.rs b/crates/cli/tests/plugins_test.rs index 6d9352160..68585e314 100644 --- a/crates/cli/tests/plugins_test.rs +++ b/crates/cli/tests/plugins_test.rs @@ -2,7 +2,8 @@ mod utils; use futures::Future; use proto_core::{ - load_tool_from_locator, Id, PluginLocator, ProtoEnvironment, Tool, UserConfig, VersionType, + load_tool_from_locator, Id, PluginLocator, ProtoEnvironment, Tool, UnresolvedVersionSpec, + UserConfig, }; use std::env; use std::path::{Path, PathBuf}; @@ -20,7 +21,7 @@ where env::set_var("PROTO_HOME", fixture.path().to_string_lossy().to_string()); - tool.setup(&VersionType::parse("1.0.0").unwrap()) + tool.setup(&UnresolvedVersionSpec::parse("1.0.0").unwrap()) .await .unwrap(); diff --git a/crates/cli/tests/run_test.rs b/crates/cli/tests/run_test.rs index 9d739065e..2728f011a 100644 --- a/crates/cli/tests/run_test.rs +++ b/crates/cli/tests/run_test.rs @@ -1,6 +1,6 @@ mod utils; -use proto_core::{ToolManifest, Version}; +use proto_core::{ToolManifest, VersionSpec}; use starbase_sandbox::predicates::prelude::*; use std::{env, fs}; use utils::*; @@ -172,7 +172,7 @@ mod run { .assert(); let manifest = ToolManifest::load(&manifest_file).unwrap(); - let version = Version::parse("19.0.0").unwrap(); + let version = VersionSpec::parse("19.0.0").unwrap(); let last_used_at = manifest.versions.get(&version).unwrap().last_used_at; diff --git a/crates/cli/tests/unalias_test.rs b/crates/cli/tests/unalias_test.rs index a0565a097..cc8d10fe3 100644 --- a/crates/cli/tests/unalias_test.rs +++ b/crates/cli/tests/unalias_test.rs @@ -1,6 +1,6 @@ mod utils; -use proto_core::{ToolManifest, VersionType}; +use proto_core::{ToolManifest, UnresolvedVersionSpec}; use starbase_sandbox::predicates::prelude::*; use std::collections::BTreeMap; use utils::*; @@ -24,9 +24,10 @@ mod unalias { let manifest_file = sandbox.path().join("tools/node/manifest.json"); let mut manifest = ToolManifest::load(&manifest_file).unwrap(); - manifest - .aliases - .insert("example".into(), VersionType::parse("19.0.0").unwrap()); + manifest.aliases.insert( + "example".into(), + UnresolvedVersionSpec::parse("19.0.0").unwrap(), + ); manifest.save().unwrap(); let mut cmd = create_proto_command(sandbox.path()); @@ -47,9 +48,10 @@ mod unalias { let manifest_file = sandbox.path().join("tools/node/manifest.json"); let mut manifest = ToolManifest::load(&manifest_file).unwrap(); - manifest - .aliases - .insert("example".into(), VersionType::parse("19.0.0").unwrap()); + manifest.aliases.insert( + "example".into(), + UnresolvedVersionSpec::parse("19.0.0").unwrap(), + ); manifest.save().unwrap(); let mut cmd = create_proto_command(sandbox.path()); @@ -63,7 +65,10 @@ mod unalias { assert_eq!( manifest.aliases, - BTreeMap::from_iter([("example".into(), VersionType::parse("19.0.0").unwrap())]) + BTreeMap::from_iter([( + "example".into(), + UnresolvedVersionSpec::parse("19.0.0").unwrap() + )]) ); } } diff --git a/crates/core/src/events.rs b/crates/core/src/events.rs index 60a0a0131..2404ae1ff 100644 --- a/crates/core/src/events.rs +++ b/crates/core/src/events.rs @@ -16,11 +16,11 @@ macro_rules! impl_event { } impl_event!(InstallingEvent, { - pub version: AliasOrVersion, + pub version: VersionSpec, }); impl_event!(InstalledEvent, { - pub version: AliasOrVersion, + pub version: VersionSpec, }); impl_event!(InstalledGlobalEvent, { @@ -28,11 +28,11 @@ impl_event!(InstalledGlobalEvent, { }); impl_event!(UninstallingEvent, { - pub version: AliasOrVersion, + pub version: VersionSpec, }); impl_event!(UninstalledEvent, { - pub version: AliasOrVersion, + pub version: VersionSpec, }); impl_event!(UninstalledGlobalEvent, { @@ -45,6 +45,6 @@ impl_event!(CreatedShimsEvent, { }); impl_event!(ResolvedVersionEvent, { - pub candidate: VersionType, - pub version: AliasOrVersion, + pub candidate: UnresolvedVersionSpec, + pub version: VersionSpec, }); diff --git a/crates/core/src/tool.rs b/crates/core/src/tool.rs index 97ea1a355..d020c01ce 100644 --- a/crates/core/src/tool.rs +++ b/crates/core/src/tool.rs @@ -6,7 +6,7 @@ use crate::shimmer::{ create_global_shim, create_local_shim, get_shim_file_name, ShimContext, SHIM_VERSION, }; use crate::tool_manifest::ToolManifest; -use crate::version::{AliasOrVersion, VersionType}; +use crate::version::{UnresolvedVersionSpec, VersionSpec}; use crate::version_resolver::VersionResolver; use crate::{ download_from_url, is_archive_file, read_json_file_with_lock, write_json_file_with_lock, @@ -36,7 +36,7 @@ pub struct Tool { pub metadata: ToolMetadataOutput, pub plugin: PluginContainer<'static>, pub proto: ProtoEnvironment, - pub version: Option, + pub version: Option, // Events pub on_created_shims: Emitter, @@ -179,10 +179,8 @@ impl Tool { } /// Return the resolved version or "latest". - pub fn get_resolved_version(&self) -> AliasOrVersion { - self.version - .clone() - .unwrap_or_else(|| AliasOrVersion::Alias("latest".into())) + pub fn get_resolved_version(&self) -> VersionSpec { + self.version.clone().unwrap_or_default() } /// Return a path to a local shim file if it exists. @@ -216,7 +214,7 @@ impl Tool { } /// Explicitly set the version to use. - pub fn set_version(&mut self, version: AliasOrVersion) { + pub fn set_version(&mut self, version: VersionSpec) { self.version = Some(version); } @@ -321,27 +319,30 @@ impl Tool { if let Some(default) = sync_changes.default_version { modified = true; - self.manifest.default_version = Some(AliasOrVersion::parse(&default)?); + self.manifest.default_version = Some(UnresolvedVersionSpec::parse(&default)?); } if let Some(versions) = sync_changes.versions { modified = true; let mut entries = BTreeMap::new(); - - for version in &versions { - entries.insert( - version.to_owned(), - self.manifest - .versions - .get(version) - .cloned() - .unwrap_or_default(), - ); + let mut installed = HashSet::new(); + + for version in versions { + let key = VersionSpec::Version(version); + let value = self + .manifest + .versions + .get(&key) + .cloned() + .unwrap_or_default(); + + installed.insert(key.clone()); + entries.insert(key, value); } self.manifest.versions = entries; - self.manifest.installed_versions = HashSet::from_iter(versions); + self.manifest.installed_versions = installed; } if modified { @@ -359,7 +360,7 @@ impl Tool { /// To reduce network overhead, results will be cached for 24 hours. pub async fn load_version_resolver( &self, - initial_version: &VersionType, + initial_version: &UnresolvedVersionSpec, ) -> miette::Result { debug!(tool = self.id.as_str(), "Loading available versions"); @@ -414,15 +415,18 @@ impl Tool { /// Given an initial version, resolve it to a fully qualifed and semantic version /// (or alias) according to the tool's ecosystem. - pub async fn resolve_version(&mut self, initial_version: &VersionType) -> miette::Result<()> { + pub async fn resolve_version( + &mut self, + initial_version: &UnresolvedVersionSpec, + ) -> miette::Result<()> { if self.version.is_some() { return Ok(()); } // If offline but we have a fully qualified semantic version, // exit early and assume the version is legitimate! - if is_offline() && matches!(initial_version, VersionType::Version(_)) { - let version = initial_version.to_explicit_version(); + if is_offline() && matches!(initial_version, UnresolvedVersionSpec::Version(_)) { + let version = initial_version.to_spec(); self.on_resolved_version .emit(ResolvedVersionEvent { @@ -443,7 +447,7 @@ impl Tool { ); let resolver = self.load_version_resolver(initial_version).await?; - let mut version = AliasOrVersion::default(); + let mut version = VersionSpec::default(); let mut resolved = false; if self.plugin.has_func("resolve_version") { @@ -463,8 +467,7 @@ impl Tool { ); resolved = true; - version = - AliasOrVersion::Version(resolver.resolve(&VersionType::parse(candidate)?)?); + version = resolver.resolve(&UnresolvedVersionSpec::parse(candidate)?)?; } if let Some(candidate) = result.version { @@ -475,12 +478,12 @@ impl Tool { ); resolved = true; - version = AliasOrVersion::parse(candidate)?; + version = VersionSpec::parse(candidate)?; } } if !resolved { - version = AliasOrVersion::Version(resolver.resolve(initial_version)?); + version = resolver.resolve(initial_version)?; } debug!( @@ -510,7 +513,7 @@ impl Tool { pub async fn detect_version_from( &self, current_dir: &Path, - ) -> miette::Result> { + ) -> miette::Result> { if !self.plugin.has_func("detect_version_files") { return Ok(None); } @@ -557,7 +560,7 @@ impl Tool { "Detected a version" ); - return Ok(Some(VersionType::try_from(version)?)); + return Ok(Some(UnresolvedVersionSpec::parse(version)?)); } Ok(None) @@ -734,7 +737,7 @@ impl Tool { /// a pre-built archive, or by using a native installation method. pub async fn install(&mut self) -> miette::Result { let install_dir = self.get_tool_dir(); - let _install_lock = fs::lock_directory(&install_dir)?; + let _install_lock = fs::lock_directory(&install_dir).await?; if self.is_installed() { debug!( @@ -1024,19 +1027,14 @@ impl Tool { impl Tool { /// Create the context object required for creating shim files. pub fn create_shim_context(&self) -> ShimContext { - let mut context = ShimContext { + ShimContext { shim_file: &self.id, bin: &self.id, tool_id: &self.id, tool_dir: Some(self.get_tool_dir()), + tool_version: Some(self.get_resolved_version().to_string()), ..ShimContext::default() - }; - - if let AliasOrVersion::Version(version) = self.get_resolved_version() { - context.tool_version = Some(version.to_string()); } - - context } /// Create global and local shim files for the current tool. @@ -1122,11 +1120,14 @@ impl Tool { self.version.as_ref().is_some_and(|v| !v.is_latest()) && dir.exists() - && !dir.join(".lock").exists() + && !fs::is_dir_locked(dir) } /// Return true if the tool has been setup (installed and binaries are located). - pub async fn is_setup(&mut self, initial_version: &VersionType) -> miette::Result { + pub async fn is_setup( + &mut self, + initial_version: &UnresolvedVersionSpec, + ) -> miette::Result { self.resolve_version(initial_version).await?; let install_dir = self.get_tool_dir(); @@ -1159,24 +1160,23 @@ impl Tool { /// Setup the tool by resolving a semantic version, installing the tool, /// locating binaries, creating shims, and more. - pub async fn setup(&mut self, initial_version: &VersionType) -> miette::Result { + pub async fn setup(&mut self, initial_version: &UnresolvedVersionSpec) -> miette::Result { self.resolve_version(initial_version).await?; if self.install().await? { self.locate_bins().await?; self.setup_shims(true).await?; - // Only insert if a version - if let AliasOrVersion::Version(version) = self.get_resolved_version() { - let mut default = None; - - if let Some(default_version) = &self.metadata.default_version { - default = Some(AliasOrVersion::parse(default_version)?); - } + // Add version to manifest + let mut default = None; - self.manifest.insert_version(&version, default)?; + if let Some(default_version) = &self.metadata.default_version { + default = Some(UnresolvedVersionSpec::parse(default_version)?); } + self.manifest + .insert_version(self.get_resolved_version(), default)?; + // Allow plugins to override manifest self.sync_manifest()?; @@ -1213,9 +1213,7 @@ impl Tool { if self.uninstall().await? { // Only remove if uninstall was successful - if let AliasOrVersion::Version(version) = self.get_resolved_version() { - self.manifest.remove_version(&version)?; - } + self.manifest.remove_version(self.get_resolved_version())?; return Ok(true); } diff --git a/crates/core/src/tool_manifest.rs b/crates/core/src/tool_manifest.rs index 887c55eeb..b954e7d3b 100644 --- a/crates/core/src/tool_manifest.rs +++ b/crates/core/src/tool_manifest.rs @@ -1,8 +1,7 @@ use crate::{ helpers::{read_json_file_with_lock, write_json_file_with_lock}, - version::{AliasOrVersion, VersionType}, + version::{UnresolvedVersionSpec, VersionSpec}, }; -use semver::Version; use serde::{Deserialize, Serialize}; use std::{ collections::{BTreeMap, HashSet}, @@ -42,11 +41,14 @@ impl Default for ToolManifestVersion { #[derive(Debug, Default, Deserialize, Serialize)] #[serde(default)] pub struct ToolManifest { - pub aliases: BTreeMap, - pub default_version: Option, - pub installed_versions: HashSet, + // Partial versions allowed + pub aliases: BTreeMap, + pub default_version: Option, + + // Full versions only + pub installed_versions: HashSet, pub shim_version: u8, - pub versions: BTreeMap, + pub versions: BTreeMap, #[serde(skip)] pub path: PathBuf, @@ -85,45 +87,44 @@ impl ToolManifest { pub fn insert_version( &mut self, - version: &Version, - default_version: Option, + version: VersionSpec, + default_version: Option, ) -> miette::Result<()> { if self.default_version.is_none() { - self.default_version = Some( - default_version.unwrap_or_else(|| AliasOrVersion::Version(version.to_owned())), - ); + self.default_version = + Some(default_version.unwrap_or_else(|| version.to_unresolved_spec())); } - self.installed_versions.insert(version.to_owned()); + self.installed_versions.insert(version.clone()); self.versions - .insert(version.to_owned(), ToolManifestVersion::default()); + .insert(version, ToolManifestVersion::default()); self.save()?; Ok(()) } - pub fn remove_version(&mut self, version: &Version) -> miette::Result<()> { - self.installed_versions.remove(version); + pub fn remove_version(&mut self, version: VersionSpec) -> miette::Result<()> { + self.installed_versions.remove(&version); // Remove default version if nothing available if (self.installed_versions.is_empty() && self.default_version.is_some()) - || self.default_version.as_ref().is_some_and(|v| v == version) + || self.default_version.as_ref().is_some_and(|v| v == &version) { info!("Unpinning default global version"); self.default_version = None; } - self.versions.remove(version); + self.versions.remove(&version); self.save()?; Ok(()) } - pub fn track_used_at(&mut self, version: &Version) { + pub fn track_used_at(&mut self, version: &VersionSpec) { self.versions .entry(version.to_owned()) .and_modify(|v| { diff --git a/crates/core/src/tools_config.rs b/crates/core/src/tools_config.rs index 7ee2b8970..cd321fa6f 100644 --- a/crates/core/src/tools_config.rs +++ b/crates/core/src/tools_config.rs @@ -1,4 +1,4 @@ -use crate::version::AliasOrVersion; +use crate::version::UnresolvedVersionSpec; use miette::IntoDiagnostic; use serde::{Deserialize, Serialize}; use starbase_utils::{fs, toml}; @@ -18,7 +18,7 @@ fn is_empty(map: &BTreeMap) -> bool { #[serde(default, rename_all = "kebab-case")] pub struct ToolsConfig { #[serde(flatten, skip_serializing_if = "is_empty")] - pub tools: BTreeMap, + pub tools: BTreeMap, #[serde(skip_serializing_if = "is_empty")] pub plugins: BTreeMap, diff --git a/crates/core/src/version.rs b/crates/core/src/version.rs index cd3983036..7abb7f279 100644 --- a/crates/core/src/version.rs +++ b/crates/core/src/version.rs @@ -10,41 +10,41 @@ use std::str::FromStr; #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(untagged, into = "String", try_from = "String")] -pub enum VersionType { +pub enum UnresolvedVersionSpec { Alias(String), - ReqAll(VersionReq), + Req(VersionReq), ReqAny(Vec), Version(Version), } -impl VersionType { +impl UnresolvedVersionSpec { pub fn parse>(value: T) -> miette::Result { Ok(Self::from_str(value.as_ref())?) } - pub fn to_explicit_version(&self) -> AliasOrVersion { + pub fn to_spec(&self) -> VersionSpec { match self { - VersionType::Alias(alias) => AliasOrVersion::Alias(alias.to_owned()), - VersionType::Version(version) => AliasOrVersion::Version(version.to_owned()), + UnresolvedVersionSpec::Alias(alias) => VersionSpec::Alias(alias.to_owned()), + UnresolvedVersionSpec::Version(version) => VersionSpec::Version(version.to_owned()), _ => unreachable!(), } } } -impl Default for VersionType { +impl Default for UnresolvedVersionSpec { fn default() -> Self { Self::Alias("latest".into()) } } -impl FromStr for VersionType { +impl FromStr for UnresolvedVersionSpec { type Err = ProtoError; fn from_str(value: &str) -> Result { let value = remove_space_after_gtlt(remove_v_prefix(value.trim().replace(".*", ""))); if is_alias_name(&value) { - return Ok(VersionType::Alias(value)); + return Ok(UnresolvedVersionSpec::Alias(value)); } let handle_error = |error: semver::Error| ProtoError::Semver { @@ -64,41 +64,41 @@ impl FromStr for VersionType { any.push(VersionReq::parse(req).map_err(handle_error)?); } - return Ok(VersionType::ReqAny(any)); + return Ok(UnresolvedVersionSpec::ReqAny(any)); } // AND requirements if value.contains(',') { - return Ok(VersionType::ReqAll( + return Ok(UnresolvedVersionSpec::Req( VersionReq::parse(&value).map_err(handle_error)?, )); } else if value.contains(' ') { - return Ok(VersionType::ReqAll( + return Ok(UnresolvedVersionSpec::Req( VersionReq::parse(&value.replace(' ', ", ")).map_err(handle_error)?, )); } Ok(match value.chars().next().unwrap() { '=' | '^' | '~' | '>' | '<' | '*' => { - VersionType::ReqAll(VersionReq::parse(&value).map_err(handle_error)?) + UnresolvedVersionSpec::Req(VersionReq::parse(&value).map_err(handle_error)?) } _ => { let dot_count = value.match_indices('.').collect::>().len(); // If not fully qualified, match using a requirement if dot_count < 2 { - VersionType::ReqAll( + UnresolvedVersionSpec::Req( VersionReq::parse(&format!("~{value}")).map_err(handle_error)?, ) } else { - VersionType::Version(Version::parse(&value).map_err(handle_error)?) + UnresolvedVersionSpec::Version(Version::parse(&value).map_err(handle_error)?) } } }) } } -impl TryFrom for VersionType { +impl TryFrom for UnresolvedVersionSpec { type Error = ProtoError; fn try_from(value: String) -> Result { @@ -106,17 +106,17 @@ impl TryFrom for VersionType { } } -impl Into for VersionType { +impl Into for UnresolvedVersionSpec { fn into(self) -> String { self.to_string() } } -impl Display for VersionType { +impl Display for UnresolvedVersionSpec { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Alias(alias) => write!(f, "{}", alias), - Self::ReqAll(req) => write!(f, "{}", req), + Self::Req(req) => write!(f, "{}", req), Self::ReqAny(reqs) => write!( f, "{}", @@ -130,18 +130,35 @@ impl Display for VersionType { } } -#[derive(Clone, Deserialize, PartialEq, Serialize)] +impl PartialEq for UnresolvedVersionSpec { + fn eq(&self, other: &VersionSpec) -> bool { + match (self, other) { + (Self::Alias(a1), VersionSpec::Alias(a2)) => a1 == a2, + (Self::Version(v1), VersionSpec::Version(v2)) => v1 == v2, + _ => false, + } + } +} + +#[derive(Clone, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] #[serde(untagged, into = "String", try_from = "String")] -pub enum AliasOrVersion { +pub enum VersionSpec { Alias(String), Version(Version), } -impl AliasOrVersion { +impl VersionSpec { pub fn parse>(value: T) -> miette::Result { Ok(Self::from_str(value.as_ref())?) } + pub fn is_canary(&self) -> bool { + match self { + Self::Alias(alias) => alias == "canary", + Self::Version(_) => false, + } + } + pub fn is_latest(&self) -> bool { match self { Self::Alias(alias) => alias == "latest", @@ -149,31 +166,31 @@ impl AliasOrVersion { } } - pub fn to_implicit_type(&self) -> VersionType { + pub fn to_unresolved_spec(&self) -> UnresolvedVersionSpec { match self { - Self::Alias(alias) => VersionType::Alias(alias.to_owned()), - Self::Version(version) => VersionType::Version(version.to_owned()), + Self::Alias(alias) => UnresolvedVersionSpec::Alias(alias.to_owned()), + Self::Version(version) => UnresolvedVersionSpec::Version(version.to_owned()), } } } -impl Default for AliasOrVersion { +impl Default for VersionSpec { fn default() -> Self { Self::Alias("latest".into()) } } -impl FromStr for AliasOrVersion { +impl FromStr for VersionSpec { type Err = ProtoError; fn from_str(value: &str) -> Result { let value = remove_space_after_gtlt(remove_v_prefix(value.trim().replace(".*", ""))); if is_alias_name(&value) { - return Ok(AliasOrVersion::Alias(value)); + return Ok(VersionSpec::Alias(value)); } - Ok(AliasOrVersion::Version(Version::parse(&value).map_err( + Ok(VersionSpec::Version(Version::parse(&value).map_err( |error| ProtoError::Semver { version: value, error, @@ -182,7 +199,7 @@ impl FromStr for AliasOrVersion { } } -impl TryFrom for AliasOrVersion { +impl TryFrom for VersionSpec { type Error = ProtoError; fn try_from(value: String) -> Result { @@ -190,19 +207,19 @@ impl TryFrom for AliasOrVersion { } } -impl Into for AliasOrVersion { +impl Into for VersionSpec { fn into(self) -> String { self.to_string() } } -impl Debug for AliasOrVersion { +impl Debug for VersionSpec { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self) } } -impl Display for AliasOrVersion { +impl Display for VersionSpec { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Alias(alias) => write!(f, "{}", alias), @@ -211,7 +228,7 @@ impl Display for AliasOrVersion { } } -impl PartialEq<&str> for AliasOrVersion { +impl PartialEq<&str> for VersionSpec { fn eq(&self, other: &&str) -> bool { match self { Self::Alias(alias) => alias == other, @@ -220,7 +237,7 @@ impl PartialEq<&str> for AliasOrVersion { } } -impl PartialEq for AliasOrVersion { +impl PartialEq for VersionSpec { fn eq(&self, other: &Version) -> bool { match self { Self::Version(version) => version == other, diff --git a/crates/core/src/version_detector.rs b/crates/core/src/version_detector.rs index bc451ccd0..d105729d5 100644 --- a/crates/core/src/version_detector.rs +++ b/crates/core/src/version_detector.rs @@ -1,14 +1,14 @@ use crate::error::ProtoError; use crate::tool::Tool; use crate::tools_config::ToolsConfig; -use crate::version::VersionType; +use crate::version::UnresolvedVersionSpec; use std::{env, path::Path}; use tracing::{debug, trace}; pub async fn detect_version( tool: &Tool, - forced_version: Option, -) -> miette::Result { + forced_version: Option, +) -> miette::Result { let mut candidate = forced_version; // Env var takes highest priority @@ -23,7 +23,7 @@ pub async fn detect_version( "Detected version from environment variable", ); - candidate = Some(VersionType::parse(session_version)?); + candidate = Some(UnresolvedVersionSpec::parse(session_version)?); } else { trace!( tool = tool.id.as_str(), @@ -65,7 +65,7 @@ pub async fn detect_version( "Detected version from .prototools file", ); - candidate = Some(local_version.to_implicit_type()); + candidate = Some(local_version.to_owned()); break; } @@ -100,7 +100,7 @@ pub async fn detect_version( "Detected global version from manifest", ); - candidate = Some(global_version.to_implicit_type()); + candidate = Some(global_version.to_owned()); } } diff --git a/crates/core/src/version_resolver.rs b/crates/core/src/version_resolver.rs index ec6d43cca..1d903bffd 100644 --- a/crates/core/src/version_resolver.rs +++ b/crates/core/src/version_resolver.rs @@ -1,13 +1,14 @@ use crate::error::ProtoError; use crate::tool_manifest::ToolManifest; -use crate::version::VersionType; +use crate::version::UnresolvedVersionSpec; +use crate::VersionSpec; use proto_pdk_api::LoadVersionsOutput; use semver::{Version, VersionReq}; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashSet}; #[derive(Debug, Default)] pub struct VersionResolver<'tool> { - pub aliases: BTreeMap, + pub aliases: BTreeMap, pub versions: Vec, manifest: Option<&'tool ToolManifest>, @@ -21,18 +22,23 @@ impl<'tool> VersionResolver<'tool> { for (alias, version) in output.aliases { resolver .aliases - .insert(alias, VersionType::Version(version)); + .insert(alias, UnresolvedVersionSpec::Version(version)); } if let Some(latest) = output.latest { resolver .aliases - .insert("latest".into(), VersionType::Version(latest)); + .insert("latest".into(), UnresolvedVersionSpec::Version(latest)); } + // if let Some(canary) = output.canary { + // resolver + // .aliases + // .insert("canary".into(), UnresolvedVersionSpec::Version(canary)); + // } + // Sort from newest to oldest resolver.versions.sort_by(|a, d| d.cmp(a)); - // resolver.canary_versions.sort_by(|a, d| d.cmp(a)); resolver } @@ -43,12 +49,12 @@ impl<'tool> VersionResolver<'tool> { Ok(()) } - pub fn resolve(&self, candidate: &VersionType) -> miette::Result { + pub fn resolve(&self, candidate: &UnresolvedVersionSpec) -> miette::Result { resolve_version(candidate, &self.versions, &self.aliases, self.manifest) } } -pub fn match_highest_version<'l, I>(req: &'l VersionReq, versions: I) -> Option +pub fn match_highest_version<'l, I>(req: &'l VersionReq, versions: I) -> Option where I: IntoIterator, { @@ -62,52 +68,86 @@ where } } - highest_match + highest_match.map(VersionSpec::Version) +} + +// Filter out aliases because they cannot be matched against +fn extract_installed_versions(installed: &HashSet) -> HashSet<&Version> { + installed + .iter() + .filter_map(|item| match item { + VersionSpec::Alias(_) => None, + VersionSpec::Version(v) => Some(v), + }) + .collect() } pub fn resolve_version( - candidate: &VersionType, + candidate: &UnresolvedVersionSpec, versions: &[Version], - aliases: &BTreeMap, + aliases: &BTreeMap, manifest: Option<&ToolManifest>, -) -> miette::Result { +) -> miette::Result { + let installed_versions = if let Some(manifest) = manifest { + extract_installed_versions(&manifest.installed_versions) + } else { + HashSet::new() + }; + match &candidate { - VersionType::Alias(alias) => { - if let Some(alias_type) = aliases.get(alias) { - return resolve_version(alias_type, versions, aliases, manifest); + UnresolvedVersionSpec::Alias(alias) => { + let mut alias_value = None; + + if let Some(manifest) = manifest { + alias_value = manifest.aliases.get(alias); + } + + if alias_value.is_none() { + alias_value = aliases.get(alias); + } + + if let Some(value) = alias_value { + return resolve_version(value, 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) { + UnresolvedVersionSpec::Req(req) => { + // Check locally installed versions first + if !installed_versions.is_empty() { + if let Some(version) = match_highest_version(req, installed_versions) { return Ok(version); } } + // Otherwise we'll need to download from remote if let Some(version) = match_highest_version(req, versions) { return Ok(version); } } - VersionType::ReqAny(reqs) => { + UnresolvedVersionSpec::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) - { + // Check locally installed versions first + if !installed_versions.is_empty() { + if let Some(version) = match_highest_version(req, installed_versions.clone()) { return Ok(version); } } + // Otherwise we'll need to download from remote if let Some(version) = match_highest_version(req, versions) { return Ok(version); } } } - VersionType::Version(ver) => { + UnresolvedVersionSpec::Version(ver) => { + // Check locally installed versions first + if installed_versions.contains(ver) { + return Ok(VersionSpec::Version(ver.to_owned())); + } + + // Otherwise we'll need to download from remote for version in versions { if ver == version { - return Ok(ver.to_owned()); + return Ok(VersionSpec::Version(ver.to_owned())); } } } diff --git a/crates/core/tests/tools_config_test.rs b/crates/core/tests/tools_config_test.rs index 59b1d9962..d16702c23 100644 --- a/crates/core/tests/tools_config_test.rs +++ b/crates/core/tests/tools_config_test.rs @@ -1,4 +1,4 @@ -use proto_core::{AliasOrVersion, ToolsConfig}; +use proto_core::{ToolsConfig, UnresolvedVersionSpec}; use starbase_sandbox::create_empty_sandbox; use std::collections::BTreeMap; use std::str::FromStr; @@ -56,8 +56,14 @@ kebab-case = "source:./camel.toml" assert_eq!( config.tools, BTreeMap::from_iter([ - (Id::raw("node"), AliasOrVersion::from_str("12.0.0").unwrap()), - (Id::raw("rust"), AliasOrVersion::Alias("stable".into())), + ( + Id::raw("node"), + UnresolvedVersionSpec::from_str("12.0.0").unwrap() + ), + ( + Id::raw("rust"), + UnresolvedVersionSpec::Alias("stable".into()) + ), ]) ); @@ -87,12 +93,14 @@ kebab-case = "source:./camel.toml" let sandbox = create_empty_sandbox(); let mut config = ToolsConfig::load_from(sandbox.path()).unwrap(); - config - .tools - .insert(Id::raw("node"), AliasOrVersion::from_str("12.0.0").unwrap()); - config - .tools - .insert(Id::raw("rust"), AliasOrVersion::Alias("stable".into())); + config.tools.insert( + Id::raw("node"), + UnresolvedVersionSpec::from_str("12.0.0").unwrap(), + ); + config.tools.insert( + Id::raw("rust"), + UnresolvedVersionSpec::Alias("stable".into()), + ); config.plugins.insert( Id::raw("foo"), @@ -159,9 +167,18 @@ foo = "source:./test.toml" assert_eq!( config.tools, BTreeMap::from_iter([ - (Id::raw("node"), AliasOrVersion::parse("1.2.3").unwrap()), - (Id::raw("bun"), AliasOrVersion::parse("4.5.6").unwrap()), - (Id::raw("deno"), AliasOrVersion::parse("7.8.9").unwrap()), + ( + Id::raw("node"), + UnresolvedVersionSpec::parse("1.2.3").unwrap() + ), + ( + Id::raw("bun"), + UnresolvedVersionSpec::parse("4.5.6").unwrap() + ), + ( + Id::raw("deno"), + UnresolvedVersionSpec::parse("7.8.9").unwrap() + ), ]) ); diff --git a/crates/core/tests/version_resolver_test.rs b/crates/core/tests/version_resolver_test.rs index b0b49246f..f747d28f8 100644 --- a/crates/core/tests/version_resolver_test.rs +++ b/crates/core/tests/version_resolver_test.rs @@ -1,4 +1,4 @@ -use proto_core::{resolve_version, VersionType}; +use proto_core::{resolve_version, ToolManifest, UnresolvedVersionSpec, VersionSpec}; use semver::Version; use std::collections::BTreeMap; @@ -19,21 +19,49 @@ mod version_resolver { ] } - fn create_aliases() -> BTreeMap { + fn create_aliases() -> BTreeMap { BTreeMap::from_iter([ ( "latest".into(), - VersionType::Version(Version::new(10, 0, 0)), + UnresolvedVersionSpec::Version(Version::new(10, 0, 0)), + ), + ( + "stable".into(), + UnresolvedVersionSpec::Alias("latest".into()), ), - ("stable".into(), VersionType::Alias("latest".into())), ( "no-version".into(), - VersionType::Version(Version::new(20, 0, 0)), + UnresolvedVersionSpec::Version(Version::new(20, 0, 0)), + ), + ( + "no-alias".into(), + UnresolvedVersionSpec::Alias("missing".into()), ), - ("no-alias".into(), VersionType::Alias("missing".into())), ]) } + fn create_manifest() -> ToolManifest { + let mut manifest = ToolManifest::default(); + + manifest.aliases.insert( + "latest-manifest".into(), + UnresolvedVersionSpec::Version(Version::new(8, 0, 0)), + ); + manifest.aliases.insert( + "stable-manifest".into(), + UnresolvedVersionSpec::Alias("stable".into()), + ); + + manifest + .installed_versions + .insert(VersionSpec::parse("3.0.0").unwrap()); + manifest + .installed_versions + .insert(VersionSpec::parse("3.3.3").unwrap()); + + manifest + } + #[test] fn resolves_aliases() { let versions = create_versions(); @@ -41,7 +69,7 @@ mod version_resolver { assert_eq!( resolve_version( - &VersionType::Alias("latest".into()), + &UnresolvedVersionSpec::Alias("latest".into()), &versions, &aliases, None, @@ -52,7 +80,7 @@ mod version_resolver { assert_eq!( resolve_version( - &VersionType::Alias("stable".into()), + &UnresolvedVersionSpec::Alias("stable".into()), &versions, &aliases, None, @@ -62,6 +90,35 @@ mod version_resolver { ); } + #[test] + fn resolves_aliases_from_manifest() { + let versions = create_versions(); + let aliases = create_aliases(); + let manifest = create_manifest(); + + assert_eq!( + resolve_version( + &UnresolvedVersionSpec::Alias("latest-manifest".into()), + &versions, + &aliases, + Some(&manifest), + ) + .unwrap(), + Version::new(8, 0, 0) + ); + + assert_eq!( + resolve_version( + &UnresolvedVersionSpec::Alias("stable-manifest".into()), + &versions, + &aliases, + Some(&manifest), + ) + .unwrap(), + Version::new(10, 0, 0) + ); + } + #[test] #[should_panic(expected = "Failed to resolve a semantic version for unknown.")] fn errors_unknown_alias() { @@ -69,7 +126,7 @@ mod version_resolver { let aliases = create_aliases(); resolve_version( - &VersionType::Alias("unknown".into()), + &UnresolvedVersionSpec::Alias("unknown".into()), &versions, &aliases, None, @@ -84,7 +141,7 @@ mod version_resolver { let aliases = create_aliases(); resolve_version( - &VersionType::Alias("no-alias".into()), + &UnresolvedVersionSpec::Alias("no-alias".into()), &versions, &aliases, None, @@ -99,7 +156,7 @@ mod version_resolver { let aliases = create_aliases(); resolve_version( - &VersionType::Alias("no-version".into()), + &UnresolvedVersionSpec::Alias("no-version".into()), &versions, &aliases, None, @@ -114,7 +171,7 @@ mod version_resolver { assert_eq!( resolve_version( - &VersionType::Version(Version::new(1, 10, 5)), + &UnresolvedVersionSpec::Version(Version::new(1, 10, 5)), &versions, &aliases, None, @@ -125,7 +182,7 @@ mod version_resolver { assert_eq!( resolve_version( - &VersionType::Version(Version::new(8, 0, 0)), + &UnresolvedVersionSpec::Version(Version::new(8, 0, 0)), &versions, &aliases, None, @@ -135,6 +192,24 @@ mod version_resolver { ); } + #[test] + fn resolves_versions_from_manifest() { + let versions = create_versions(); + let aliases = create_aliases(); + let manifest = create_manifest(); + + assert_eq!( + resolve_version( + &UnresolvedVersionSpec::Version(Version::new(3, 0, 0)), + &versions, + &aliases, + Some(&manifest), + ) + .unwrap(), + Version::new(3, 0, 0) + ); + } + #[test] fn resolves_partial_versions() { let versions = create_versions(); @@ -142,7 +217,7 @@ mod version_resolver { assert_eq!( resolve_version( - &VersionType::parse("1.2").unwrap(), + &UnresolvedVersionSpec::parse("1.2").unwrap(), &versions, &aliases, None, @@ -153,7 +228,7 @@ mod version_resolver { assert_eq!( resolve_version( - &VersionType::parse("1.0").unwrap(), + &UnresolvedVersionSpec::parse("1.0").unwrap(), &versions, &aliases, None, @@ -163,11 +238,46 @@ mod version_resolver { ); assert_eq!( - resolve_version(&VersionType::parse("1").unwrap(), &versions, &aliases, None,).unwrap(), + resolve_version( + &UnresolvedVersionSpec::parse("1").unwrap(), + &versions, + &aliases, + None, + ) + .unwrap(), Version::new(1, 10, 5) ); } + #[test] + fn resolves_partial_versions_with_manifest() { + let versions = create_versions(); + let aliases = create_aliases(); + let manifest = create_manifest(); + + assert_eq!( + resolve_version( + &UnresolvedVersionSpec::parse("3.3").unwrap(), + &versions, + &aliases, + Some(&manifest), + ) + .unwrap(), + Version::new(3, 3, 3) + ); + + assert_eq!( + resolve_version( + &UnresolvedVersionSpec::parse("3").unwrap(), + &versions, + &aliases, + Some(&manifest), + ) + .unwrap(), + Version::new(3, 3, 3) + ); + } + #[test] fn removes_v_prefix() { let versions = create_versions(); @@ -175,7 +285,7 @@ mod version_resolver { assert_eq!( resolve_version( - &VersionType::parse("v8.0.0").unwrap(), + &UnresolvedVersionSpec::parse("v8.0.0").unwrap(), &versions, &aliases, None, @@ -186,7 +296,7 @@ mod version_resolver { assert_eq!( resolve_version( - &VersionType::parse("V8").unwrap(), + &UnresolvedVersionSpec::parse("V8").unwrap(), &versions, &aliases, None, @@ -203,7 +313,7 @@ mod version_resolver { let aliases = create_aliases(); resolve_version( - &VersionType::Version(Version::new(20, 0, 0)), + &UnresolvedVersionSpec::Version(Version::new(20, 0, 0)), &versions, &aliases, None, @@ -218,7 +328,7 @@ mod version_resolver { assert_eq!( resolve_version( - &VersionType::parse("^8").unwrap(), + &UnresolvedVersionSpec::parse("^8").unwrap(), &versions, &aliases, None, @@ -229,7 +339,7 @@ mod version_resolver { assert_eq!( resolve_version( - &VersionType::parse("~1.1").unwrap(), + &UnresolvedVersionSpec::parse("~1.1").unwrap(), &versions, &aliases, None, @@ -240,7 +350,7 @@ mod version_resolver { assert_eq!( resolve_version( - &VersionType::parse(">1 <10").unwrap(), + &UnresolvedVersionSpec::parse(">1 <10").unwrap(), &versions, &aliases, None, @@ -251,7 +361,7 @@ mod version_resolver { assert_eq!( resolve_version( - &VersionType::parse(">1, <10").unwrap(), + &UnresolvedVersionSpec::parse(">1, <10").unwrap(), &versions, &aliases, None, @@ -263,7 +373,7 @@ mod version_resolver { // Highest match assert_eq!( resolve_version( - &VersionType::parse("^1").unwrap(), + &UnresolvedVersionSpec::parse("^1").unwrap(), &versions, &aliases, None, @@ -274,7 +384,13 @@ mod version_resolver { // Star (latest) assert_eq!( - resolve_version(&VersionType::parse("*").unwrap(), &versions, &aliases, None,).unwrap(), + resolve_version( + &UnresolvedVersionSpec::parse("*").unwrap(), + &versions, + &aliases, + None, + ) + .unwrap(), Version::new(10, 0, 0) ); } @@ -286,7 +402,7 @@ mod version_resolver { let aliases = create_aliases(); resolve_version( - &VersionType::parse("^20").unwrap(), + &UnresolvedVersionSpec::parse("^20").unwrap(), &versions, &aliases, None, @@ -301,7 +417,7 @@ mod version_resolver { assert_eq!( resolve_version( - &VersionType::parse("^1 || ^6 || ^8").unwrap(), + &UnresolvedVersionSpec::parse("^1 || ^6 || ^8").unwrap(), &versions, &aliases, None, @@ -318,7 +434,7 @@ mod version_resolver { let aliases = create_aliases(); resolve_version( - &VersionType::parse("^3 || ^5 || ^9").unwrap(), + &UnresolvedVersionSpec::parse("^3 || ^5 || ^9").unwrap(), &versions, &aliases, None, @@ -332,7 +448,13 @@ 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, &aliases, None).unwrap(); + resolve_version( + &UnresolvedVersionSpec::parse(req).unwrap(), + &versions, + &aliases, + None, + ) + .unwrap(); } } } diff --git a/crates/core/tests/version_test.rs b/crates/core/tests/version_test.rs index a3cf93530..2ff7cad0c 100644 --- a/crates/core/tests/version_test.rs +++ b/crates/core/tests/version_test.rs @@ -1,4 +1,4 @@ -use proto_core::VersionType; +use proto_core::UnresolvedVersionSpec; use semver::{Version, VersionReq}; use std::str::FromStr; @@ -8,16 +8,16 @@ mod version_type { #[test] fn parses_alias() { assert_eq!( - VersionType::from_str("stable").unwrap(), - VersionType::Alias("stable".to_owned()) + UnresolvedVersionSpec::from_str("stable").unwrap(), + UnresolvedVersionSpec::Alias("stable".to_owned()) ); assert_eq!( - VersionType::from_str("latest").unwrap(), - VersionType::Alias("latest".to_owned()) + UnresolvedVersionSpec::from_str("latest").unwrap(), + UnresolvedVersionSpec::Alias("latest".to_owned()) ); assert_eq!( - VersionType::from_str("lts-2014").unwrap(), - VersionType::Alias("lts-2014".to_owned()) + UnresolvedVersionSpec::from_str("lts-2014").unwrap(), + UnresolvedVersionSpec::Alias("lts-2014".to_owned()) ); } @@ -25,8 +25,8 @@ mod version_type { fn parses_req() { for req in ["=1.2.3", "^1.2", "~1", ">1.2.0", "<1", "*", ">1, <=1.5"] { assert_eq!( - VersionType::from_str(req).unwrap(), - VersionType::ReqAll(VersionReq::parse(req).unwrap()) + UnresolvedVersionSpec::from_str(req).unwrap(), + UnresolvedVersionSpec::Req(VersionReq::parse(req).unwrap()) ); } } @@ -34,24 +34,24 @@ mod version_type { #[test] fn parses_req_spaces() { assert_eq!( - VersionType::from_str("> 10").unwrap(), - VersionType::ReqAll(VersionReq::parse(">10").unwrap()) + UnresolvedVersionSpec::from_str("> 10").unwrap(), + UnresolvedVersionSpec::Req(VersionReq::parse(">10").unwrap()) ); assert_eq!( - VersionType::from_str("1.2 , 2").unwrap(), - VersionType::ReqAll(VersionReq::parse("1.2, 2").unwrap()) + UnresolvedVersionSpec::from_str("1.2 , 2").unwrap(), + UnresolvedVersionSpec::Req(VersionReq::parse("1.2, 2").unwrap()) ); assert_eq!( - VersionType::from_str(">= 1.2 < 2").unwrap(), - VersionType::ReqAll(VersionReq::parse(">=1.2, <2").unwrap()) + UnresolvedVersionSpec::from_str(">= 1.2 < 2").unwrap(), + UnresolvedVersionSpec::Req(VersionReq::parse(">=1.2, <2").unwrap()) ); } #[test] fn parses_req_any() { assert_eq!( - VersionType::from_str("^1 || ~2 || =3").unwrap(), - VersionType::ReqAny(vec![ + UnresolvedVersionSpec::from_str("^1 || ~2 || =3").unwrap(), + UnresolvedVersionSpec::ReqAny(vec![ VersionReq::parse("~2").unwrap(), VersionReq::parse("^1").unwrap(), VersionReq::parse("=3").unwrap(), @@ -62,16 +62,16 @@ mod version_type { #[test] fn sorts_any_req() { assert_eq!( - VersionType::from_str("^1 || ^2 || ^3").unwrap(), - VersionType::ReqAny(vec![ + UnresolvedVersionSpec::from_str("^1 || ^2 || ^3").unwrap(), + UnresolvedVersionSpec::ReqAny(vec![ VersionReq::parse("^3").unwrap(), VersionReq::parse("^2").unwrap(), VersionReq::parse("^1").unwrap(), ]) ); assert_eq!( - VersionType::from_str("^1.1 || ^1.10 || ^1.10.1 || ^1.2").unwrap(), - VersionType::ReqAny(vec![ + UnresolvedVersionSpec::from_str("^1.1 || ^1.10 || ^1.10.1 || ^1.2").unwrap(), + UnresolvedVersionSpec::ReqAny(vec![ VersionReq::parse("^1.10.1").unwrap(), VersionReq::parse("^1.10").unwrap(), VersionReq::parse("^1.2").unwrap(), @@ -84,8 +84,8 @@ mod version_type { fn parses_version() { for req in ["1.2.3", "4.5.6", "7.8.9-alpha", "10.11.12+build"] { assert_eq!( - VersionType::from_str(req).unwrap(), - VersionType::Version(Version::parse(req).unwrap()) + UnresolvedVersionSpec::from_str(req).unwrap(), + UnresolvedVersionSpec::Version(Version::parse(req).unwrap()) ); } } @@ -93,24 +93,24 @@ mod version_type { #[test] fn parses_version_with_v() { assert_eq!( - VersionType::from_str("v1.2.3").unwrap(), - VersionType::Version(Version::parse("1.2.3").unwrap()) + UnresolvedVersionSpec::from_str("v1.2.3").unwrap(), + UnresolvedVersionSpec::Version(Version::parse("1.2.3").unwrap()) ); } #[test] fn no_patch_becomes_req() { assert_eq!( - VersionType::from_str("1.2").unwrap(), - VersionType::ReqAll(VersionReq::parse("~1.2").unwrap()) + UnresolvedVersionSpec::from_str("1.2").unwrap(), + UnresolvedVersionSpec::Req(VersionReq::parse("~1.2").unwrap()) ); } #[test] fn no_minor_becomes_req() { assert_eq!( - VersionType::from_str("1").unwrap(), - VersionType::ReqAll(VersionReq::parse("~1").unwrap()) + UnresolvedVersionSpec::from_str("1").unwrap(), + UnresolvedVersionSpec::Req(VersionReq::parse("~1").unwrap()) ); } } diff --git a/crates/pdk-api/src/api.rs b/crates/pdk-api/src/api.rs index 8b1f8e407..f75c207d7 100644 --- a/crates/pdk-api/src/api.rs +++ b/crates/pdk-api/src/api.rs @@ -382,6 +382,10 @@ json_struct!( json_struct!( /// Output returned by the `load_versions` function. pub struct LoadVersionsOutput { + /// Latest canary version. + #[serde(skip_serializing_if = "Option::is_none")] + pub canary: Option, + /// Latest stable version. #[serde(skip_serializing_if = "Option::is_none")] pub latest: Option, @@ -391,9 +395,6 @@ json_struct!( /// List of available production versions to install. pub versions: Vec, - - /// List of available canary versions to install. - pub canary_versions: Vec, } ); diff --git a/crates/pdk-test-utils/src/lib.rs b/crates/pdk-test-utils/src/lib.rs index 4eeeb3945..4598b6254 100644 --- a/crates/pdk-test-utils/src/lib.rs +++ b/crates/pdk-test-utils/src/lib.rs @@ -4,8 +4,8 @@ mod wrapper; pub use macros::*; pub use proto_core as core; pub use proto_core::{ - AliasOrVersion, Id, ProtoEnvironment, Tool, ToolManifest, ToolsConfig, UserConfig, Version, - VersionReq, VersionType, + Id, ProtoEnvironment, Tool, ToolManifest, ToolsConfig, UnresolvedVersionSpec, UserConfig, + Version, VersionReq, VersionSpec, }; pub use proto_pdk_api::*; pub use wrapper::WasmTestWrapper; diff --git a/crates/pdk-test-utils/src/macros.rs b/crates/pdk-test-utils/src/macros.rs index 0724e016c..f0ea99ce6 100644 --- a/crates/pdk-test-utils/src/macros.rs +++ b/crates/pdk-test-utils/src/macros.rs @@ -15,7 +15,7 @@ macro_rules! generate_download_install_tests { plugin .tool - .setup(&proto_pdk_test_utils::VersionType::parse($version).unwrap()) + .setup(&proto_pdk_test_utils::UnresolvedVersionSpec::parse($version).unwrap()) .await .unwrap(); @@ -51,7 +51,7 @@ macro_rules! generate_download_install_tests { }; let mut tool = plugin.tool; - tool.version = Some(proto_pdk_test_utils::AliasOrVersion::parse($version).unwrap()); + tool.version = Some(proto_pdk_test_utils::VersionSpec::parse($version).unwrap()); let download_file = tool .install_from_prebuilt(&tool.get_tool_dir()) @@ -72,7 +72,7 @@ macro_rules! generate_download_install_tests { let mut tool = plugin.tool; let version_inst = proto_pdk_test_utils::Version::parse($version).unwrap(); - tool.version = Some(proto_pdk_test_utils::AliasOrVersion::Version( + tool.version = Some(proto_pdk_test_utils::VersionSpec::Version( version_inst.clone(), )); @@ -101,7 +101,7 @@ macro_rules! generate_resolve_versions_tests { }; plugin.tool.resolve_version( - &proto_pdk_test_utils::VersionType::parse("latest").unwrap(), + &proto_pdk_test_utils::UnresolvedVersionSpec::parse("latest").unwrap(), ).await.unwrap(); assert_ne!(plugin.tool.get_resolved_version(), "latest"); @@ -118,7 +118,7 @@ macro_rules! generate_resolve_versions_tests { $( plugin.tool.resolve_version( - &proto_pdk_test_utils::VersionType::parse($k).unwrap(), + &proto_pdk_test_utils::UnresolvedVersionSpec::parse($k).unwrap(), ).await.unwrap(); assert_eq!( @@ -158,7 +158,7 @@ macro_rules! generate_resolve_versions_tests { }; plugin.tool.resolve_version( - &proto_pdk_test_utils::VersionType::parse("unknown").unwrap(), + &proto_pdk_test_utils::UnresolvedVersionSpec::parse("unknown").unwrap(), ).await.unwrap(); } @@ -173,7 +173,7 @@ macro_rules! generate_resolve_versions_tests { }; plugin.tool.resolve_version( - &proto_pdk_test_utils::VersionType::parse("99.99.99").unwrap(), + &proto_pdk_test_utils::UnresolvedVersionSpec::parse("99.99.99").unwrap(), ).await.unwrap(); } }; diff --git a/crates/pdk/src/helpers.rs b/crates/pdk/src/helpers.rs index 3d89f622f..cba2d0964 100644 --- a/crates/pdk/src/helpers.rs +++ b/crates/pdk/src/helpers.rs @@ -132,6 +132,24 @@ pub fn check_supported_os_and_arch( Ok(()) } +/// Check whether a command exists or not on the host machine. +pub fn command_exists(env: &HostEnvironment, command: &str) -> bool { + let result = if env.os == HostOS::Windows { + let line = format!("Get-Command {command}"); + + unsafe { + exec_command(Json(ExecCommandInput::pipe( + "powershell", + ["-Command", &line], + ))) + } + } else { + unsafe { exec_command(Json(ExecCommandInput::pipe("which", [command]))) } + }; + + result.is_err() || result.is_ok_and(|out| out.0.exit_code != 0) +} + /// Detect whether the current OS is utilizing musl instead of gnu. pub fn is_musl(env: &HostEnvironment) -> bool { if !env.os.is_linux() {