diff --git a/CHANGELOG.md b/CHANGELOG.md index ab23d5b7f..1487e3272 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,30 @@ - [Rust](https://github.com/moonrepo/rust-plugin/blob/master/CHANGELOG.md) - [TOML schema](https://github.com/moonrepo/schema-plugin/blob/master/CHANGELOG.md) +## Unreleased + +#### 🚀 Updates + +- Refactored and standardized how executables (bins and shims) are managed. + - Binaries (`~/.proto/bin`) and shims (`~/.proto/shims`) now share the same internal data structures. + - For the most part, is a 1:1 relation. There will be a shim for every binary, and vice versa. + - Reduced the amount of WASM calls to locate executables to 1 call. + - Removed the concept of local shims (was a hidden implementation detail). +- Reworked the `proto bin` command. + - By default returns an absolute path to the real executable (`~/.proto/tools///bin`). + - Pass `--bin` to return the `~/.proto/bin` path. + - Pass `--shim` to return the `~/.proto/shims` path. +- Updated `proto clean --purge` and `proto uninstall` to accurately delete all executables. +- WASM API + - Added `locate_executables` function. + - Added `LocateExecutablesInput`, `LocateExecutablesOutput`, `ExecutableConfig` structs. + - Deprecated `locate_bins` and `create_shims` functions. + - Deprecated `LocateBinsInput`, `LocateBinsOutput`, `CreateShimsInput`, `CreateShimsOutput`, `ShimConfig` structs. + +#### ⚙️ Internal + +- Plugin versions are now pinned and tied to proto releases to avoid unintended drift and API changes. + ## 0.21.1 #### 🐞 Fixes diff --git a/Cargo.lock b/Cargo.lock index 529d9fd56..50077a1c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1826,9 +1826,9 @@ dependencies = [ [[package]] name = "json_comments" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ee439ee368ba4a77ac70d04f14015415af8600d6c894dc1f11bd79758c57d5" +checksum = "9dbbfed4e59ba9750e15ba154fdfd9329cee16ff3df539c2666b70f58cc32105" [[package]] name = "lazy_static" @@ -2318,6 +2318,7 @@ dependencies = [ "reqwest", "semver", "serde", + "shell-words", "starbase", "starbase_archive", "starbase_sandbox", @@ -2332,7 +2333,7 @@ dependencies = [ [[package]] name = "proto_core" -version = "0.21.2" +version = "0.22.1" dependencies = [ "cached", "extism", @@ -2362,7 +2363,7 @@ dependencies = [ [[package]] name = "proto_pdk" -version = "0.9.0" +version = "0.10.1" dependencies = [ "anyhow", "extism-pdk", @@ -2372,7 +2373,7 @@ dependencies = [ [[package]] name = "proto_pdk_api" -version = "0.9.0" +version = "0.10.1" dependencies = [ "anyhow", "semver", @@ -2386,13 +2387,13 @@ dependencies = [ [[package]] name = "proto_pdk_test_utils" -version = "0.9.1" +version = "0.10.1" dependencies = [ "extism", "proto_core", "proto_pdk_api", "serde_json", - "toml 0.8.5", + "toml 0.8.6", ] [[package]] @@ -2891,9 +2892,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -3143,9 +3144,9 @@ dependencies = [ [[package]] name = "starbase_utils" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9faf731068b919569efae7f6c7c6c3d2e29411c1cba666ef0c6fe20b82f50a1" +checksum = "d948dcd63f1dd11f2b7a9ab15c53e7866faa11ccd31377dca1625c106d40fcee" dependencies = [ "dirs 5.0.1", "fs4", @@ -3159,7 +3160,7 @@ dependencies = [ "starbase_styles", "thiserror", "tokio", - "toml 0.8.5", + "toml 0.8.6", "tracing", "wax", ] @@ -3259,7 +3260,7 @@ dependencies = [ [[package]] name = "system_env" -version = "0.1.2" +version = "0.1.3" dependencies = [ "schematic", "serde", @@ -3521,14 +3522,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3efaf127c78d5339cc547cce4e4d973bd5e4f56e949a06d091c082ebeef2f800" +checksum = "8ff9e3abce27ee2c9a37f9ad37238c1bdd4e789c84ba37df76aa4d528f5072cc" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.20.5", + "toml_edit 0.20.7", ] [[package]] @@ -3555,9 +3556,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.20.5" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "782bf6c2ddf761c1e7855405e8975472acf76f7f36d0d4328bd3b7a2fae12a85" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ "indexmap 2.0.2", "serde", @@ -3794,10 +3795,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "version_spec" -version = "0.1.3" +version = "0.1.4" dependencies = [ "human-sort", - "once_cell", "regex", "semver", "serde", diff --git a/Cargo.toml b/Cargo.toml index 5e26458ba..278fb3c26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,17 +14,14 @@ human-sort = "0.2.2" miette = "5.10.0" once_cell = "1.18.0" once_map = "0.4.10" -regex = { version = "1.10.2", default-features = false, features = [ - "std", - "unicode-perl", -] } +regex = { version = "1.10.2", default-features = false, features = ["std"] } reqwest = { version = "0.11.22", default-features = false } schematic = { version = "0.12.7", default-features = false, features = [ "schema", ] } semver = "1.0.20" serde = { version = "1.0.190", features = ["derive"] } -serde_json = "1.0.107" +serde_json = "1.0.108" sha2 = "0.10.8" starbase = "0.2.9" starbase_archive = { version = "0.2.4", features = [ @@ -37,7 +34,7 @@ starbase_archive = { version = "0.2.4", features = [ starbase_events = "0.2.2" starbase_sandbox = { version = "0.1.12" } starbase_styles = "0.1.16" -starbase_utils = { version = "0.3.6", default-features = false, features = [ +starbase_utils = { version = "0.3.7", default-features = false, features = [ "json", "toml", ] } diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index c4d5249f2..a15b80550 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -27,9 +27,9 @@ name = "proto" path = "src/main.rs" [dependencies] -proto_core = { version = "0.21.2", path = "../core" } -proto_pdk_api = { version = "0.9.0", path = "../pdk-api" } -system_env = { version = "0.1.2", path = "../system-env" } +proto_core = { version = "0.22.1", path = "../core" } +proto_pdk_api = { version = "0.10.1", path = "../pdk-api" } +system_env = { version = "0.1.3", path = "../system-env" } chrono = "0.4.31" clap = { workspace = true, features = ["derive", "env"] } clap_complete = { workspace = true } @@ -43,6 +43,7 @@ miette = { workspace = true } reqwest = { workspace = true, features = ["rustls-tls-native-roots", "stream"] } semver = { workspace = true } serde = { workspace = true } +shell-words = "1.1.0" starbase = { workspace = true } starbase_archive = { workspace = true } starbase_styles = { workspace = true } diff --git a/crates/cli/src/app.rs b/crates/cli/src/app.rs index 3ff44a05b..c313a52f7 100644 --- a/crates/cli/src/app.rs +++ b/crates/cli/src/app.rs @@ -99,8 +99,8 @@ pub enum Commands { #[command( name = "bin", - about = "Display the absolute path to a tools binary.", - long_about = "Display the absolute path to a tools binary. If no version is provided,\nit will detected from the current environment." + about = "Display the absolute path to a tools executable.", + long_about = "Display the absolute path to a tools executable. If no version is provided,\nit will be detected from the current environment." )] Bin(BinArgs), @@ -144,7 +144,7 @@ pub enum Commands { alias = "lsg", name = "list-global", about = "List installed globals.", - long_about = "List installed globals by scanning the global bins installation directory. Will return the canonical source path." + long_about = "List installed globals by scanning the global packages installation directory. Will return the canonical source path." )] ListGlobal(ListGlobalArgs), @@ -191,7 +191,7 @@ pub enum Commands { alias = "r", name = "run", about = "Run a tool after detecting a version from the environment.", - long_about = "Run a tool after detecting a version from the environment. In order of priority,\na version will be resolved from a provided CLI argument, a PROTO_VERSION environment variable,\na local version file (.prototools), and lastly a global version file (~/.proto/tools/version).\n\nIf no version can be found, the program will exit with an error." + long_about = "Run a tool after detecting a version from the environment. In order of priority,\na version will be resolved from a provided CLI argument, a PROTO_VERSION environment variable,\na local version file (.prototools), and lastly a global version file (~/.proto/tools).\n\nIf no version can be found, the program will exit with an error." )] Run(RunArgs), diff --git a/crates/cli/src/commands/bin.rs b/crates/cli/src/commands/bin.rs index f3f3028a4..6fa892ee7 100644 --- a/crates/cli/src/commands/bin.rs +++ b/crates/cli/src/commands/bin.rs @@ -7,6 +7,9 @@ pub struct BinArgs { #[arg(required = true, help = "ID of tool")] id: Id, + #[arg(long, help = "Display symlinked binary path when available")] + bin: bool, + #[arg(help = "Version or alias of tool")] spec: Option, @@ -20,17 +23,25 @@ pub async fn bin(args: ArgsRef) { let version = detect_version(&tool, args.spec.clone()).await?; tool.resolve_version(&version).await?; - tool.locate_bins().await?; + tool.create_executables(true, false).await?; + + if args.bin { + for bin in tool.get_bin_locations()? { + if bin.primary { + println!("{}", bin.path.display()); + return Ok(()); + } + } + } if args.shim { - tool.setup_shims(true).await?; - - if let Some(shim_path) = tool.get_shim_path() { - println!("{}", shim_path.to_string_lossy()); - - return Ok(()); + for shim in tool.get_shim_locations()? { + if shim.primary { + println!("{}", shim.path.display()); + return Ok(()); + } } } - println!("{}", tool.get_bin_path()?.to_string_lossy()); + println!("{}", tool.get_exe_path()?.display()); } diff --git a/crates/cli/src/commands/clean.rs b/crates/cli/src/commands/clean.rs index 0640ca8b7..cc35204e0 100644 --- a/crates/cli/src/commands/clean.rs +++ b/crates/cli/src/commands/clean.rs @@ -1,10 +1,8 @@ use clap::Args; use dialoguer::Confirm; use proto_core::{ - get_plugins_dir, get_shim_file_name, get_temp_dir, load_tool, Id, ProtoError, Tool, - ToolsConfig, VersionSpec, + get_plugins_dir, get_temp_dir, load_tool, Id, ProtoError, Tool, ToolsConfig, VersionSpec, }; -use proto_pdk_api::{CreateShimsInput, CreateShimsOutput}; use starbase::diagnostics::IntoDiagnostic; use starbase::{system, SystemResult}; use starbase_styles::color; @@ -198,31 +196,14 @@ async fn purge_tool(id: &Id, yes: bool) -> SystemResult { // Delete inventory fs::remove_dir_all(inventory_dir)?; - // Delete binary - fs::remove_file(tool.proto.bin_dir.join(tool.get_bin_name()))?; + // Delete binaries + for bin in tool.get_bin_locations()? { + fs::remove_file(bin.path)?; + } // Delete shims - fs::remove_file( - tool.proto - .shims_dir - .join(get_shim_file_name(id.as_str(), true)), - )?; - - if tool.plugin.has_func("create_shims") { - let shim_configs: CreateShimsOutput = tool.plugin.cache_func_with( - "create_shims", - CreateShimsInput { - context: tool.create_context(), - }, - )?; - - for global_shim in shim_configs.global_shims.keys() { - fs::remove_file( - tool.proto - .shims_dir - .join(get_shim_file_name(global_shim, true)), - )?; - } + for shim in tool.get_shim_locations()? { + fs::remove_file(shim.path)?; } info!("Removed {}", tool.get_name()); diff --git a/crates/cli/src/commands/install.rs b/crates/cli/src/commands/install.rs index a0ae6d7f2..b0cd69792 100644 --- a/crates/cli/src/commands/install.rs +++ b/crates/cli/src/commands/install.rs @@ -41,7 +41,7 @@ pub struct InstallArgs { pub passthrough: Vec, } -fn pin_version( +async fn pin_version( tool: &mut Tool, initial_version: &UnresolvedVersionSpec, global: bool, @@ -56,7 +56,7 @@ fn pin_version( if global { args.global = true; - return internal_pin(tool, &args, true); + return internal_pin(tool, &args, true).await; } // via `pin-latest` setting @@ -66,7 +66,7 @@ fn pin_version( if let Some(pin_type) = user_config.pin_latest { args.global = matches!(pin_type, PinType::Global); - return internal_pin(tool, &args, true); + return internal_pin(tool, &args, true).await; } } @@ -89,7 +89,7 @@ pub async fn internal_install(args: InstallArgs, tool: Option) -> miette:: tool.disable_caching(); if !version.is_canary() && tool.is_setup(&version).await? { - pin_version(&mut tool, &version, args.pin)?; + pin_version(&mut tool, &version, args.pin).await?; info!( "{} has already been installed at {}", @@ -138,7 +138,7 @@ pub async fn internal_install(args: InstallArgs, tool: Option) -> miette:: return Ok(tool); } - pin_version(&mut tool, &version, args.pin)?; + pin_version(&mut tool, &version, args.pin).await?; info!( "{} has been installed to {}!", diff --git a/crates/cli/src/commands/migrate/v0_20.rs b/crates/cli/src/commands/migrate/v0_20.rs index e3aa1229e..d003c768a 100644 --- a/crates/cli/src/commands/migrate/v0_20.rs +++ b/crates/cli/src/commands/migrate/v0_20.rs @@ -42,7 +42,7 @@ pub async fn migrate() -> SystemResult { for tool in &mut tools { // Always create shims for all active tools - tool.setup_shims(true).await?; + tool.generate_shims(true).await?; } info!("Linking new binaries..."); @@ -50,8 +50,7 @@ pub async fn migrate() -> SystemResult { for tool in &mut tools { // Only the global version is linked, so only create if set if tool.manifest.default_version.is_some() { - tool.locate_bins().await?; - tool.setup_bin_link(true)?; + tool.symlink_bins(true).await?; } } diff --git a/crates/cli/src/commands/pin.rs b/crates/cli/src/commands/pin.rs index c9ec6d051..164b7e2a5 100644 --- a/crates/cli/src/commands/pin.rs +++ b/crates/cli/src/commands/pin.rs @@ -19,7 +19,7 @@ pub struct PinArgs { pub global: bool, } -pub fn internal_pin(tool: &mut Tool, args: &PinArgs, link: bool) -> SystemResult { +pub async fn internal_pin(tool: &mut Tool, args: &PinArgs, link: bool) -> SystemResult { if args.global { tool.manifest.default_version = Some(args.spec.clone()); tool.manifest.save()?; @@ -32,7 +32,7 @@ pub fn internal_pin(tool: &mut Tool, args: &PinArgs, link: bool) -> SystemResult // Create symlink to this new version if link { - tool.setup_bin_link(true)?; + tool.symlink_bins(true).await?; } } else { let mut config = ToolsConfig::load()?; @@ -53,7 +53,7 @@ pub fn internal_pin(tool: &mut Tool, args: &PinArgs, link: bool) -> SystemResult pub async fn pin(args: ArgsRef) -> SystemResult { let mut tool = load_tool(&args.id).await?; - internal_pin(&mut tool, args, false)?; + internal_pin(&mut tool, args, false).await?; info!( "Set the {} version to {}", diff --git a/crates/cli/src/commands/run.rs b/crates/cli/src/commands/run.rs index 188855a17..39c2deb7f 100644 --- a/crates/cli/src/commands/run.rs +++ b/crates/cli/src/commands/run.rs @@ -6,6 +6,7 @@ use proto_core::{detect_version, load_tool, Id, ProtoError, Tool, UnresolvedVers use proto_pdk_api::RunHook; use starbase::system; use std::env; +use std::path::Path; use std::process::exit; use system_env::is_command_on_path; use tokio::process::Command; @@ -19,7 +20,7 @@ pub struct RunArgs { #[arg(help = "Version or alias of tool")] spec: Option, - #[arg(long, help = "Path to an alternate binary to run")] + #[arg(long, help = "Path to a relative alternate binary to run")] bin: Option, // Passthrough args (after --) @@ -48,6 +49,30 @@ fn is_trying_to_self_upgrade(tool: &Tool, args: &[String]) -> bool { false } +fn create_command(exe_path: &Path, args: &[String]) -> Command { + match exe_path.extension().map(|e| e.to_str().unwrap()) { + Some("ps1" | "cmd" | "bat") => { + let mut cmd = Command::new(if is_command_on_path("pwsh") { + "pwsh" + } else { + "powershell" + }); + cmd.arg("-Command"); + cmd.arg(format!( + "{} {}", + exe_path.display(), + shell_words::join(args) + )); + cmd + } + _ => { + let mut cmd = Command::new(exe_path); + cmd.args(args); + cmd + } + } +} + #[system] pub async fn run(args: ArgsRef) -> SystemResult { let mut tool = load_tool(&args.id).await?; @@ -92,30 +117,26 @@ pub async fn run(args: ArgsRef) -> SystemResult { } // Determine the binary path to execute - let tool_dir = tool.get_tool_dir(); - let mut bin_path = tool.get_bin_path()?.to_path_buf(); + let exe_path = match &args.bin { + Some(alt_bin) => { + let alt_path = tool.get_tool_dir().join(alt_bin); - if let Some(alt_bin) = &args.bin { - let alt_bin_path = tool_dir.join(alt_bin); + debug!(bin = alt_bin, path = ?alt_path, "Received an alternate binary to run with"); - debug!(bin = alt_bin, path = ?alt_bin_path, "Received an alternate binary to run with"); - - if alt_bin_path.exists() { - bin_path = alt_bin_path; - } else { - return Err(ProtoCliError::MissingRunAltBin { - bin: alt_bin.to_owned(), - path: alt_bin_path, + if alt_path.exists() { + alt_path + } else { + return Err(ProtoCliError::MissingRunAltBin { + bin: alt_bin.to_owned(), + path: alt_path, + } + .into()); } - .into()); } - } else if let Some(shim_path) = tool.get_shim_path() { - bin_path = shim_path; - - debug!(shim = ?bin_path, "Using local shim for tool"); - } + None => tool.get_exe_path()?.to_path_buf(), + }; - debug!(bin = ?bin_path, args = ?args.passthrough, "Running {}", tool.get_name()); + debug!(bin = ?exe_path, args = ?args.passthrough, "Running {}", tool.get_name()); // Run before hook tool.run_hook("pre_run", || RunHook { @@ -124,44 +145,14 @@ pub async fn run(args: ArgsRef) -> SystemResult { })?; // Run the command - let mut command = match bin_path.extension().map(|e| e.to_str().unwrap()) { - Some("ps1") => { - let mut cmd = Command::new(if is_command_on_path("pwsh") { - "pwsh" - } else { - "powershell" - }); - cmd.arg("-Command").arg(format!( - "{} {}", - bin_path.display(), - args.passthrough.join(" ") - )); - cmd - } - Some("cmd" | "bat") => { - let mut cmd = Command::new("cmd"); - cmd.arg("/q").arg("/c").arg(format!( - "{} {}", - bin_path.display(), - args.passthrough.join(" ") - )); - cmd - } - _ => { - let mut cmd = Command::new(bin_path); - cmd.args(&args.passthrough); - cmd - } - }; - - let status = command + let status = create_command(&exe_path, &args.passthrough) .env( format!("{}_VERSION", tool.get_env_var_prefix()), tool.get_resolved_version().to_string(), ) .env( format!("{}_BIN", tool.get_env_var_prefix()), - tool.get_bin_path()?.to_string_lossy().to_string(), + exe_path.to_string_lossy().to_string(), ) .spawn() .into_diagnostic()? @@ -169,15 +160,13 @@ pub async fn run(args: ArgsRef) -> SystemResult { .await .into_diagnostic()?; - if !status.success() { - exit(status.code().unwrap_or(1)); - } - // Run after hook - tool.run_hook("post_run", || RunHook { - context: tool.create_context(), - passthrough_args: args.passthrough.clone(), - })?; + if status.success() { + tool.run_hook("post_run", || RunHook { + context: tool.create_context(), + passthrough_args: args.passthrough.clone(), + })?; + } // Update the last used timestamp in a separate task, // as to not interrupt this task incase something fails! @@ -187,4 +176,8 @@ pub async fn run(args: ArgsRef) -> SystemResult { let _ = tool.manifest.save(); }); } + + if !status.success() { + exit(status.code().unwrap_or(1)); + } } diff --git a/crates/cli/src/helpers.rs b/crates/cli/src/helpers.rs index 62b9ee16b..4bf8957da 100644 --- a/crates/cli/src/helpers.rs +++ b/crates/cli/src/helpers.rs @@ -3,7 +3,7 @@ use indicatif::{ProgressBar, ProgressStyle}; use miette::IntoDiagnostic; use proto_core::{ get_temp_dir, load_tool_from_locator, Id, ProtoEnvironment, ProtoError, Tool, ToolsConfig, - UserConfig, + UserConfig, SCHEMA_PLUGIN_KEY, }; use starbase_utils::fs; use std::cmp; @@ -136,6 +136,11 @@ impl ToolsLoader { continue; } + // This shouldn't be treated as a "normal plugin" + if id == SCHEMA_PLUGIN_KEY { + continue; + } + let id = id.to_owned(); let locator = locator.to_owned(); let proto = Arc::clone(&self.proto); diff --git a/crates/cli/tests/bin_test.rs b/crates/cli/tests/bin_test.rs index 19cfe7ed0..f04db6871 100644 --- a/crates/cli/tests/bin_test.rs +++ b/crates/cli/tests/bin_test.rs @@ -14,7 +14,7 @@ mod bin { let assert = cmd.arg("bin").arg("npm").arg("9.0.0").assert(); assert.stderr(predicate::str::contains( - "Unable to find an executable binary for npm", + "Unable to find an executable for npm", )); } @@ -33,14 +33,44 @@ mod bin { let assert = cmd.arg("bin").arg("npm").arg("9.0.0").assert(); if cfg!(windows) { - assert.stdout(predicate::str::contains( - "tools\\npm\\9.0.0\\bin/npm-cli.js", - )); + assert.stdout(predicate::str::contains("tools\\npm\\9.0.0\\bin/npm.cmd")); } else { - assert.stdout(predicate::str::contains("tools/npm/9.0.0/bin/npm-cli.js")); + assert.stdout(predicate::str::contains("tools/npm/9.0.0/bin/npm")); } + } + + #[test] + fn returns_bin_path() { + let sandbox = create_empty_sandbox(); + + let mut cmd = create_proto_command(sandbox.path()); + cmd.arg("install") + .arg("npm") + .arg("9.0.0") + .assert() + .success(); + + let mut cmd = create_proto_command(sandbox.path()); + let assert = cmd.arg("bin").arg("npm").arg("9.0.0").arg("--bin").assert(); + + if cfg!(windows) { + assert.stdout(predicate::str::contains("bin\\npm.cmd")); + } else { + assert.stdout(predicate::str::contains("bin/npm")); + } + } + + #[test] + fn returns_shim_path() { + let sandbox = create_empty_sandbox(); + + let mut cmd = create_proto_command(sandbox.path()); + cmd.arg("install") + .arg("npm") + .arg("9.0.0") + .assert() + .success(); - // With shims let mut cmd = create_proto_command(sandbox.path()); let assert = cmd .arg("bin") @@ -50,9 +80,9 @@ mod bin { .assert(); if cfg!(windows) { - assert.stdout(predicate::str::contains("tools\\npm\\9.0.0\\shims\\npm")); + assert.stdout(predicate::str::contains("shims\\npm.cmd")); } else { - assert.stdout(predicate::str::contains("tools/npm/9.0.0/shims/npm")); + assert.stdout(predicate::str::contains("shims/npm")); } } } diff --git a/crates/cli/tests/clean_test.rs b/crates/cli/tests/clean_test.rs index edef889b2..6f5cb2f81 100644 --- a/crates/cli/tests/clean_test.rs +++ b/crates/cli/tests/clean_test.rs @@ -47,8 +47,8 @@ mod clean { #[test] fn purges_tool_shims() { let sandbox = create_empty_sandbox(); - sandbox.create_file("shims/node", ""); - sandbox.create_file("shims/node.cmd", ""); + sandbox.create_file("shims/npm", ""); + sandbox.create_file("shims/npm.cmd", ""); sandbox.create_file("shims/npx", ""); sandbox.create_file("shims/npx.cmd", ""); @@ -56,15 +56,15 @@ mod clean { cmd.arg("clean") .arg("--yes") .arg("--purge") - .arg("node") + .arg("npm") .assert() .success(); if cfg!(windows) { - assert!(!sandbox.path().join("shims/node.cmd").exists()); + assert!(!sandbox.path().join("shims/npm.cmd").exists()); assert!(!sandbox.path().join("shims/npx.cmd").exists()); } else { - assert!(!sandbox.path().join("shims/node").exists()); + assert!(!sandbox.path().join("shims/npm").exists()); assert!(!sandbox.path().join("shims/npx").exists()); } } diff --git a/crates/cli/tests/plugins_test.rs b/crates/cli/tests/plugins_test.rs index 6ee2745d4..6f0659dce 100644 --- a/crates/cli/tests/plugins_test.rs +++ b/crates/cli/tests/plugins_test.rs @@ -32,10 +32,10 @@ where let base_dir = proto.tools_dir.join("moon/1.0.0"); if cfg!(windows) { - assert_eq!(tool.get_bin_path().unwrap(), &base_dir.join("moon.exe")); + assert_eq!(tool.get_exe_path().unwrap(), &base_dir.join("moon.exe")); assert!(proto.shims_dir.join("moon.cmd").exists()); } else { - assert_eq!(tool.get_bin_path().unwrap(), &base_dir.join("moon")); + assert_eq!(tool.get_exe_path().unwrap(), &base_dir.join("moon")); assert!(proto.shims_dir.join("moon").exists()); } } diff --git a/crates/cli/tests/use_test.rs b/crates/cli/tests/use_test.rs index 017a70c08..ed6d3711a 100644 --- a/crates/cli/tests/use_test.rs +++ b/crates/cli/tests/use_test.rs @@ -42,9 +42,7 @@ mod install_all { assert!(!node_path.exists()); let mut cmd = create_proto_command(temp.path()); - let assert = cmd.arg("use").assert().success(); - - println!("{}", assert); + cmd.arg("use").assert().success(); assert!(node_path.exists()); } diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index f86045903..082e338d7 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proto_core" -version = "0.21.2" +version = "0.22.1" edition = "2021" license = "MIT" description = "Core proto APIs." @@ -8,8 +8,8 @@ homepage = "https://moonrepo.dev/proto" repository = "https://github.com/moonrepo/proto" [dependencies] -proto_pdk_api = { version = "0.9.0", path = "../pdk-api" } -version_spec = { version = "0.1.3", path = "../version-spec" } +proto_pdk_api = { version = "0.10.1", path = "../pdk-api" } +version_spec = { version = "0.1.4", path = "../version-spec" } warpgate = { version = "0.5.13", path = "../warpgate" } cached = { workspace = true } extism = { workspace = true } diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 4d7cd32a4..8eeb5f13c 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -56,9 +56,9 @@ pub enum ProtoError { #[error("Unable to determine your home directory.")] MissingHomeDir, - #[diagnostic(code(proto::execute::missing_bin))] - #[error("Unable to find an executable binary for {tool}, expected file {} does not exist.", .bin.style(Style::Path))] - MissingToolBin { tool: String, bin: PathBuf }, + #[diagnostic(code(proto::execute::missing_file))] + #[error("Unable to find an executable for {tool}, expected file {} does not exist.", .path.style(Style::Path))] + MissingToolExecutable { tool: String, path: PathBuf }, #[diagnostic(code(proto::tool::required))] #[error( diff --git a/crates/core/src/events.rs b/crates/core/src/events.rs index ce93ff21e..cf5c01914 100644 --- a/crates/core/src/events.rs +++ b/crates/core/src/events.rs @@ -39,6 +39,10 @@ impl_event!(UninstalledGlobalEvent, { pub dependency: String, }); +impl_event!(CreatedBinariesEvent, { + pub bins: Vec, +}); + impl_event!(CreatedShimsEvent, { pub global: Vec, pub local: Vec, diff --git a/crates/core/src/helpers.rs b/crates/core/src/helpers.rs index 3e486df3f..a0cc60855 100644 --- a/crates/core/src/helpers.rs +++ b/crates/core/src/helpers.rs @@ -20,6 +20,10 @@ use tracing::trace; pub static ENV_VAR: Lazy = Lazy::new(|| Regex::new(r"\$([A-Z0-9_]+)").unwrap()); pub fn get_proto_home() -> miette::Result { + // if cfg!(debug_assertions) { + // return Ok(get_home_dir()?.join(".proto-debug")); + // } + if let Ok(root) = env::var("PROTO_HOME") { return Ok(root.into()); } diff --git a/crates/core/src/shimmer.rs b/crates/core/src/shimmer.rs index 19649cbcd..b8a123901 100644 --- a/crates/core/src/shimmer.rs +++ b/crates/core/src/shimmer.rs @@ -1,5 +1,4 @@ use crate::error::ProtoError; -use crate::ProtoEnvironment; use serde::Serialize; use serde_json::Value; use starbase_utils::fs; @@ -9,23 +8,16 @@ use tinytemplate::error::Error as TemplateError; use tinytemplate::TinyTemplate; use tracing::debug; -pub const SHIM_VERSION: u8 = 7; +pub const SHIM_VERSION: u8 = 8; #[derive(Debug, Default, Serialize)] pub struct ShimContext<'tool> { - /// Name of the shim file. - pub shim_file: &'tool str, - // BINARY INFO /// Name of the binary to execute. Will be used for `proto run` in the shim. pub bin: &'tool str, - /// Alternate path to the binary to execute. - /// For global (optional), passes `--bin`. For local (required), executes the file. - pub bin_path: Option<&'tool Path>, - - /// Name of a parent binary required to execute the current binary. - pub parent_bin: Option<&'tool str>, + /// Alternate path to the binary to execute. Uses `proto run --bin`. + pub bin_path: Option, /// Args to prepend to user-provided args. pub before_args: Option<&'tool str>, @@ -44,6 +36,25 @@ pub struct ShimContext<'tool> { pub tool_version: Option, } +impl<'tool> ShimContext<'tool> { + pub fn create_shim(&self, shim_path: &Path, find_only: bool) -> miette::Result<()> { + if find_only && shim_path.exists() { + return Ok(()); + } + + debug!( + tool = &self.tool_id, + shim = ?shim_path, + "Creating global shim" + ); + + fs::write_file(shim_path, build_shim_file(self, shim_path, true)?)?; + fs::update_perms(shim_path, None)?; + + Ok(()) + } +} + impl<'tool> AsRef> for ShimContext<'tool> { fn as_ref(&self) -> &ShimContext<'tool> { self @@ -125,55 +136,3 @@ pub fn get_shim_file_name(name: &str, global: bool) -> String { pub fn get_shim_file_name(name: &str, _global: bool) -> String { name.to_owned() } - -fn create_shim( - context: &ShimContext, - shim_path: PathBuf, - global: bool, - find_only: bool, -) -> miette::Result { - if find_only && shim_path.exists() { - return Ok(shim_path); - } - - fs::write_file(&shim_path, build_shim_file(context, &shim_path, global)?)?; - fs::update_perms(&shim_path, None)?; - - Ok(shim_path) -} - -pub fn create_global_shim<'tool, C: AsRef>>( - proto: &ProtoEnvironment, - context: C, - find_only: bool, -) -> miette::Result { - let context = context.as_ref(); - let shim_path = proto - .shims_dir - .join(get_shim_file_name(context.shim_file, true)); - - if !find_only { - debug!(tool = &context.tool_id, shim = ?shim_path, "Creating global shim"); - } - - create_shim(context, shim_path, true, find_only) -} - -pub fn create_local_shim<'tool, C: AsRef>>( - context: C, - find_only: bool, -) -> miette::Result { - let context = context.as_ref(); - let shim_path = context - .tool_dir - .as_ref() - .expect("Missing tool directory for shims.") - .join("shims") - .join(get_shim_file_name(context.shim_file, false)); - - if !find_only { - debug!(tool = &context.tool_id, shim = ?shim_path, "Creating local shim"); - } - - create_shim(context, shim_path, false, find_only) -} diff --git a/crates/core/src/tool.rs b/crates/core/src/tool.rs index 22ee2aa31..119ed0356 100644 --- a/crates/core/src/tool.rs +++ b/crates/core/src/tool.rs @@ -6,9 +6,7 @@ use crate::helpers::{ }; use crate::host_funcs::{create_host_functions, HostData}; use crate::proto::ProtoEnvironment; -use crate::shimmer::{ - create_global_shim, create_local_shim, get_shim_file_name, ShimContext, SHIM_VERSION, -}; +use crate::shimmer::{get_shim_file_name, ShimContext, SHIM_VERSION}; use crate::tool_manifest::ToolManifest; use crate::version_resolver::VersionResolver; use extism::{manifest::Wasm, Manifest as PluginManifest}; @@ -30,6 +28,14 @@ use std::time::{Duration, SystemTime}; use tracing::{debug, trace}; use warpgate::{download_from_url_to_file, Id, PluginContainer, PluginLocator, VirtualPath}; +#[derive(Debug)] +pub struct ExecutableLocation { + pub config: ExecutableConfig, + pub name: String, + pub path: PathBuf, + pub primary: bool, +} + pub struct Tool { pub id: Id, pub manifest: ToolManifest, @@ -40,6 +46,7 @@ pub struct Tool { pub version: Option, // Events + pub on_created_bins: Emitter, pub on_created_shims: Emitter, pub on_installing: Emitter, pub on_installed: Emitter, @@ -50,7 +57,7 @@ pub struct Tool { pub on_uninstalled_global: Emitter, cache: bool, - bin_path: Option, + exe_path: Option, globals_dir: Option, globals_prefix: Option, } @@ -87,15 +94,16 @@ impl Tool { }; if let Ok(level) = env::var("PROTO_WASM_LOG") { - extism::set_log_file( - proto.cwd.join(format!("{}-debug.log", id)), - std::str::FromStr::from_str(&level).ok(), - ); + let log_file = proto.cwd.join(format!("{}-debug.log", id)); + + trace!(file = ?log_file, "Created WASM log file"); + + extism::set_log_file(log_file, std::str::FromStr::from_str(&level).ok()); } let mut tool = Tool { - bin_path: None, cache: true, + exe_path: None, globals_dir: None, globals_prefix: None, id: id.to_owned(), @@ -111,6 +119,7 @@ impl Tool { version: None, // Events + on_created_bins: Emitter::new(), on_created_shims: Emitter::new(), on_installing: Emitter::new(), on_installed: Emitter::new(), @@ -150,53 +159,34 @@ impl Tool { Ok(manifest) } + fn call_locate_executables(&self) -> miette::Result { + self.plugin.cache_func_with( + "locate_executables", + LocateExecutablesInput { + context: self.create_context(), + }, + ) + } + /// Disable internal caching when applicable. pub fn disable_caching(&mut self) { self.cache = false; } - /// Return the name of the executable binary, using proto's tool ID. - pub fn get_bin_name(&self) -> String { - if cfg!(windows) { - format!("{}.exe", self.id) - } else { - self.id.to_string() - } - } - - /// Return an absolute path to the executable binary for the tool. - pub fn get_bin_path(&self) -> miette::Result<&Path> { - self.bin_path.as_deref().ok_or_else(|| { - ProtoError::UnknownTool { - id: self.id.clone(), - } - .into() - }) - } - /// Return the prefix for environment variable names. pub fn get_env_var_prefix(&self) -> String { format!("PROTO_{}", self.id.to_uppercase().replace('-', "_")) } - /// Return an absolute path to the globals directory in which packages are installed to. - pub fn get_globals_bin_dir(&self) -> Option<&Path> { - 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.as_str()) + self.metadata + .inventory + .override_dir + .as_ref() + .map(|dir| dir.to_owned()) + .unwrap_or_else(|| self.proto.tools_dir.join(self.id.as_str())) } /// Return a human readable name for the tool. @@ -209,23 +199,12 @@ impl Tool { self.version.clone().unwrap_or_default() } - /// Return a path to a local shim file if it exists. - pub fn get_shim_path(&self) -> Option { - let local = self - .get_tool_dir() - .join("shims") - .join(get_shim_file_name(&self.id, false)); - - if local.exists() { - return Some(local); - } - - None - } - /// Return an absolute path to a temp directory solely for this tool. pub fn get_temp_dir(&self) -> PathBuf { - self.proto.temp_dir.join(self.id.as_str()) + self.proto + .temp_dir + .join(self.id.as_str()) + .join(self.get_resolved_version().to_string()) } /// Return an absolute path to the tool's install directory for the currently resolved version. @@ -249,10 +228,12 @@ impl Tool { self.metadata.inventory.disable_progress_bars } + /// Convert a virtual path to a real path. pub fn from_virtual_path(&self, path: &Path) -> PathBuf { self.plugin.from_virtual_path(path) } + /// Convert a real path to a virtual path. pub fn to_virtual_path(&self, path: &Path) -> VirtualPath { // This is a temporary hack. Only newer plugins support the `VirtualPath` // type, so we need to check if the plugin has a version or not, which @@ -551,11 +532,7 @@ impl Tool { Ok(()) } -} -// VERSION DETECTION - -impl Tool { /// Attempt to detect an applicable version from the provided directory. pub async fn detect_version_from( &self, @@ -620,6 +597,21 @@ impl Tool { // INSTALLATION impl Tool { + /// Return true if the tool has been installed. This is less accurate than `is_setup`, + /// as it only checks for the existence of the inventory directory. + pub fn is_installed(&self) -> bool { + let dir = self.get_tool_dir(); + + self.version + .as_ref() + // Canary can be overwritten so treat as not-installed + .is_some_and(|v| { + !v.is_latest() && !v.is_canary() && self.manifest.installed_versions.contains(v) + }) + && dir.exists() + && !fs::is_dir_locked(dir) + } + /// Verify the downloaded file using the checksum strategy for the tool. /// Common strategies are SHA256 and MD5. pub async fn verify_checksum( @@ -723,9 +715,7 @@ impl Tool { .into()); } - let temp_dir = self - .get_temp_dir() - .join(self.get_resolved_version().to_string()); + let temp_dir = self.get_temp_dir(); let options: BuildInstructionsOutput = self.plugin.cache_func_with( "build_instructions", @@ -836,9 +826,7 @@ impl Tool { }, )?; - let temp_dir = self - .get_temp_dir() - .join(self.get_resolved_version().to_string()); + let temp_dir = self.get_temp_dir(); // Download the prebuilt let download_url = options.download_url; @@ -1135,57 +1123,167 @@ impl Tool { Ok(result.uninstalled) } +} - /// Find the absolute file path to the tool's binary that will be executed. - pub async fn locate_bins(&mut self) -> miette::Result<()> { - if self.bin_path.is_some() { - return Ok(()); +// BINARIES, SHIMS + +impl Tool { + /// Create the context object required for creating shim files. + pub fn create_shim_context(&self) -> ShimContext { + ShimContext { + 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() } + } - let mut options = LocateBinsOutput::default(); - let tool_dir = self.get_tool_dir(); + /// Create all executables for the current tool. + /// - Locate the primary binary to execute. + /// - Generate shims to `~/.proto/shims`. + /// - Symlink bins to `~/.proto/bin`. + pub async fn create_executables( + &mut self, + force_shims: bool, + force_bins: bool, + ) -> miette::Result<()> { + self.locate_executable().await?; + self.generate_shims(force_shims).await?; + self.symlink_bins(force_bins).await?; - debug!(tool = self.id.as_str(), "Locating binaries for tool"); + Ok(()) + } - if self.plugin.has_func("locate_bins") { - options = self.plugin.cache_func_with( - "locate_bins", - LocateBinsInput { - context: self.create_context(), - }, - )?; + /// Return an absolute path to the executable file for the tool. + pub fn get_exe_path(&self) -> miette::Result<&Path> { + self.exe_path.as_deref().ok_or_else(|| { + ProtoError::UnknownTool { + id: self.id.clone(), + } + .into() + }) + } + + /// Return an absolute path to the globals directory in which packages are installed to. + pub fn get_globals_bin_dir(&self) -> Option<&Path> { + 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 a list of all binaries that get created in `~/.proto/bin`. + /// The list will contain the executable config, and an absolute path + /// to the binaries final location. + pub fn get_bin_locations(&self) -> miette::Result> { + let options = self.call_locate_executables()?; + let mut locations = vec![]; + + let mut add = |name: &str, config: ExecutableConfig, primary: bool| { + if !config.no_bin { + if let Some(exe_path) = &config.exe_path { + locations.push(ExecutableLocation { + path: self.proto.bin_dir.join(match exe_path.extension() { + Some(ext) => format!("{name}.{}", ext.to_string_lossy()), + None => name.to_owned(), + }), + name: name.to_owned(), + config, + primary, + }); + } + } + }; + + if let Some(primary) = options.primary { + add(&self.id, primary, true); } - let bin_path = if let Some(bin) = options.bin_path { - let bin = self.from_virtual_path(&bin); + for (name, secondary) in options.secondary { + add(&name, secondary, false); + } - if bin.is_absolute() { - bin - } else { - tool_dir.join(bin) + Ok(locations) + } + + /// Return location information for the primary executable within the tool directory. + pub fn get_exe_location(&self) -> miette::Result> { + let options = self.call_locate_executables()?; + + if let Some(primary) = options.primary { + if let Some(exe_path) = &primary.exe_path { + return Ok(Some(ExecutableLocation { + path: self.get_tool_dir().join(exe_path), + name: self.id.to_string(), + config: primary, + primary: true, + })); + } + } + + Ok(None) + } + + /// Return a list of all shims that get created in `~/.proto/shims`. + /// The list will contain the executable config, and an absolute path + /// to the shims final location. + pub fn get_shim_locations(&self) -> miette::Result> { + let options = self.call_locate_executables()?; + let mut locations = vec![]; + + let mut add = |name: &str, config: ExecutableConfig, primary: bool| { + if !config.no_shim { + locations.push(ExecutableLocation { + path: self.proto.shims_dir.join(get_shim_file_name(name, true)), + name: name.to_owned(), + config, + primary, + }); } + }; + + if let Some(primary) = options.primary { + add(&self.id, primary, true); + } + + for (name, secondary) in options.secondary { + add(&name, secondary, false); + } + + Ok(locations) + } + + /// Locate the primary executable from the tool directory. + pub async fn locate_executable(&mut self) -> miette::Result<()> { + debug!(tool = self.id.as_str(), "Locating executable for tool"); + + let exe_path = if let Some(location) = self.get_exe_location()? { + location.path } else { - tool_dir.join(self.id.as_str()) + self.get_tool_dir().join(self.id.as_str()) }; - debug!(tool = self.id.as_str(), bin_path = ?bin_path, "Found a binary"); + if exe_path.exists() { + debug!(tool = self.id.as_str(), exe_path = ?exe_path, "Found an executable"); - if bin_path.exists() { - self.bin_path = Some(bin_path); + self.exe_path = Some(exe_path); return Ok(()); } - Err(ProtoError::MissingToolBin { + Err(ProtoError::MissingToolExecutable { tool: self.get_name().to_owned(), - bin: bin_path, + path: exe_path, } .into()) } - /// Find the directory global packages are installed to. + /// Locate the directory that global packages are installed to. pub async fn locate_globals_dir(&mut self) -> miette::Result<()> { - if !self.plugin.has_func("locate_bins") || self.globals_dir.is_some() { + if !self.plugin.has_func("locate_executables") || self.globals_dir.is_some() { return Ok(()); } @@ -1195,12 +1293,7 @@ impl Tool { ); let install_dir = self.get_tool_dir(); - let options: LocateBinsOutput = self.plugin.cache_func_with( - "locate_bins", - LocateBinsInput { - context: self.create_context(), - }, - )?; + let options = self.call_locate_executables()?; self.globals_prefix = options.globals_prefix; @@ -1236,7 +1329,7 @@ impl Tool { PathBuf::from(dir) }; - if dir_path.exists() || (index == lookup_count && options.fallback_last_globals_dir) { + if dir_path.exists() || index == lookup_count { debug!(tool = self.id.as_str(), bin_dir = ?dir_path, "Found a globals directory"); self.globals_dir = Some(dir_path); @@ -1246,93 +1339,107 @@ impl Tool { Ok(()) } -} -// SHIMMER + /// Create shim files for the current tool if they are missing or out of date. + /// If find only is enabled, will only check if they exist, and not create. + pub async fn generate_shims(&mut self, force: bool) -> miette::Result<()> { + let shims = self.get_shim_locations()?; -impl Tool { - /// Create the context object required for creating shim files. - pub fn create_shim_context(&self) -> 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 shims.is_empty() { + return Ok(()); } - } - /// Create global and local shim files for the current tool. - /// If find only is enabled, will only check if they exist, and not create. - pub async fn create_shims(&self, find_only: bool) -> miette::Result<()> { - let mut primary_context = self.create_shim_context(); - let mut shim_event = CreatedShimsEvent { + let is_outdated = self.manifest.shim_version != SHIM_VERSION; + let force_create = force || is_outdated || env::var("CI").is_ok(); + let find_only = !force_create; + + if force_create { + debug!( + tool = self.id.as_str(), + shims_dir = ?self.proto.shims_dir, + "Creating shims as they either do not exist, or are outdated" + ); + + self.manifest.shim_version = SHIM_VERSION; + self.manifest.save()?; + } + + let mut event = CreatedShimsEvent { global: vec![], local: vec![], }; - // If not configured from the plugin, always create the primary global - if !self.plugin.has_func("create_shims") { - create_global_shim(&self.proto, primary_context, find_only)?; + for location in shims { + let mut context = self.create_shim_context(); + context.before_args = location.config.shim_before_args.as_deref(); + context.after_args = location.config.shim_after_args.as_deref(); - shim_event.global.push(self.id.to_string()); + if !location.primary { + context.bin_path = location.config.exe_path; + } - self.on_created_shims.emit(shim_event).await?; + context.create_shim(&location.path, find_only)?; - return Ok(()); + event.global.push(location.name); } - let shim_configs: CreateShimsOutput = self.plugin.cache_func_with( - "create_shims", - CreateShimsInput { - context: self.create_context(), - }, - )?; + self.on_created_shims.emit(event).await?; - // Create the primary global shim - if let Some(primary_config) = &shim_configs.primary { - primary_context.before_args = primary_config.before_args.as_deref(); - primary_context.after_args = primary_config.after_args.as_deref(); + Ok(()) + } + + /// Symlink all primary and secondary binaries for the current tool. + pub async fn symlink_bins(&mut self, force: bool) -> miette::Result<()> { + let bins = self.get_bin_locations()?; + + if bins.is_empty() { + return Ok(()); } - if !shim_configs.no_primary_global { - create_global_shim(&self.proto, primary_context, find_only)?; - shim_event.global.push(self.id.to_string()); + if force { + debug!( + tool = self.id.as_str(), + bins_dir = ?self.proto.bin_dir, + "Creating symlinks to the original tool executables" + ); } - // Create alternate/secondary global shims - for (name, config) in &shim_configs.global_shims { - let mut context = self.create_shim_context(); - context.shim_file = name; - context.bin_path = config.bin_path.as_deref(); - context.before_args = config.before_args.as_deref(); - context.after_args = config.after_args.as_deref(); + fs::create_dir_all(&self.proto.bin_dir)?; - create_global_shim(&self.proto, context, find_only)?; - shim_event.global.push(name.to_owned()); - } + let tool_dir = self.get_tool_dir(); + let mut event = CreatedBinariesEvent { bins: vec![] }; - // Create local shims - for (name, config) in &shim_configs.local_shims { - let bin_path = if let Some(path) = &config.bin_path { - self.get_tool_dir().join(path) - } else { - self.get_tool_dir().join(self.id.as_str()) - }; + for location in bins { + let input_path = tool_dir.join(location.config.exe_path.unwrap()); + let output_path = location.path; - let mut context = self.create_shim_context(); - context.shim_file = name; - context.bin_path = Some(&bin_path); - context.parent_bin = config.parent_bin.as_deref(); - context.before_args = config.before_args.as_deref(); - context.after_args = config.after_args.as_deref(); - - create_local_shim(context, find_only)?; - shim_event.local.push(name.to_owned()); + if output_path.exists() && !force { + continue; + } + + debug!( + tool = self.id.as_str(), + source = ?input_path, + target = ?output_path, + "Creating binary symlink" + ); + + fs::remove_file(&output_path)?; + + #[cfg(windows)] + { + std::os::windows::fs::symlink_file(input_path, &output_path).into_diagnostic()?; + } + + #[cfg(not(windows))] + { + std::os::unix::fs::symlink(input_path, &output_path).into_diagnostic()?; + } + + event.bins.push(location.name); } - self.on_created_shims.emit(shim_event).await?; + self.on_created_bins.emit(event).await?; Ok(()) } @@ -1341,21 +1448,6 @@ impl Tool { // OPERATIONS impl Tool { - /// Return true if the tool has been installed. This is less accurate than `is_setup`, - /// as it only checks for the existence of the inventory directory. - pub fn is_installed(&self) -> bool { - let dir = self.get_tool_dir(); - - self.version - .as_ref() - // Canary can be overwritten so treat as not-installed - .is_some_and(|v| { - !v.is_latest() && !v.is_canary() && self.manifest.installed_versions.contains(v) - }) - && dir.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, @@ -1378,10 +1470,8 @@ impl Tool { "Tool has already been installed, locating binaries and shims", ); - if self.bin_path.is_none() { - self.locate_bins().await?; - self.setup_shims(false).await?; - self.setup_bin_link(false)?; + if self.exe_path.is_none() { + self.create_executables(false, false).await?; } return Ok(true); @@ -1402,14 +1492,8 @@ impl Tool { self.resolve_version(initial_version).await?; if self.install(build_from_source).await? { + self.create_executables(true, false).await?; self.cleanup().await?; - self.locate_bins().await?; - - // Always force create shims to ensure changes are propagated - self.setup_shims(true).await?; - - // Only link on the first install or if the bin doesn't exist - self.setup_bin_link(false)?; // Add version to manifest self.manifest.insert_version( @@ -1426,84 +1510,34 @@ impl Tool { Ok(false) } - /// Create a symlink from the current tool to the proto bin directory. - pub fn setup_bin_link(&mut self, force: bool) -> miette::Result<()> { - let input_path = self.get_bin_path()?; - let output_path = self.proto.bin_dir.join(self.get_bin_name()); - - if output_path.exists() && !force { - return Ok(()); - } - - // Don't support other extensions on Windows at this time - #[cfg(windows)] - { - if input_path.extension().is_some_and(|e| e != "exe") { - return Ok(()); - } - } - - debug!( - tool = self.id.as_str(), - source = ?input_path, - target = ?output_path, - "Creating a symlink to the original tool executable" - ); - - fs::remove_file(&output_path)?; - fs::create_dir_all(&self.proto.bin_dir)?; - - #[cfg(windows)] - { - std::os::windows::fs::symlink_file(input_path, &output_path).into_diagnostic()?; - } - - #[cfg(not(windows))] - { - std::os::unix::fs::symlink(input_path, &output_path).into_diagnostic()?; - } - - Ok(()) - } - - /// Setup shims if they are missing or out of date. - pub async fn setup_shims(&mut self, force: bool) -> miette::Result<()> { - let is_outdated = self.manifest.shim_version != SHIM_VERSION; - let do_create = force || is_outdated || env::var("CI").is_ok(); - - if do_create { - debug!( - tool = self.id.as_str(), - "Creating shims as they either do not exist, or are outdated" - ); - - self.manifest.shim_version = SHIM_VERSION; - self.manifest.save()?; - } - - self.create_shims(!do_create).await?; - - Ok(()) - } - /// Teardown the tool by uninstalling the current version, removing the version /// from the manifest, and cleaning up temporary files. Return true if the teardown occurred. pub async fn teardown(&mut self) -> miette::Result { self.cleanup().await?; - if self.uninstall().await? { - // Only remove if uninstall was successful - self.manifest.remove_version(self.get_resolved_version())?; + if !self.uninstall().await? { + return Ok(false); + } - // If no more default version, delete the symlink - if self.manifest.default_version.is_none() { - fs::remove_file(&self.proto.bin_dir.join(self.get_bin_name()))?; + // Only remove if uninstall was successful + self.manifest.remove_version(self.get_resolved_version())?; + + // If no more default version, delete the symlink, + // otherwise the OS will throw errors for missing sources + if self.manifest.default_version.is_none() { + for bin in self.get_bin_locations()? { + fs::remove_file(bin.path)?; } + } - return Ok(true); + // If no more versions in general, delete all shims + if self.manifest.installed_versions.is_empty() { + for shim in self.get_shim_locations()? { + fs::remove_file(shim.path)?; + } } - Ok(false) + Ok(true) } /// Delete temporary files and downloads for the current version. @@ -1513,10 +1547,7 @@ impl Tool { "Cleaning up temporary files and downloads" ); - fs::remove( - self.get_temp_dir() - .join(self.get_resolved_version().to_string()), - )?; + fs::remove(self.get_temp_dir())?; Ok(()) } diff --git a/crates/core/src/tool_loader.rs b/crates/core/src/tool_loader.rs index 27077f09b..b66f96b19 100644 --- a/crates/core/src/tool_loader.rs +++ b/crates/core/src/tool_loader.rs @@ -1,7 +1,7 @@ use crate::error::ProtoError; use crate::proto::ProtoEnvironment; use crate::tool::Tool; -use crate::tools_config::ToolsConfig; +use crate::tools_config::{ToolsConfig, SCHEMA_PLUGIN_KEY}; use crate::user_config::UserConfig; use extism::{manifest::Wasm, Manifest}; use miette::IntoDiagnostic; @@ -55,6 +55,68 @@ pub fn inject_default_manifest_config( Ok(()) } +pub fn locate_tool( + id: &Id, + proto: &ProtoEnvironment, + user_config: &UserConfig, + current_dir_only: bool, +) -> miette::Result { + let mut locator = None; + + debug!( + tool = id.as_str(), + "Traversing upwards to find a configured plugin" + ); + + // Traverse upwards checking each `.prototools` for a plugin + if let Ok(working_dir) = env::current_dir() { + let mut current_dir: Option<&Path> = Some(&working_dir); + + while let Some(dir) = current_dir { + // Don't traverse past the home directory + if dir == proto.home { + break; + } + + let tools_config = ToolsConfig::load_from(dir)?; + + if let Some(maybe_locator) = tools_config.plugins.get(id) { + locator = Some(maybe_locator.to_owned()); + break; + } + + // We only want to check the current directory + if current_dir_only { + break; + } + + current_dir = dir.parent(); + } + } + + // Then check the user's config + if locator.is_none() { + if let Some(maybe_locator) = user_config.plugins.get(id) { + locator = Some(maybe_locator.to_owned()); + } + } + + // And finally the builtin plugins + if locator.is_none() { + let builtin_plugins = ToolsConfig::builtin_plugins(); + + if let Some(maybe_locator) = builtin_plugins.get(id) { + locator = Some(maybe_locator.to_owned()); + } + } + + let Some(locator) = locator else { + return Err(ProtoError::UnknownTool { id: id.to_owned() }.into()); + }; + + Ok(locator) +} + pub async fn load_tool_from_locator( id: impl AsRef, proto: impl AsRef, @@ -80,11 +142,14 @@ pub async fn load_tool_from_locator( { debug!(source = ?plugin_path, "Loading TOML plugin"); + let schema_id = Id::raw(SCHEMA_PLUGIN_KEY); + let schema_locator = locate_tool(&schema_id, proto, user_config, true)?; + let mut manifest = Tool::create_plugin_manifest( proto, Wasm::file( plugin_loader - .load_plugin_with_client(id, ToolsConfig::schema_plugin(), &http_client) + .load_plugin_with_client(schema_id, schema_locator, &http_client) .await?, ), )?; @@ -116,53 +181,7 @@ pub async fn load_tool_from_locator( pub async fn load_tool(id: &Id) -> miette::Result { let proto = ProtoEnvironment::new()?; let user_config = proto.load_user_config()?; - let mut locator = None; - - debug!( - tool = id.as_str(), - "Traversing upwards to find a configured plugin" - ); - - // Traverse upwards checking each `.prototools` for a plugin - if let Ok(working_dir) = env::current_dir() { - let mut current_dir: Option<&Path> = Some(&working_dir); - - while let Some(dir) = current_dir { - // Don't traverse past the home directory - if dir == proto.home { - break; - } - - let tools_config = ToolsConfig::load_from(dir)?; - - if let Some(maybe_locator) = tools_config.plugins.get(id) { - locator = Some(maybe_locator.to_owned()); - break; - } - - current_dir = dir.parent(); - } - } - - // Then check the user's config - if locator.is_none() { - if let Some(maybe_locator) = user_config.plugins.get(id) { - locator = Some(maybe_locator.to_owned()); - } - } - - // And finally the builtin plugins - if locator.is_none() { - let builtin_plugins = ToolsConfig::builtin_plugins(); - - if let Some(maybe_locator) = builtin_plugins.get(id) { - locator = Some(maybe_locator.to_owned()); - } - } - - let Some(locator) = locator else { - return Err(ProtoError::UnknownTool { id: id.to_owned() }.into()); - }; + let locator = locate_tool(id, &proto, &user_config, false)?; load_tool_from_locator(id, proto, locator, &user_config).await } diff --git a/crates/core/src/tools_config.rs b/crates/core/src/tools_config.rs index ce5858dbc..c57e8aef5 100644 --- a/crates/core/src/tools_config.rs +++ b/crates/core/src/tools_config.rs @@ -9,6 +9,7 @@ use version_spec::*; use warpgate::{Id, PluginLocator}; pub const TOOLS_CONFIG_NAME: &str = ".prototools"; +pub const SCHEMA_PLUGIN_KEY: &str = "internal-schema"; fn is_empty(map: &BTreeMap) -> bool { map.is_empty() @@ -34,12 +35,6 @@ impl ToolsConfig { config.plugins } - pub fn schema_plugin() -> PluginLocator { - PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/schema-plugin/releases/latest/download/schema_plugin.wasm".into() - } - } - #[tracing::instrument(skip_all)] pub fn load() -> miette::Result { let working_dir = env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); @@ -131,7 +126,7 @@ impl ToolsConfig { self.plugins.insert( Id::raw("bun"), PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/bun-plugin/releases/latest/download/bun_plugin.wasm".into() + url: "https://github.com/moonrepo/bun-plugin/releases/download/v0.5.0-alpha.0/bun_plugin.wasm".into() } ); } @@ -140,7 +135,7 @@ impl ToolsConfig { self.plugins.insert( Id::raw("deno"), PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/deno-plugin/releases/latest/download/deno_plugin.wasm".into() + url: "https://github.com/moonrepo/deno-plugin/releases/download/v0.5.0-alpha.0/deno_plugin.wasm".into() } ); } @@ -149,7 +144,7 @@ impl ToolsConfig { self.plugins.insert( Id::raw("go"), PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/go-plugin/releases/latest/download/go_plugin.wasm".into() + url: "https://github.com/moonrepo/go-plugin/releases/download/v0.5.0-alpha.0/go_plugin.wasm".into() } ); } @@ -158,7 +153,7 @@ impl ToolsConfig { self.plugins.insert( Id::raw("node"), PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/node-plugin/releases/latest/download/node_plugin.wasm".into() + url: "https://github.com/moonrepo/node-plugin/releases/download/v0.5.0-alpha.0/node_plugin.wasm".into() } ); } @@ -168,7 +163,7 @@ impl ToolsConfig { self.plugins.insert( Id::raw(depman), PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/node-plugin/releases/latest/download/node_depman_plugin.wasm".into() + url: "https://github.com/moonrepo/node-plugin/releases/download/v0.5.0-alpha.0/node_depman_plugin.wasm".into() } ); } @@ -178,7 +173,7 @@ impl ToolsConfig { self.plugins.insert( Id::raw("python"), PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/python-plugin/releases/latest/download/python_plugin.wasm".into() + url: "https://github.com/moonrepo/python-plugin/releases/download/v0.2.0-alpha.0/python_plugin.wasm".into() } ); } @@ -187,7 +182,16 @@ impl ToolsConfig { self.plugins.insert( Id::raw("rust"), PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/rust-plugin/releases/latest/download/rust_plugin.wasm".into() + url: "https://github.com/moonrepo/rust-plugin/releases/download/v0.4.0-alpha.0/rust_plugin.wasm".into() + } + ); + } + + if !self.plugins.contains_key(SCHEMA_PLUGIN_KEY) { + self.plugins.insert( + Id::raw(SCHEMA_PLUGIN_KEY), + PluginLocator::SourceUrl { + url: "https://github.com/moonrepo/schema-plugin/releases/download/v0.5.0-alpha.0/schema_plugin.wasm".into() } ); } diff --git a/crates/core/tests/shimmer_test.rs b/crates/core/tests/shimmer_test.rs index 33a1d23d6..7350b8bb2 100644 --- a/crates/core/tests/shimmer_test.rs +++ b/crates/core/tests/shimmer_test.rs @@ -1,14 +1,13 @@ // Windows generates different snapshots #[cfg(not(windows))] mod shimmer { - use proto_core::{create_global_shim, create_local_shim, ProtoEnvironment, ShimContext}; + use proto_core::{ProtoEnvironment, ShimContext}; use starbase_sandbox::{assert_snapshot, create_empty_sandbox}; use std::fs; use std::path::{Path, PathBuf}; fn create_context<'l>(id: &'l str, proto: &'l ProtoEnvironment) -> ShimContext<'l> { ShimContext { - shim_file: id, bin: id, tool_id: id, tool_dir: Some(proto.tools_dir.join(id).join("1.2.3")), @@ -30,10 +29,11 @@ mod shimmer { let proto = ProtoEnvironment::new_testing(sandbox.path()); let context = create_context("primary", &proto); - let shim = create_global_shim(&proto, context, true).unwrap(); + let shim_path = proto.shims_dir.join("primary"); - assert_eq!(shim, proto.shims_dir.join("primary")); - assert_eq!(read_shim(&shim, sandbox.path()), "test"); + context.create_shim(&shim_path, true).unwrap(); + + assert_eq!(read_shim(&shim_path, sandbox.path()), "test"); } #[test] @@ -41,10 +41,11 @@ mod shimmer { let sandbox = create_empty_sandbox(); let proto = ProtoEnvironment::new_testing(sandbox.path()); let context = create_context("primary", &proto); - let shim = create_global_shim(&proto, &context, false).unwrap(); - assert_eq!(shim, proto.shims_dir.join("primary")); - assert_snapshot!(read_shim(&shim, sandbox.path())); + let shim_path = proto.shims_dir.join("primary"); + context.create_shim(&shim_path, false).unwrap(); + + assert_snapshot!(read_shim(&shim_path, sandbox.path())); } #[test] @@ -56,10 +57,10 @@ mod shimmer { context.before_args = Some("--a -b"); context.after_args = Some("./file"); - let shim = create_global_shim(&proto, &context, false).unwrap(); + let shim_path = proto.shims_dir.join("primary"); + context.create_shim(&shim_path, false).unwrap(); - assert_eq!(shim, proto.shims_dir.join("primary")); - assert_snapshot!(read_shim(&shim, sandbox.path())); + assert_snapshot!(read_shim(&shim_path, sandbox.path())); } #[test] @@ -69,13 +70,12 @@ mod shimmer { let bin_path = PathBuf::from("other/bin/path"); let mut context = create_context("primary", &proto); - context.shim_file = "secondary"; - context.bin_path = Some(&bin_path); + context.bin_path = Some(bin_path); - let shim = create_global_shim(&proto, &context, false).unwrap(); + let shim_path = proto.shims_dir.join("secondary"); + context.create_shim(&shim_path, false).unwrap(); - assert_eq!(shim, proto.shims_dir.join("secondary")); - assert_snapshot!(read_shim(&shim, sandbox.path())); + assert_snapshot!(read_shim(&shim_path, sandbox.path())); } #[test] @@ -85,112 +85,13 @@ mod shimmer { let bin_path = PathBuf::from("other/bin/path"); let mut context = create_context("primary", &proto); - context.shim_file = "secondary"; - context.bin_path = Some(&bin_path); - context.before_args = Some("--a -b"); - context.after_args = Some("./file"); - - let shim = create_global_shim(&proto, &context, false).unwrap(); - - assert_eq!(shim, proto.shims_dir.join("secondary")); - assert_snapshot!(read_shim(&shim, sandbox.path())); - } - - #[test] - fn doesnt_update_local_if_find_only() { - let sandbox = create_empty_sandbox(); - sandbox.create_file(".proto/tools/tool/1.2.3/shims/tool", "test"); - - let proto = ProtoEnvironment::new_testing(sandbox.path()); - let bin_path = PathBuf::from("bin/tool"); - let mut context = create_context("tool", &proto); - context.bin_path = Some(&bin_path); - - let shim = create_local_shim(&context, true).unwrap(); - - assert_eq!(shim, context.tool_dir.unwrap().join("shims/tool")); - assert_eq!(read_shim(&shim, sandbox.path()), "test"); - } - - #[test] - fn local() { - let sandbox = create_empty_sandbox(); - let proto = ProtoEnvironment::new_testing(sandbox.path()); - - let bin_path = PathBuf::from("bin/tool"); - let mut context = create_context("tool", &proto); - context.bin_path = Some(&bin_path); - - let shim = create_local_shim(&context, false).unwrap(); - - assert_eq!(shim, context.tool_dir.unwrap().join("shims/tool")); - assert_snapshot!(read_shim(&shim, sandbox.path())); - } - - #[test] - fn local_with_args() { - let sandbox = create_empty_sandbox(); - let proto = ProtoEnvironment::new_testing(sandbox.path()); - - let bin_path = PathBuf::from("bin/tool"); - let mut context = create_context("tool", &proto); - context.bin_path = Some(&bin_path); + context.bin_path = Some(bin_path); context.before_args = Some("--a -b"); context.after_args = Some("./file"); - let shim = create_local_shim(&context, false).unwrap(); - - assert_eq!(shim, context.tool_dir.unwrap().join("shims/tool")); - assert_snapshot!(read_shim(&shim, sandbox.path())); - } - - #[test] - fn local_with_parent() { - let sandbox = create_empty_sandbox(); - let proto = ProtoEnvironment::new_testing(sandbox.path()); - - let bin_path = PathBuf::from("bin/tool"); - let mut context = create_context("tool", &proto); - context.bin_path = Some(&bin_path); - context.parent_bin = Some("node"); - - let shim = create_local_shim(&context, false).unwrap(); - - assert_eq!(shim, context.tool_dir.unwrap().join("shims/tool")); - assert_snapshot!(read_shim(&shim, sandbox.path())); - } - - #[test] - fn local_with_parent_and_args() { - let sandbox = create_empty_sandbox(); - let proto = ProtoEnvironment::new_testing(sandbox.path()); - - let bin_path = PathBuf::from("bin/tool"); - let mut context = create_context("tool", &proto); - context.bin_path = Some(&bin_path); - context.parent_bin = Some("node"); - context.before_args = Some("--a -b"); - context.after_args = Some("./file"); - - let shim = create_local_shim(&context, false).unwrap(); - - assert_eq!(shim, context.tool_dir.unwrap().join("shims/tool")); - assert_snapshot!(read_shim(&shim, sandbox.path())); - } - - #[test] - fn tool_id_formats() { - let sandbox = create_empty_sandbox(); - let proto = ProtoEnvironment::new_testing(sandbox.path()); - - let bin_path = PathBuf::from("bin/tool"); - let mut context = create_context("tool-name", &proto); - context.bin_path = Some(&bin_path); - context.parent_bin = Some("parentName"); - - let shim = create_local_shim(&context, false).unwrap(); + let shim_path = proto.shims_dir.join("secondary"); + context.create_shim(&shim_path, false).unwrap(); - assert_eq!(shim, context.tool_dir.unwrap().join("shims/tool-name")); - assert_snapshot!(read_shim(&shim, sandbox.path())); + assert_snapshot!(read_shim(&shim_path, sandbox.path())); } } diff --git a/crates/core/tests/snapshots/shimmer_test__shimmer__local.snap b/crates/core/tests/snapshots/shimmer_test__shimmer__local.snap deleted file mode 100644 index 58a43edcf..000000000 --- a/crates/core/tests/snapshots/shimmer_test__shimmer__local.snap +++ /dev/null @@ -1,22 +0,0 @@ ---- -source: crates/core/tests/shimmer_test.rs -expression: "read_shim(&shim, sandbox.path())" ---- -#!/usr/bin/env bash -set -e -[ -n "$PROTO_DEBUG" ] && set -x - - - -export PROTO_TOOL_DIR="/.proto/tools/tool/1.2.3" - - - -export PROTO_TOOL_VERSION="1.2.3" - - - - -exec "bin/tool" "$@" - - diff --git a/crates/core/tests/snapshots/shimmer_test__shimmer__local_with_args.snap b/crates/core/tests/snapshots/shimmer_test__shimmer__local_with_args.snap deleted file mode 100644 index ee05e11a6..000000000 --- a/crates/core/tests/snapshots/shimmer_test__shimmer__local_with_args.snap +++ /dev/null @@ -1,22 +0,0 @@ ---- -source: crates/core/tests/shimmer_test.rs -expression: "read_shim(&shim, sandbox.path())" ---- -#!/usr/bin/env bash -set -e -[ -n "$PROTO_DEBUG" ] && set -x - - - -export PROTO_TOOL_DIR="/.proto/tools/tool/1.2.3" - - - -export PROTO_TOOL_VERSION="1.2.3" - - - - -exec "bin/tool" --a -b "$@" ./file - - diff --git a/crates/core/tests/snapshots/shimmer_test__shimmer__local_with_parent.snap b/crates/core/tests/snapshots/shimmer_test__shimmer__local_with_parent.snap deleted file mode 100644 index 5da012d78..000000000 --- a/crates/core/tests/snapshots/shimmer_test__shimmer__local_with_parent.snap +++ /dev/null @@ -1,23 +0,0 @@ ---- -source: crates/core/tests/shimmer_test.rs -expression: "read_shim(&shim, sandbox.path())" ---- -#!/usr/bin/env bash -set -e -[ -n "$PROTO_DEBUG" ] && set -x - - - -export PROTO_TOOL_DIR="/.proto/tools/tool/1.2.3" - - - -export PROTO_TOOL_VERSION="1.2.3" - - - -parent="${PROTO_NODE_BIN:-node}" - -exec "$parent" "bin/tool" "$@" - - diff --git a/crates/core/tests/snapshots/shimmer_test__shimmer__local_with_parent_and_args.snap b/crates/core/tests/snapshots/shimmer_test__shimmer__local_with_parent_and_args.snap deleted file mode 100644 index ec22139cf..000000000 --- a/crates/core/tests/snapshots/shimmer_test__shimmer__local_with_parent_and_args.snap +++ /dev/null @@ -1,23 +0,0 @@ ---- -source: crates/core/tests/shimmer_test.rs -expression: "read_shim(&shim, sandbox.path())" ---- -#!/usr/bin/env bash -set -e -[ -n "$PROTO_DEBUG" ] && set -x - - - -export PROTO_TOOL_DIR="/.proto/tools/tool/1.2.3" - - - -export PROTO_TOOL_VERSION="1.2.3" - - - -parent="${PROTO_NODE_BIN:-node}" - -exec "$parent" "bin/tool" --a -b "$@" ./file - - diff --git a/crates/core/tests/snapshots/shimmer_test__shimmer__tool_id_formats.snap b/crates/core/tests/snapshots/shimmer_test__shimmer__tool_id_formats.snap deleted file mode 100644 index c3f6ff424..000000000 --- a/crates/core/tests/snapshots/shimmer_test__shimmer__tool_id_formats.snap +++ /dev/null @@ -1,23 +0,0 @@ ---- -source: crates/core/tests/shimmer_test.rs -expression: "read_shim(&shim, sandbox.path())" ---- -#!/usr/bin/env bash -set -e -[ -n "$PROTO_DEBUG" ] && set -x - - - -export PROTO_TOOL_NAME_DIR="/.proto/tools/tool-name/1.2.3" - - - -export PROTO_TOOL_NAME_VERSION="1.2.3" - - - -parent="${PROTO_PARENTNAME_BIN:-parentName}" - -exec "$parent" "bin/tool" "$@" - - diff --git a/crates/core/tests/user_config_test.rs b/crates/core/tests/user_config_test.rs index e34f2d965..4a1b25a85 100644 --- a/crates/core/tests/user_config_test.rs +++ b/crates/core/tests/user_config_test.rs @@ -140,7 +140,7 @@ foo = "source:../file.wasm" file: "../file.wasm".into(), path: sandbox.path().join("../file.wasm") } - ),]) + )]) ); } } diff --git a/crates/pdk-api/Cargo.toml b/crates/pdk-api/Cargo.toml index cec4bae37..4912dd5e3 100644 --- a/crates/pdk-api/Cargo.toml +++ b/crates/pdk-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proto_pdk_api" -version = "0.9.0" +version = "0.10.1" edition = "2021" license = "MIT" description = "Core APIs for creating proto WASM plugins." @@ -8,8 +8,8 @@ homepage = "https://moonrepo.dev/proto" repository = "https://github.com/moonrepo/proto" [dependencies] -system_env = { version = "0.1.2", path = "../system-env" } -version_spec = { version = "0.1.3", path = "../version-spec" } +system_env = { version = "0.1.3", path = "../system-env" } +version_spec = { version = "0.1.4", path = "../version-spec" } warpgate_api = { version = "0.1.4", path = "../warpgate-api" } anyhow = "1.0.75" semver = { workspace = true, features = ["serde"] } diff --git a/crates/pdk-api/src/api.rs b/crates/pdk-api/src/api.rs index d3029b9ee..02b93a6a9 100644 --- a/crates/pdk-api/src/api.rs +++ b/crates/pdk-api/src/api.rs @@ -1,7 +1,6 @@ use crate::host_funcs::ExecCommandOutput; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use std::ffi::OsStr; use std::path::PathBuf; use system_env::SystemDependency; use version_spec::{UnresolvedVersionSpec, VersionSpec}; @@ -9,6 +8,10 @@ use warpgate_api::VirtualPath; pub use semver::{Version, VersionReq}; +fn is_false(value: &bool) -> bool { + !(*value) +} + #[doc(hidden)] #[macro_export] macro_rules! json_struct { @@ -67,6 +70,7 @@ json_struct!( /// Controls aspects of the tool inventory. pub struct ToolInventoryMetadata { /// Disable progress bars when installing or uninstalling tools. + #[serde(skip_serializing_if = "is_false")] pub disable_progress_bars: bool, /// Override the tool inventory directory (where all versions are installed). @@ -107,7 +111,7 @@ json_struct!( } ); -// Detector +// VERSION DETECTION json_struct!( /// Output returned by the `detect_version_files` function. @@ -138,7 +142,7 @@ json_struct!( } ); -// Downloader, Installer, Verifier +// DOWNLOAD, BUILD, INSTALL, VERIFY json_struct!( /// Input passed to the `native_install` function. @@ -323,27 +327,61 @@ json_struct!( } ); -// Executor +// EXECUTABLES, BINARYS, GLOBALS json_struct!( - /// Input passed to the `locate_bins` function. - pub struct LocateBinsInput { + /// Input passed to the `locate_executables` function. + pub struct LocateExecutablesInput { /// Current tool context. pub context: ToolContext, } ); json_struct!( - /// Output returned by the `locate_bins` function. - pub struct LocateBinsOutput { - /// Relative path from the tool directory to the binary to execute. + /// Configuration for generated shim and symlinked binary files. + pub struct ExecutableConfig { + /// The binary file to execute, relative from the tool directory. + /// Does *not* support virtual paths. + /// + /// The following scenarios are powered by this field: + /// - Is the primary executable. + /// - For primary and secondary bins, the source file to be symlinked, + /// and the extension to use for the symlink file itself. + /// - For primary shim, this field is ignored. + /// - For secondary shims, the file to pass to `proto run --bin`. #[serde(skip_serializing_if = "Option::is_none")] - pub bin_path: Option, + pub exe_path: Option, + + /// Do not symlink a binary in `~/.proto/bin`. + #[serde(skip_serializing_if = "is_false")] + pub no_bin: bool, + + /// Do not generate a shim in `~/.proto/shims`. + #[serde(skip_serializing_if = "is_false")] + pub no_shim: bool, - /// When true, the last item in `globals_lookup_dirs` will be used, - /// regardless if it exists on the file system or not. - pub fallback_last_globals_dir: bool, + /// Custom args to prepend to user-provided args within the generated shim. + #[serde(skip_serializing_if = "Option::is_none")] + pub shim_before_args: Option, + /// Custom args to append to user-provided args within the generated shim. + #[serde(skip_serializing_if = "Option::is_none")] + pub shim_after_args: Option, + } +); + +impl ExecutableConfig { + pub fn new>(exe_path: T) -> Self { + Self { + exe_path: Some(PathBuf::from(exe_path.as_ref())), + ..ExecutableConfig::default() + } + } +} + +json_struct!( + /// Output returned by the `locate_executables` function. + pub struct LocateExecutablesOutput { /// List of directory paths to find the globals installation directory. /// Each path supports environment variable expansion. pub globals_lookup_dirs: Vec, @@ -352,6 +390,15 @@ json_struct!( /// when listing and filtering available globals. #[serde(skip_serializing_if = "Option::is_none")] pub globals_prefix: Option, + + /// Configures the primary/default executable to create. + /// If not provided, a primary shim and binary will *not* be created. + #[serde(skip_serializing_if = "Option::is_none")] + pub primary: Option, + + /// Configures secondary/additional executables to create. + /// The map key is the name of the shim/binary file. + pub secondary: HashMap, } ); @@ -439,7 +486,7 @@ impl UninstallGlobalOutput { } } -// Resolver +// VERSION RESOLVING json_struct!( /// Input passed to the `load_versions` function. @@ -469,11 +516,6 @@ json_struct!( ); impl LoadVersionsOutput { - #[deprecated = "Use from() instead."] - pub fn from_tags(tags: &[String]) -> anyhow::Result { - Self::from(tags.to_vec()) - } - /// Create the output from a list of strings that'll be parsed as versions. /// The latest version will be the highest version number. pub fn from(values: Vec) -> anyhow::Result { @@ -529,111 +571,7 @@ json_struct!( } ); -// Shimmer - -json_struct!( - /// Configuration for individual shim files. - pub struct ShimConfig { - /// Relative path from the tool directory to the binary to execute. - #[serde(skip_serializing_if = "Option::is_none")] - pub bin_path: Option, - - /// Name of a parent binary that's required for this shim to work. - /// For example, `npm` requires `node`. - #[serde(skip_serializing_if = "Option::is_none")] - pub parent_bin: Option, - - /// Custom args to prepend to user-provided args. - #[serde(skip_serializing_if = "Option::is_none")] - pub before_args: Option, - - /// Custom args to append to user-provided args. - #[serde(skip_serializing_if = "Option::is_none")] - pub after_args: Option, - } -); - -impl ShimConfig { - /// Create a global shim that executes the parent tool, - /// but uses the provided binary as the entry point. - pub fn global_with_alt_bin(bin_path: B) -> ShimConfig - where - B: AsRef, - { - ShimConfig { - bin_path: Some(bin_path.as_ref().into()), - ..ShimConfig::default() - } - } - - /// Create a global shim that executes the parent tool, - /// but prefixes the user-provided arguments with the - /// provided arguments (typically a sub-command). - pub fn global_with_sub_command(args: A) -> ShimConfig - where - A: AsRef, - { - ShimConfig { - before_args: Some(args.as_ref().to_owned()), - ..ShimConfig::default() - } - } - - /// Create a local shim that executes the provided binary. - pub fn local(bin_path: B) -> ShimConfig - where - B: AsRef, - { - ShimConfig { - bin_path: Some(bin_path.as_ref().into()), - ..ShimConfig::default() - } - } - - /// Create a local shim that executes the provided binary - /// through the context of the configured parent. - pub fn local_with_parent(bin_path: B, parent: P) -> ShimConfig - where - B: AsRef, - P: AsRef, - { - ShimConfig { - bin_path: Some(bin_path.as_ref().into()), - parent_bin: Some(parent.as_ref().to_owned()), - ..ShimConfig::default() - } - } -} - -json_struct!( - /// Input passed to the `create_shims` function. - pub struct CreateShimsInput { - /// Current tool context. - pub context: ToolContext, - } -); - -json_struct!( - /// Output returned by the `create_shims` function. - pub struct CreateShimsOutput { - /// Avoid creating the global shim. - pub no_primary_global: bool, - - /// Configures the default/primary global shim. - #[serde(skip_serializing_if = "Option::is_none")] - pub primary: Option, - - /// Additional global shims to create in the `~/.proto/shims` directory. - /// Maps a shim name to a relative binary path. - pub global_shims: HashMap, - - /// Local shims to create in the `~/.proto/tools///shims` directory. - /// Maps a shim name to its configuration. - pub local_shims: HashMap, - } -); - -// Misc +// MISCELLANEOUS json_struct!( /// Input passed to the `sync_manifest` function. diff --git a/crates/pdk-api/src/api_deprecated.rs b/crates/pdk-api/src/api_deprecated.rs new file mode 100644 index 000000000..8de105397 --- /dev/null +++ b/crates/pdk-api/src/api_deprecated.rs @@ -0,0 +1,145 @@ +#![allow(deprecated)] + +use crate::api::ToolContext; +use crate::json_struct; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::ffi::OsStr; +use std::path::PathBuf; + +json_struct!( + /// Input passed to the `locate_bins` function. + pub struct LocateBinsInput { + /// Current tool context. + pub context: ToolContext, + } +); + +json_struct!( + /// Output returned by the `locate_bins` function. + #[deprecated(since = "0.22.0", note = "Use `locate_executables` function instead.")] + pub struct LocateBinsOutput { + /// Relative path from the tool directory to the binary to execute. + #[serde(skip_serializing_if = "Option::is_none")] + pub bin_path: Option, + + /// When true, the last item in `globals_lookup_dirs` will be used, + /// regardless if it exists on the file system or not. + pub fallback_last_globals_dir: bool, + + /// 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, + } +); + +// Shimmer + +json_struct!( + /// Configuration for individual shim files. + pub struct ShimConfig { + /// The binary to execute. Can be a relative path from the tool directory, + /// or an absolute path + #[serde(skip_serializing_if = "Option::is_none")] + pub bin_path: Option, + + /// Name of a parent binary that's required for this shim to work. + /// For example, `npm` requires `node`. + #[serde(skip_serializing_if = "Option::is_none")] + pub parent_bin: Option, + + /// Custom args to prepend to user-provided args. + #[serde(skip_serializing_if = "Option::is_none")] + pub before_args: Option, + + /// Custom args to append to user-provided args. + #[serde(skip_serializing_if = "Option::is_none")] + pub after_args: Option, + } +); + +impl ShimConfig { + /// Create a global shim that executes the parent tool, + /// but uses the provided binary as the entry point. + pub fn global_with_alt_bin(bin_path: B) -> ShimConfig + where + B: AsRef, + { + ShimConfig { + bin_path: Some(bin_path.as_ref().into()), + ..ShimConfig::default() + } + } + + /// Create a global shim that executes the parent tool, + /// but prefixes the user-provided arguments with the + /// provided arguments (typically a sub-command). + pub fn global_with_sub_command(args: A) -> ShimConfig + where + A: AsRef, + { + ShimConfig { + before_args: Some(args.as_ref().to_owned()), + ..ShimConfig::default() + } + } + + /// Create a local shim that executes the provided binary. + pub fn local(bin_path: B) -> ShimConfig + where + B: AsRef, + { + ShimConfig { + bin_path: Some(bin_path.as_ref().into()), + ..ShimConfig::default() + } + } + + /// Create a local shim that executes the provided binary + /// through the context of the configured parent. + pub fn local_with_parent(bin_path: B, parent: P) -> ShimConfig + where + B: AsRef, + P: AsRef, + { + ShimConfig { + bin_path: Some(bin_path.as_ref().into()), + parent_bin: Some(parent.as_ref().to_owned()), + ..ShimConfig::default() + } + } +} + +json_struct!( + /// Input passed to the `create_shims` function. + pub struct CreateShimsInput { + /// Current tool context. + pub context: ToolContext, + } +); + +json_struct!( + /// Output returned by the `create_shims` function. + #[deprecated(since = "0.22.0", note = "Use `locate_executables` function instead.")] + pub struct CreateShimsOutput { + /// Avoid creating the global shim. + pub no_primary_global: bool, + + /// Configures the default/primary global shim. + #[serde(skip_serializing_if = "Option::is_none")] + pub primary: Option, + + /// Additional global shims to create in the `~/.proto/shims` directory. + /// Maps a shim name to a relative binary path. + pub global_shims: HashMap, + + /// Local shims to create in the `~/.proto/tools///shims` directory. + /// Maps a shim name to its configuration. + pub local_shims: HashMap, + } +); diff --git a/crates/pdk-api/src/lib.rs b/crates/pdk-api/src/lib.rs index a5fa9b16c..ccb23f786 100644 --- a/crates/pdk-api/src/lib.rs +++ b/crates/pdk-api/src/lib.rs @@ -1,10 +1,12 @@ mod api; +mod api_deprecated; mod error; mod hooks; mod host; mod host_funcs; pub use api::*; +pub use api_deprecated::*; pub use error::*; pub use hooks::*; pub use host::*; diff --git a/crates/pdk-test-utils/Cargo.toml b/crates/pdk-test-utils/Cargo.toml index 8dc6452b5..1dcef6fd4 100644 --- a/crates/pdk-test-utils/Cargo.toml +++ b/crates/pdk-test-utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proto_pdk_test_utils" -version = "0.9.1" +version = "0.10.1" edition = "2021" license = "MIT" description = "Utilities for testing proto WASM plugins." @@ -8,11 +8,11 @@ homepage = "https://moonrepo.dev/proto" repository = "https://github.com/moonrepo/proto" [dependencies] -proto_core = { version = "0.21.2", path = "../core" } -proto_pdk_api = { version = "0.9.0", path = "../pdk-api" } +proto_core = { version = "0.22.1", path = "../core" } +proto_pdk_api = { version = "0.10.1", path = "../pdk-api" } extism = { workspace = true } serde_json = { workspace = true } -toml = { version = "0.8.2", optional = true } +toml = { version = "0.8.6", optional = true } [features] default = [] diff --git a/crates/pdk-test-utils/src/macros.rs b/crates/pdk-test-utils/src/macros.rs index c195df2c8..1f99e7b61 100644 --- a/crates/pdk-test-utils/src/macros.rs +++ b/crates/pdk-test-utils/src/macros.rs @@ -12,15 +12,9 @@ macro_rules! generate_download_install_tests { } else { create_plugin($id, sandbox.path()) }; + let spec = proto_pdk_test_utils::UnresolvedVersionSpec::parse($version).unwrap(); - plugin - .tool - .setup( - &proto_pdk_test_utils::UnresolvedVersionSpec::parse($version).unwrap(), - false, - ) - .await - .unwrap(); + plugin.tool.setup(&spec, false).await.unwrap(); // Check install dir exists let base_dir = sandbox.path().join(".proto/tools").join($id).join($version); @@ -30,18 +24,16 @@ macro_rules! generate_download_install_tests { assert!(base_dir.exists()); // Check bin path exists (would panic) - plugin.tool.get_bin_path().unwrap(); + plugin.tool.get_exe_path().unwrap(); - // Check global shim exists - assert!(sandbox - .path() - .join(".proto/shims") - .join(if cfg!(windows) { - format!("{}.cmd", $id) - } else { - $id.into() - }) - .exists()); + // Check things exist + for bin in plugin.tool.get_bin_locations().unwrap() { + assert!(bin.path.exists()); + } + + for shim in plugin.tool.get_shim_locations().unwrap() { + assert!(shim.path.exists()); + } } #[tokio::test] @@ -187,12 +179,12 @@ macro_rules! generate_resolve_versions_tests { } #[macro_export] -macro_rules! generate_global_shims_test { +macro_rules! generate_shims_test { ($id:literal) => { - generate_global_shims_test!($id, []); + generate_shims_test!($id, []); }; ($id:literal, [ $($bin:literal),* ]) => { - generate_global_shims_test!($id, [ $($bin),* ], None); + generate_shims_test!($id, [ $($bin),* ], None); }; ($id:literal, [ $($bin:literal),* ], $schema:expr) => { #[tokio::test] @@ -204,7 +196,7 @@ macro_rules! generate_global_shims_test { create_plugin($id, sandbox.path()) }; - plugin.tool.create_shims(false).await.unwrap(); + plugin.tool.generate_shims(false).await.unwrap(); starbase_sandbox::assert_snapshot!(std::fs::read_to_string( sandbox.path().join(".proto/shims").join(if cfg!(windows) { @@ -227,36 +219,6 @@ macro_rules! generate_global_shims_test { }; } -#[macro_export] -macro_rules! generate_local_shims_test { - ($id:literal, [ $($bin:literal),* ]) => { - generate_local_shims_test!($id, [ $($bin),* ], None); - }; - ($id:literal, [ $($bin:literal),* ], $schema:expr) => { - #[tokio::test] - async fn creates_local_shims() { - let sandbox = starbase_sandbox::create_empty_sandbox(); - let mut plugin = if let Some(schema) = $schema { - create_schema_plugin($id, sandbox.path(), schema) - } else { - create_plugin($id, sandbox.path()) - }; - - plugin.tool.create_shims(false).await.unwrap(); - - $( - starbase_sandbox::assert_snapshot!(std::fs::read_to_string( - sandbox.path().join(".proto/tools").join($id).join("latest/shims").join(if cfg!(windows) { - format!("{}.ps1", $bin) - } else { - $bin.to_string() - }) - ).unwrap()); - )* - } - }; -} - #[macro_export] macro_rules! generate_globals_test { ($id:literal, $dep:literal) => { diff --git a/crates/pdk-test-utils/src/wrapper.rs b/crates/pdk-test-utils/src/wrapper.rs index c52173872..fbd69c467 100644 --- a/crates/pdk-test-utils/src/wrapper.rs +++ b/crates/pdk-test-utils/src/wrapper.rs @@ -23,15 +23,6 @@ impl WasmTestWrapper { self.tool.plugin.reload_config().unwrap(); } - pub fn create_shims(&self, mut input: CreateShimsInput) -> CreateShimsOutput { - input.context = self.prepare_context(input.context); - - self.tool - .plugin - .call_func_with("create_shims", input) - .unwrap() - } - pub fn detect_version_files(&self) -> DetectVersionOutput { self.tool.plugin.call_func("detect_version_files").unwrap() } @@ -62,20 +53,13 @@ impl WasmTestWrapper { .unwrap() } - pub fn locate_bins(&self, mut input: LocateBinsInput) -> LocateBinsOutput { + pub fn locate_executables(&self, mut input: LocateExecutablesInput) -> LocateExecutablesOutput { input.context = self.prepare_context(input.context); - let mut output: LocateBinsOutput = self - .tool + self.tool .plugin - .call_func_with("locate_bins", input) - .unwrap(); - - if let Some(bin_path) = output.bin_path { - output.bin_path = Some(self.from_virtual_path(&bin_path)); - } - - output + .call_func_with("locate_executables", input) + .unwrap() } pub fn native_install(&self, mut input: NativeInstallInput) -> NativeInstallOutput { diff --git a/crates/pdk/Cargo.toml b/crates/pdk/Cargo.toml index 472002cc6..1eb0de330 100644 --- a/crates/pdk/Cargo.toml +++ b/crates/pdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proto_pdk" -version = "0.9.0" +version = "0.10.1" edition = "2021" license = "MIT" description = "A plugin development kit for creating proto WASM plugins." @@ -8,7 +8,7 @@ homepage = "https://moonrepo.dev/proto" repository = "https://github.com/moonrepo/proto" [dependencies] -proto_pdk_api = { version = "0.9.0", path = "../pdk-api" } +proto_pdk_api = { version = "0.10.1", path = "../pdk-api" } anyhow = "1.0.75" extism-pdk = { workspace = true } serde = { workspace = true } diff --git a/crates/pdk/src/helpers.rs b/crates/pdk/src/helpers.rs index de450830b..aa8f1bc29 100644 --- a/crates/pdk/src/helpers.rs +++ b/crates/pdk/src/helpers.rs @@ -89,7 +89,7 @@ where let output = exec_command!( pipe, "git", - ["ls-remote", "--tags", "--sort", "version:refname", url,] + ["ls-remote", "--tags", "--sort", "version:refname", url] ); let mut tags: Vec = vec![]; @@ -124,6 +124,7 @@ where /// Return the name of the binary for the provided name and OS. /// On Windows, will append ".exe", and keep as-is on other OS's. +#[deprecated] pub fn format_bin_name>(name: T, os: HostOS) -> String { if os == HostOS::Windows { return format!("{}.exe", name.as_ref()); diff --git a/crates/system-env/Cargo.toml b/crates/system-env/Cargo.toml index 751dfa66d..da1aab836 100644 --- a/crates/system-env/Cargo.toml +++ b/crates/system-env/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "system_env" -version = "0.1.2" +version = "0.1.3" edition = "2021" license = "MIT" description = "Information about the system environment: operating system, architecture, package manager, etc." diff --git a/crates/system-env/src/env.rs b/crates/system-env/src/env.rs index 1b0c32602..7882664ec 100644 --- a/crates/system-env/src/env.rs +++ b/crates/system-env/src/env.rs @@ -84,6 +84,35 @@ impl SystemOS { .expect("Unknown operating system!") } + /// Return either a Unix or Windows value based on the current native host. + pub fn for_native<'value, T: AsRef + ?Sized>( + &self, + unix: &'value T, + windows: &'value T, + ) -> &'value str { + if self.is_windows() { + windows.as_ref() + } else { + unix.as_ref() + } + } + + /// Return the provided name as a host formatted file name for executables. + /// On Windows this will append an ".exe" extension. On Unix, no extension. + pub fn get_exe_name(&self, name: impl AsRef) -> String { + self.get_file_name(name, "exe") + } + + /// Return the provided file name formatted with the extension (without dot) + /// when on Windows. On Unix, returns the name as-is. + pub fn get_file_name(&self, name: impl AsRef, windows_ext: impl AsRef) -> String { + if self.is_windows() { + format!("{}.{}", name.as_ref(), windows_ext.as_ref()) + } else { + name.as_ref().to_owned() + } + } + pub fn is_bsd(&self) -> bool { matches!( self, diff --git a/crates/version-spec/Cargo.toml b/crates/version-spec/Cargo.toml index dcd57085a..e7056b191 100644 --- a/crates/version-spec/Cargo.toml +++ b/crates/version-spec/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "version_spec" -version = "0.1.3" +version = "0.1.4" edition = "2021" license = "MIT" description = "A specification for working with partial, full, or aliased versions." @@ -9,7 +9,6 @@ repository = "https://github.com/moonrepo/proto" [dependencies] human-sort = { workspace = true } -once_cell = { workspace = true } regex = { workspace = true } semver = { workspace = true } serde = { workspace = true } diff --git a/crates/version-spec/src/lib.rs b/crates/version-spec/src/lib.rs index 93029bf25..1f76cf01f 100644 --- a/crates/version-spec/src/lib.rs +++ b/crates/version-spec/src/lib.rs @@ -1,7 +1,6 @@ mod resolved_spec; mod unresolved_spec; -use once_cell::sync::Lazy; use regex::Regex; pub use resolved_spec::*; @@ -25,8 +24,6 @@ pub fn is_alias_name>(value: T) -> bool { }) } -static CLEAN_VERSION: Lazy = Lazy::new(|| Regex::new(r"([><]=?)\s+(\d)").unwrap()); - pub fn clean_version_string>(value: T) -> String { let value = value.as_ref().trim().replace(".*", ""); let mut version = value.as_str(); @@ -37,5 +34,21 @@ pub fn clean_version_string>(value: T) -> String { } // Remove invalid space after <, <=, >, >=. - CLEAN_VERSION.replace_all(version, "$1$2").to_string() + Regex::new(r"([><]=?)[ ]+([0-9])") + .unwrap() + .replace_all(version, "$1$2") + .to_string() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn cleans_string() { + assert_eq!(clean_version_string(">= 1.2.3"), ">=1.2.3"); + assert_eq!(clean_version_string("> 1.2.3"), ">1.2.3"); + assert_eq!(clean_version_string("<1.2.3"), "<1.2.3"); + assert_eq!(clean_version_string("<= 1.2.3"), "<=1.2.3"); + } } diff --git a/crates/warpgate/src/loader.rs b/crates/warpgate/src/loader.rs index da21cf95a..7b63cad44 100644 --- a/crates/warpgate/src/loader.rs +++ b/crates/warpgate/src/loader.rs @@ -71,7 +71,6 @@ impl PluginLoader { trace!( plugin = id.as_str(), - locator = locator.to_string(), "Loading plugin {}", color::id(id.as_str()) ); diff --git a/plugins/Cargo.lock b/plugins/Cargo.lock index 21cf7f0a4..ee41331be 100644 --- a/plugins/Cargo.lock +++ b/plugins/Cargo.lock @@ -1578,9 +1578,9 @@ dependencies = [ [[package]] name = "json_comments" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ee439ee368ba4a77ac70d04f14015415af8600d6c894dc1f11bd79758c57d5" +checksum = "9dbbfed4e59ba9750e15ba154fdfd9329cee16ff3df539c2666b70f58cc32105" [[package]] name = "lazy_static" @@ -2016,7 +2016,7 @@ dependencies = [ [[package]] name = "proto_core" -version = "0.20.4" +version = "0.21.2" dependencies = [ "cached", "extism", @@ -2045,7 +2045,7 @@ dependencies = [ [[package]] name = "proto_pdk" -version = "0.8.2" +version = "0.9.0" dependencies = [ "anyhow", "extism-pdk", @@ -2055,7 +2055,7 @@ dependencies = [ [[package]] name = "proto_pdk_api" -version = "0.8.2" +version = "0.9.0" dependencies = [ "anyhow", "semver", @@ -2069,24 +2069,12 @@ dependencies = [ [[package]] name = "proto_pdk_test_utils" -version = "0.8.5" +version = "0.9.1" dependencies = [ "extism", "proto_core", "proto_pdk_api", - "proto_wasm_plugin", - "serde_json", -] - -[[package]] -name = "proto_wasm_plugin" -version = "0.7.2" -dependencies = [ - "extism", - "proto_pdk_api", "serde_json", - "starbase_utils", - "tracing", ] [[package]] @@ -2540,9 +2528,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -2748,9 +2736,9 @@ dependencies = [ [[package]] name = "starbase_utils" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9faf731068b919569efae7f6c7c6c3d2e29411c1cba666ef0c6fe20b82f50a1" +checksum = "d948dcd63f1dd11f2b7a9ab15c53e7866faa11ccd31377dca1625c106d40fcee" dependencies = [ "dirs 5.0.1", "fs4", @@ -2764,7 +2752,7 @@ dependencies = [ "starbase_styles", "thiserror", "tokio", - "toml 0.8.5", + "toml 0.8.6", "tracing", "wax", ] @@ -2846,7 +2834,7 @@ dependencies = [ [[package]] name = "system_env" -version = "0.1.1" +version = "0.1.2" dependencies = [ "serde", "serde_json", @@ -3057,14 +3045,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3efaf127c78d5339cc547cce4e4d973bd5e4f56e949a06d091c082ebeef2f800" +checksum = "8ff9e3abce27ee2c9a37f9ad37238c1bdd4e789c84ba37df76aa4d528f5072cc" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.20.5", + "toml_edit 0.20.7", ] [[package]] @@ -3091,9 +3079,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.20.5" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "782bf6c2ddf761c1e7855405e8975472acf76f7f36d0d4328bd3b7a2fae12a85" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ "indexmap 2.0.0", "serde", @@ -3268,7 +3256,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "version_spec" -version = "0.1.1" +version = "0.1.3" dependencies = [ "human-sort", "once_cell", @@ -3307,7 +3295,7 @@ dependencies = [ [[package]] name = "warpgate" -version = "0.5.12" +version = "0.5.13" dependencies = [ "extism", "miette", diff --git a/plugins/wasm-test/src/lib.rs b/plugins/wasm-test/src/lib.rs index e730f0335..edcb8652f 100644 --- a/plugins/wasm-test/src/lib.rs +++ b/plugins/wasm-test/src/lib.rs @@ -107,6 +107,22 @@ pub fn download_prebuilt( // Ok(()) // } +#[plugin_fn] +pub fn locate_executables( + Json(_): Json, +) -> FnResult> { + let env = get_proto_environment()?; + + Ok(Json(LocateExecutablesOutput { + globals_lookup_dirs: vec!["$WASM_ROOT/bin".into(), "$HOME/.wasm/bin".into()], + primary: Some(ExecutableConfig::new( + env.os.for_native("bin/node", "node.exe"), + )), + secondary: HashMap::from_iter([("global1".into(), ExecutableConfig::new("bin/global1"))]), + ..LocateExecutablesOutput::default() + })) +} + #[plugin_fn] pub fn locate_bins(Json(_): Json) -> FnResult> { let env = get_proto_environment()?; diff --git a/plugins/wasm-test/tests/macros_test.rs b/plugins/wasm-test/tests/macros_test.rs index a3f5100aa..fb8a44423 100644 --- a/plugins/wasm-test/tests/macros_test.rs +++ b/plugins/wasm-test/tests/macros_test.rs @@ -9,8 +9,6 @@ generate_resolve_versions_tests!("wasm-test", { "latest" => "1.2.3", }); -generate_global_shims_test!("wasm-test"); - -generate_local_shims_test!("wasm-test", ["other"]); +generate_shims_test!("wasm-test", ["other"]); generate_globals_test!("wasm-test", "dependency");