From 2a40611cc8d640c067829b82c752764ee1a6b1d5 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 6 Nov 2023 12:31:58 -0800 Subject: [PATCH 1/7] new: Add new executables system. (#268) --- CHANGELOG.md | 24 + Cargo.lock | 40 +- Cargo.toml | 9 +- crates/cli/Cargo.toml | 7 +- crates/cli/src/app.rs | 8 +- crates/cli/src/commands/bin.rs | 27 +- crates/cli/src/commands/clean.rs | 33 +- crates/cli/src/commands/install.rs | 10 +- crates/cli/src/commands/migrate/v0_20.rs | 5 +- crates/cli/src/commands/pin.rs | 6 +- crates/cli/src/commands/run.rs | 113 ++-- crates/cli/src/helpers.rs | 7 +- crates/cli/tests/bin_test.rs | 46 +- crates/cli/tests/clean_test.rs | 10 +- crates/cli/tests/plugins_test.rs | 4 +- crates/cli/tests/use_test.rs | 4 +- crates/core/Cargo.toml | 6 +- crates/core/src/error.rs | 6 +- crates/core/src/events.rs | 4 + crates/core/src/helpers.rs | 4 + crates/core/src/shimmer.rs | 85 +-- crates/core/src/tool.rs | 561 +++++++++--------- crates/core/src/tool_loader.rs | 117 ++-- crates/core/src/tools_config.rs | 30 +- crates/core/tests/shimmer_test.rs | 139 +---- .../shimmer_test__shimmer__local.snap | 22 - ...himmer_test__shimmer__local_with_args.snap | 22 - ...mmer_test__shimmer__local_with_parent.snap | 23 - ...__shimmer__local_with_parent_and_args.snap | 23 - ...himmer_test__shimmer__tool_id_formats.snap | 23 - crates/core/tests/user_config_test.rs | 2 +- crates/pdk-api/Cargo.toml | 6 +- crates/pdk-api/src/api.rs | 186 ++---- crates/pdk-api/src/api_deprecated.rs | 145 +++++ crates/pdk-api/src/lib.rs | 2 + crates/pdk-test-utils/Cargo.toml | 8 +- crates/pdk-test-utils/src/macros.rs | 68 +-- crates/pdk-test-utils/src/wrapper.rs | 24 +- crates/pdk/Cargo.toml | 4 +- crates/pdk/src/helpers.rs | 3 +- crates/system-env/Cargo.toml | 2 +- crates/system-env/src/env.rs | 29 + crates/version-spec/Cargo.toml | 3 +- crates/version-spec/src/lib.rs | 21 +- crates/warpgate/src/loader.rs | 1 - plugins/Cargo.lock | 50 +- plugins/wasm-test/src/lib.rs | 16 + plugins/wasm-test/tests/macros_test.rs | 4 +- 48 files changed, 957 insertions(+), 1035 deletions(-) delete mode 100644 crates/core/tests/snapshots/shimmer_test__shimmer__local.snap delete mode 100644 crates/core/tests/snapshots/shimmer_test__shimmer__local_with_args.snap delete mode 100644 crates/core/tests/snapshots/shimmer_test__shimmer__local_with_parent.snap delete mode 100644 crates/core/tests/snapshots/shimmer_test__shimmer__local_with_parent_and_args.snap delete mode 100644 crates/core/tests/snapshots/shimmer_test__shimmer__tool_id_formats.snap create mode 100644 crates/pdk-api/src/api_deprecated.rs 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"); From 5e95bffb4fb6545a246fb9867f33a1adf66b75d1 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 6 Nov 2023 16:17:47 -0800 Subject: [PATCH 2/7] new: Reduce internet connection checks. (#274) --- CHANGELOG.md | 18 +++++++ crates/cli/src/commands/bin.rs | 2 +- crates/cli/src/commands/migrate/v0_20.rs | 2 +- crates/core/src/error.rs | 2 +- crates/core/src/proto.rs | 4 +- crates/core/src/tool.rs | 62 +++++++++++++----------- crates/core/src/tool_loader.rs | 10 ++-- crates/core/src/tools_config.rs | 16 +++--- crates/core/src/version_detector.rs | 10 ++-- crates/pdk-test-utils/src/macros.rs | 4 ++ crates/warpgate/src/loader.rs | 37 +++++++++----- 11 files changed, 102 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1487e3272..0c8e6136f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,12 +24,30 @@ - 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. +- Updated internet connection checks to only check during critical workflows. + - Will no longer happen if we have a fully-qualified version (primarily for `proto run`). + - Will still happen for partial versions, as we need to resolve to a fully-qualified. + - Will always happen for install, upgrade, and other flows that must download files. +- TOML API + - Added `install.no_bin` and `install.no_shim` fields. - 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. +#### 🐞 Fixes + +- Fixed an issue where config files in the user home directory were not loaded. + +#### 🧩 Plugins + +- **Node** + - Updated the `npm` tool to create the `npx` shim instead of the `node` tool. + - Updated executable detection for package managers to use the shell scripts instead of the source `.js` files (when applicable). + - Previously we would execute the JS file with node: `node ./bin/npm-cli.js` + - Now we execute the shell script: `./bin/npm` (unix), `./bin/npm.cmd` (windows) + #### ⚙️ Internal - Plugin versions are now pinned and tied to proto releases to avoid unintended drift and API changes. diff --git a/crates/cli/src/commands/bin.rs b/crates/cli/src/commands/bin.rs index 6fa892ee7..2ee93b9f4 100644 --- a/crates/cli/src/commands/bin.rs +++ b/crates/cli/src/commands/bin.rs @@ -22,7 +22,7 @@ pub async fn bin(args: ArgsRef) { let mut tool = load_tool(&args.id).await?; let version = detect_version(&tool, args.spec.clone()).await?; - tool.resolve_version(&version).await?; + tool.resolve_version(&version, true).await?; tool.create_executables(true, false).await?; if args.bin { diff --git a/crates/cli/src/commands/migrate/v0_20.rs b/crates/cli/src/commands/migrate/v0_20.rs index d003c768a..40cd3b752 100644 --- a/crates/cli/src/commands/migrate/v0_20.rs +++ b/crates/cli/src/commands/migrate/v0_20.rs @@ -19,7 +19,7 @@ pub async fn migrate() -> SystemResult { for tool in &mut tools { // Resolve the global version for use in shims and bins if let Some(spec) = tool.manifest.default_version.clone() { - tool.resolve_version(&spec).await?; + tool.resolve_version(&spec, false).await?; } } diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 8eeb5f13c..0ba9d7d19 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -31,7 +31,7 @@ pub enum ProtoError { #[diagnostic(code(proto::misc::offline_version_required))] #[error( - "Internet connection required to resolve a valid version. To work around this:\n - Pass a semantic version explicitly: {}\n - Execute the non-shim binaries instead: {}", + "Internet connection required to load and resolve a valid version. To work around this:\n - Pass a semantic version explicitly: {}\n - Execute the non-shim binaries instead: {}", .command.style(Style::Shell), .bin_dir.style(Style::Path) )] diff --git a/crates/core/src/proto.rs b/crates/core/src/proto.rs index 04dd5be52..6b10f5701 100644 --- a/crates/core/src/proto.rs +++ b/crates/core/src/proto.rs @@ -7,7 +7,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use warpgate::{create_http_client_with_options, PluginLoader}; -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct ProtoEnvironment { pub bin_dir: PathBuf, pub cwd: PathBuf, @@ -63,7 +63,7 @@ impl ProtoEnvironment { pub fn get_plugin_loader(&self) -> &PluginLoader { self.loader.get_or_init(|| { let mut loader = PluginLoader::new(&self.plugins_dir, &self.temp_dir); - loader.set_offline(is_offline()); + loader.set_offline_checker(is_offline); loader.set_seed(env!("CARGO_PKG_VERSION")); loader }) diff --git a/crates/core/src/tool.rs b/crates/core/src/tool.rs index 119ed0356..3a42c1846 100644 --- a/crates/core/src/tool.rs +++ b/crates/core/src/tool.rs @@ -379,19 +379,26 @@ impl Tool { .join("remote-versions.json"); // Attempt to read from the cache first - if cache_path.exists() && (is_cache_enabled() || is_offline()) { - let metadata = fs::metadata(&cache_path)?; - - // If offline, always use the cache, otherwise only within the last 12 hours - let read_cache = if is_offline() { - true - } else if !self.cache { - false - } else if let Ok(modified_time) = metadata.modified().or_else(|_| metadata.created()) { - modified_time > SystemTime::now() - Duration::from_secs(60 * 60 * 12) - } else { - false - }; + if cache_path.exists() { + let mut read_cache = + // Check if cache is enabled here, so that we can handle offline below + if !self.cache || !is_cache_enabled() { + false + // Otherwise, only read the cache every 12 hours + } else { + let metadata = fs::metadata(&cache_path)?; + + if let Ok(modified_time) = metadata.modified().or_else(|_| metadata.created()) { + modified_time > SystemTime::now() - Duration::from_secs(60 * 60 * 12) + } else { + false + } + }; + + // If offline, always read the cache + if !read_cache && is_offline() { + read_cache = true; + } if read_cache { debug!(tool = self.id.as_str(), cache = ?cache_path, "Loading from local cache"); @@ -404,7 +411,11 @@ impl Tool { // Nothing cached, so load from the plugin if !cached { if is_offline() { - return Err(ProtoError::InternetConnectionRequired.into()); + return Err(ProtoError::InternetConnectionRequiredForVersion { + command: format!("{}_VERSION=1.2.3 {}", self.get_env_var_prefix(), self.id), + bin_dir: self.proto.bin_dir.clone(), + } + .into()); } versions = self.plugin.cache_func_with( @@ -429,6 +440,7 @@ impl Tool { pub async fn resolve_version( &mut self, initial_version: &UnresolvedVersionSpec, + short_circuit: bool, ) -> miette::Result<()> { if self.version.is_some() { return Ok(()); @@ -440,10 +452,10 @@ impl Tool { "Resolving a semantic version or alias", ); - // If offline but we have a fully qualified semantic version, - // exit early and assume the version is legitimate! Additionally, - // canary is a special type that we can simply just use. - if is_offline() && matches!(initial_version, UnresolvedVersionSpec::Version(_)) + // If we have a fully qualified semantic version, + // exit early and assume the version is legitimate! + // Also canary is a special type that we can simply just use. + if short_circuit && matches!(initial_version, UnresolvedVersionSpec::Version(_)) || matches!(initial_version, UnresolvedVersionSpec::Canary) { let version = initial_version.to_resolved_spec(); @@ -451,7 +463,7 @@ impl Tool { debug!( tool = self.id.as_str(), version = version.to_string(), - "Resolved to {}", + "Resolved to {} (without validation)", version ); @@ -467,14 +479,6 @@ impl Tool { return Ok(()); } - if is_offline() { - return Err(ProtoError::InternetConnectionRequiredForVersion { - command: format!("{}_VERSION=1.2.3 {}", self.get_env_var_prefix(), self.id), - bin_dir: self.proto.bin_dir.clone(), - } - .into()); - } - let resolver = self.load_version_resolver(initial_version).await?; let mut version = VersionSpec::default(); let mut resolved = false; @@ -1453,7 +1457,7 @@ impl Tool { &mut self, initial_version: &UnresolvedVersionSpec, ) -> miette::Result { - self.resolve_version(initial_version).await?; + self.resolve_version(initial_version, true).await?; let install_dir = self.get_tool_dir(); @@ -1489,7 +1493,7 @@ impl Tool { initial_version: &UnresolvedVersionSpec, build_from_source: bool, ) -> miette::Result { - self.resolve_version(initial_version).await?; + self.resolve_version(initial_version, false).await?; if self.install(build_from_source).await? { self.create_executables(true, false).await?; diff --git a/crates/core/src/tool_loader.rs b/crates/core/src/tool_loader.rs index b66f96b19..deb50f719 100644 --- a/crates/core/src/tool_loader.rs +++ b/crates/core/src/tool_loader.rs @@ -73,11 +73,6 @@ pub fn locate_tool( 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) { @@ -85,8 +80,9 @@ pub fn locate_tool( break; } - // We only want to check the current directory - if current_dir_only { + // Don't traverse passed the home directory, + // or only want to check the current directory + if dir == proto.home || current_dir_only { break; } diff --git a/crates/core/src/tools_config.rs b/crates/core/src/tools_config.rs index c57e8aef5..66aaf2d31 100644 --- a/crates/core/src/tools_config.rs +++ b/crates/core/src/tools_config.rs @@ -126,7 +126,7 @@ impl ToolsConfig { self.plugins.insert( Id::raw("bun"), PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/bun-plugin/releases/download/v0.5.0-alpha.0/bun_plugin.wasm".into() + url: "https://github.com/moonrepo/bun-plugin/releases/download/v0.5.0-alpha.1/bun_plugin.wasm".into() } ); } @@ -135,7 +135,7 @@ impl ToolsConfig { self.plugins.insert( Id::raw("deno"), PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/deno-plugin/releases/download/v0.5.0-alpha.0/deno_plugin.wasm".into() + url: "https://github.com/moonrepo/deno-plugin/releases/download/v0.5.0-alpha.1/deno_plugin.wasm".into() } ); } @@ -144,7 +144,7 @@ impl ToolsConfig { self.plugins.insert( Id::raw("go"), PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/go-plugin/releases/download/v0.5.0-alpha.0/go_plugin.wasm".into() + url: "https://github.com/moonrepo/go-plugin/releases/download/v0.5.0-alpha.1/go_plugin.wasm".into() } ); } @@ -153,7 +153,7 @@ impl ToolsConfig { self.plugins.insert( Id::raw("node"), PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/node-plugin/releases/download/v0.5.0-alpha.0/node_plugin.wasm".into() + url: "https://github.com/moonrepo/node-plugin/releases/download/v0.5.0-alpha.1/node_plugin.wasm".into() } ); } @@ -163,7 +163,7 @@ impl ToolsConfig { self.plugins.insert( Id::raw(depman), PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/node-plugin/releases/download/v0.5.0-alpha.0/node_depman_plugin.wasm".into() + url: "https://github.com/moonrepo/node-plugin/releases/download/v0.5.0-alpha.1/node_depman_plugin.wasm".into() } ); } @@ -173,7 +173,7 @@ impl ToolsConfig { self.plugins.insert( Id::raw("python"), PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/python-plugin/releases/download/v0.2.0-alpha.0/python_plugin.wasm".into() + url: "https://github.com/moonrepo/python-plugin/releases/download/v0.2.0-alpha.1/python_plugin.wasm".into() } ); } @@ -182,7 +182,7 @@ impl ToolsConfig { self.plugins.insert( Id::raw("rust"), PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/rust-plugin/releases/download/v0.4.0-alpha.0/rust_plugin.wasm".into() + url: "https://github.com/moonrepo/rust-plugin/releases/download/v0.4.0-alpha.1/rust_plugin.wasm".into() } ); } @@ -191,7 +191,7 @@ impl ToolsConfig { 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() + url: "https://github.com/moonrepo/schema-plugin/releases/download/v0.5.0-alpha.1/schema_plugin.wasm".into() } ); } diff --git a/crates/core/src/version_detector.rs b/crates/core/src/version_detector.rs index efae369a9..4ca8f5c7b 100644 --- a/crates/core/src/version_detector.rs +++ b/crates/core/src/version_detector.rs @@ -48,11 +48,6 @@ pub async fn detect_version( let mut current_dir: Option<&Path> = Some(&working_dir); while let Some(dir) = current_dir { - // Don't traverse past the home directory - if dir == tool.proto.home { - break; - } - trace!( tool = tool.id.as_str(), dir = ?dir, @@ -84,6 +79,11 @@ pub async fn detect_version( return Ok(detected_version); } + // Don't traverse passed the home directory + if dir == tool.proto.home { + break; + } + current_dir = dir.parent(); } } diff --git a/crates/pdk-test-utils/src/macros.rs b/crates/pdk-test-utils/src/macros.rs index 1f99e7b61..ef4c47980 100644 --- a/crates/pdk-test-utils/src/macros.rs +++ b/crates/pdk-test-utils/src/macros.rs @@ -101,6 +101,7 @@ macro_rules! generate_resolve_versions_tests { plugin.tool.resolve_version( &proto_pdk_test_utils::UnresolvedVersionSpec::parse("latest").unwrap(), + false, ).await.unwrap(); assert_ne!(plugin.tool.get_resolved_version(), "latest"); @@ -118,6 +119,7 @@ macro_rules! generate_resolve_versions_tests { $( plugin.tool.resolve_version( &proto_pdk_test_utils::UnresolvedVersionSpec::parse($k).unwrap(), + false, ).await.unwrap(); assert_eq!( @@ -158,6 +160,7 @@ macro_rules! generate_resolve_versions_tests { plugin.tool.resolve_version( &proto_pdk_test_utils::UnresolvedVersionSpec::parse("unknown").unwrap(), + false, ).await.unwrap(); } @@ -173,6 +176,7 @@ macro_rules! generate_resolve_versions_tests { plugin.tool.resolve_version( &proto_pdk_test_utils::UnresolvedVersionSpec::parse("99.99.99").unwrap(), + false, ).await.unwrap(); } }; diff --git a/crates/warpgate/src/loader.rs b/crates/warpgate/src/loader.rs index 7b63cad44..0204e4c09 100644 --- a/crates/warpgate/src/loader.rs +++ b/crates/warpgate/src/loader.rs @@ -12,15 +12,18 @@ use starbase_styles::color; use starbase_utils::fs; use std::env; use std::path::{Path, PathBuf}; +use std::sync::Arc; use std::time::{Duration, SystemTime}; use tracing::trace; +pub type OfflineChecker = Arc bool>; + /// A system for loading plugins from a locator strategy, /// and caching the `.wasm` file to the host's file system. -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct PluginLoader { - /// Whether there's an internet connection or not. - offline: bool, + /// Checks whether there's an internet connection or not. + offline_checker: Option, /// Location where downloaded `.wasm` plugins are stored. plugins_dir: PathBuf, @@ -40,7 +43,7 @@ impl PluginLoader { trace!(cache_dir = ?plugins_dir, "Creating plugin loader"); Self { - offline: false, + offline_checker: None, plugins_dir: plugins_dir.to_owned(), temp_dir: temp_dir.as_ref().to_owned(), seed: None, @@ -140,7 +143,7 @@ impl PluginLoader { let mut cached = true; // If latest, cache only lasts for 7 days - if !self.offline && fs::file_name(path).contains("-latest-") { + if fs::file_name(path).contains("-latest-") { let metadata = fs::metadata(path)?; cached = if let Ok(filetime) = metadata.created().or_else(|_| metadata.modified()) { @@ -149,6 +152,10 @@ impl PluginLoader { false }; + if !cached && self.is_offline() { + cached = true; + } + if !cached { fs::remove_file(path)?; } @@ -163,9 +170,17 @@ impl PluginLoader { Ok(cached) } - /// Set the offline state. - pub fn set_offline(&mut self, value: bool) { - self.offline = value; + /// Check for an internet connection. + pub fn is_offline(&self) -> bool { + self.offline_checker + .as_ref() + .map(|op| op()) + .unwrap_or_default() + } + + /// Set the function that checks for offline state. + pub fn set_offline_checker(&mut self, op: fn() -> bool) { + self.offline_checker = Some(Arc::new(op)); } /// Set the provided value as a seed for generating hashes. @@ -184,7 +199,7 @@ impl PluginLoader { return Ok(dest_file); } - if self.offline { + if self.is_offline() { return Err(WarpgateError::InternetConnectionRequired { message: "Unable to download plugin.".into(), url: source_url.to_owned(), @@ -251,7 +266,7 @@ impl PluginLoader { url: api_url.clone(), }; - if self.offline { + if self.is_offline() { return Err(WarpgateError::InternetConnectionRequired { message: format!( "Unable to download plugin {} from GitHub.", @@ -350,7 +365,7 @@ impl PluginLoader { // Otherwise make a GraphQL request to the WAPM registry API. let url = "https://registry.wapm.io/graphql".to_owned(); - if self.offline { + if self.is_offline() { return Err(WarpgateError::InternetConnectionRequired { message: format!( "Unable to download plugin {} from wapm.io.", From a6bee26af02481e25f6b5eb4a81ab2c2c10d04f9 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 6 Nov 2023 18:23:16 -0800 Subject: [PATCH 3/7] new: Support uninstalling a tool entirely. (#275) --- CHANGELOG.md | 1 + crates/cli/src/commands/clean.rs | 8 +++---- crates/cli/src/commands/uninstall.rs | 24 +++++++++++++------- crates/cli/tests/uninstall_test.rs | 33 ++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c8e6136f..29dfb4ae9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ - 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. +- Updated `proto uninstall` to support removing the tool entirely (simply omit the version). - Updated internet connection checks to only check during critical workflows. - Will no longer happen if we have a fully-qualified version (primarily for `proto run`). - Will still happen for partial versions, as we need to resolve to a fully-qualified. diff --git a/crates/cli/src/commands/clean.rs b/crates/cli/src/commands/clean.rs index cc35204e0..1e6b6b304 100644 --- a/crates/cli/src/commands/clean.rs +++ b/crates/cli/src/commands/clean.rs @@ -179,7 +179,7 @@ pub async fn clean_plugins(days: u64) -> miette::Result { Ok(clean_count) } -async fn purge_tool(id: &Id, yes: bool) -> SystemResult { +pub async fn purge_tool(id: &Id, yes: bool) -> SystemResult { let tool = load_tool(id).await?; let inventory_dir = tool.get_inventory_dir(); @@ -206,13 +206,13 @@ async fn purge_tool(id: &Id, yes: bool) -> SystemResult { fs::remove_file(shim.path)?; } - info!("Removed {}", tool.get_name()); + info!("Purged {}", tool.get_name()); } Ok(()) } -async fn purge_plugins(yes: bool) -> SystemResult { +pub async fn purge_plugins(yes: bool) -> SystemResult { let plugins_dir = get_plugins_dir()?; if yes @@ -227,7 +227,7 @@ async fn purge_plugins(yes: bool) -> SystemResult { fs::remove_dir_all(&plugins_dir)?; fs::create_dir_all(plugins_dir)?; - info!("Removed all downloaded plugins"); + info!("Purged all downloaded plugins"); } Ok(()) diff --git a/crates/cli/src/commands/uninstall.rs b/crates/cli/src/commands/uninstall.rs index cff093cab..0d1a54e5a 100644 --- a/crates/cli/src/commands/uninstall.rs +++ b/crates/cli/src/commands/uninstall.rs @@ -1,3 +1,4 @@ +use crate::commands::clean::purge_tool; use crate::helpers::{create_progress_bar, disable_progress_bars}; use clap::Args; use proto_core::{load_tool, Id, UnresolvedVersionSpec}; @@ -9,15 +10,26 @@ pub struct UninstallArgs { #[arg(required = true, help = "ID of tool")] id: Id, - #[arg(required = true, help = "Version or alias of tool")] - semver: UnresolvedVersionSpec, + #[arg(help = "Version or alias of tool")] + semver: Option, + + #[arg(long, help = "Avoid and force confirm prompts")] + yes: bool, } #[system] pub async fn uninstall(args: ArgsRef) { + // Uninstall everything + let Some(spec) = &args.semver else { + purge_tool(&args.id, args.yes).await?; + + return Ok(()); + }; + + // Uninstall a tool by version let mut tool = load_tool(&args.id).await?; - if !tool.is_setup(&args.semver).await? { + if !tool.is_setup(spec).await? { info!( "{} {} does not exist!", tool.get_name(), @@ -27,11 +39,7 @@ pub async fn uninstall(args: ArgsRef) { return Ok(()); } - debug!( - "Uninstalling {} with version {}", - tool.get_name(), - args.semver - ); + debug!("Uninstalling {} with version {}", tool.get_name(), spec); if tool.disable_progress_bars() { disable_progress_bars(); diff --git a/crates/cli/tests/uninstall_test.rs b/crates/cli/tests/uninstall_test.rs index 23d16a801..b12ccba5c 100644 --- a/crates/cli/tests/uninstall_test.rs +++ b/crates/cli/tests/uninstall_test.rs @@ -15,4 +15,37 @@ mod uninstall { assert.stderr(predicate::str::contains("Node.js 19.0.0 does not exist!")); } + + #[test] + fn uninstalls_by_version() { + let temp = create_empty_sandbox(); + + let mut cmd = create_proto_command(temp.path()); + cmd.arg("install").arg("node").arg("19.0.0").assert(); + + let mut cmd = create_proto_command(temp.path()); + cmd.arg("uninstall").arg("node").arg("19.0.0").assert(); + + assert!(!temp.path().join("tools/node/19.0.0").exists()); + assert!(temp.path().join("tools/node/manifest.json").exists()); + } + + #[test] + fn uninstalls_everything() { + let temp = create_empty_sandbox(); + + let mut cmd = create_proto_command(temp.path()); + cmd.arg("install").arg("node").arg("19.0.0").assert(); + + let mut cmd = create_proto_command(temp.path()); + cmd.arg("install").arg("node").arg("20.0.0").assert(); + + assert!(temp.path().join("tools/node/19.0.0").exists()); + assert!(temp.path().join("tools/node/20.0.0").exists()); + + let mut cmd = create_proto_command(temp.path()); + cmd.arg("uninstall").arg("node").arg("--yes").assert(); + + assert!(!temp.path().join("tools/node").exists()); + } } From dfc6a029074aea4360a12aca3df2410b565e49d7 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Tue, 7 Nov 2023 16:02:47 -0800 Subject: [PATCH 4/7] docs: Improve docs and testing. (#276) --- crates/system-env/src/deps.rs | 38 ++++- crates/system-env/src/env.rs | 12 ++ crates/system-env/src/pm.rs | 10 +- crates/system-env/src/pm_vendor.rs | 160 +++++++++++------- crates/system-env/src/system.rs | 33 +++- crates/version-spec/src/lib.rs | 77 ++++++++- crates/version-spec/src/resolved_spec.rs | 19 +++ crates/version-spec/src/unresolved_spec.rs | 36 +++- .../version-spec/tests/resolved_spec_test.rs | 61 +++++++ .../tests/unresolved_spec_test.rs | 100 +++++++++++ 10 files changed, 459 insertions(+), 87 deletions(-) create mode 100644 crates/version-spec/tests/resolved_spec_test.rs create mode 100644 crates/version-spec/tests/unresolved_spec_test.rs diff --git a/crates/system-env/src/deps.rs b/crates/system-env/src/deps.rs index de6feb1b6..4161a28ad 100644 --- a/crates/system-env/src/deps.rs +++ b/crates/system-env/src/deps.rs @@ -4,12 +4,19 @@ use crate::pm::*; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +/// A system dependency name in multiple formats. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[cfg_attr(feature = "schematic", derive(schematic::Schematic))] #[serde(untagged)] pub enum DependencyName { + /// A single package by name. Single(String), + + /// A single package by name, but with different names (values) + /// depending on operating system or package manager (keys). SingleMap(HashMap), + + /// Multiple packages by name. Multiple(Vec), } @@ -19,20 +26,32 @@ impl Default for DependencyName { } } +/// Configuration for one or many system dependencies (packages). #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[cfg_attr(feature = "schematic", derive(schematic::Schematic))] #[serde(default)] pub struct DependencyConfig { + /// Only install on this architecture. pub arch: Option, + + /// The dependency name or name(s) to install. pub dep: DependencyName, + + /// Only install with this package manager. pub manager: Option, - // pub optional: bool, + + /// Only install on this operating system. pub os: Option, + + /// Install using sudo. pub sudo: bool, + + /// The version to install. pub version: Option, } impl DependencyConfig { + /// Get a list of package names for hte provided OS and package manager. pub fn get_package_names( &self, os: &SystemOS, @@ -51,22 +70,33 @@ impl DependencyConfig { } } -// This shape is what users configure. +/// Represents a system dependency (one or many packages) to install. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[cfg_attr(feature = "schematic", derive(schematic::Schematic))] #[serde(untagged)] pub enum SystemDependency { + /// A single package by name. Name(String), + + /// Multiple packages by name. Names(Vec), + + /// Either a single or multiple package, defined as an + /// explicit configuration object. Config(DependencyConfig), + + /// A single package by name, but with different names (values) + /// depending on operating system or package manager (keys). Map(HashMap), } impl SystemDependency { + /// Create a single dependency by name. pub fn name(name: &str) -> SystemDependency { SystemDependency::Name(name.to_owned()) } + /// Create multiple dependencies by name. pub fn names(names: I) -> SystemDependency where I: IntoIterator, @@ -75,6 +105,7 @@ impl SystemDependency { SystemDependency::Names(names.into_iter().map(|n| n.as_ref().to_owned()).collect()) } + /// Create a single dependency by name for the target architecture. pub fn for_arch(name: &str, arch: SystemArch) -> SystemDependency { SystemDependency::Config(DependencyConfig { arch: Some(arch), @@ -83,6 +114,7 @@ impl SystemDependency { }) } + /// Create a single dependency by name for the target operating system. pub fn for_os(name: &str, os: SystemOS) -> SystemDependency { SystemDependency::Config(DependencyConfig { dep: DependencyName::Single(name.into()), @@ -91,6 +123,7 @@ impl SystemDependency { }) } + /// Create a single dependency by name for the target operating system and architecture. pub fn for_os_arch(name: &str, os: SystemOS, arch: SystemArch) -> SystemDependency { SystemDependency::Config(DependencyConfig { arch: Some(arch), @@ -100,6 +133,7 @@ impl SystemDependency { }) } + /// Convert and expand to a dependency configuration. pub fn to_config(self) -> DependencyConfig { match self { Self::Name(name) => DependencyConfig { diff --git a/crates/system-env/src/env.rs b/crates/system-env/src/env.rs index 7882664ec..28d5c3c93 100644 --- a/crates/system-env/src/env.rs +++ b/crates/system-env/src/env.rs @@ -27,11 +27,13 @@ pub enum SystemArch { } impl SystemArch { + /// Return an instance derived from [`std::env::costs::ARCH`]. pub fn from_env() -> SystemArch { serde_json::from_value(Value::String(consts::ARCH.to_owned())) .expect("Unknown architecture!") } + /// Convert to a [`std::env::costs::ARCH`] compatible string. pub fn to_rust_arch(&self) -> String { match self { Self::X64 => "x86_64".into(), @@ -79,6 +81,7 @@ pub enum SystemOS { } impl SystemOS { + /// Return an instance derived from [`std::env::costs::OS`]. pub fn from_env() -> SystemOS { serde_json::from_value(Value::String(consts::OS.to_owned())) .expect("Unknown operating system!") @@ -113,6 +116,7 @@ impl SystemOS { } } + /// Return true if in the BSD family. pub fn is_bsd(&self) -> bool { matches!( self, @@ -120,22 +124,27 @@ impl SystemOS { ) } + /// Return true if Linux. pub fn is_linux(&self) -> bool { matches!(self, Self::Linux) } + /// Return true if MacOS. pub fn is_mac(&self) -> bool { matches!(self, Self::MacOS) } + /// Return true if a Unix based OS. pub fn is_unix(&self) -> bool { self.is_bsd() || matches!(self, Self::Linux | Self::MacOS) } + /// Return true if Windows. pub fn is_windows(&self) -> bool { matches!(self, Self::Windows) } + /// Convert to a [`std::env::costs::OS`] compatible string. pub fn to_rust_os(&self) -> String { self.to_string() } @@ -159,6 +168,8 @@ impl fmt::Display for SystemOS { } } +/// Return true if the provided program (without extension) is available +/// on `PATH`. Will use `PATHEXT` to cycle through known extensions. #[cfg(windows)] pub fn is_command_on_path(name: &str) -> bool { let Ok(system_path) = env::var("PATH") else { @@ -180,6 +191,7 @@ pub fn is_command_on_path(name: &str) -> bool { false } +/// Return true if the provided command is available on `PATH`. #[cfg(not(windows))] pub fn is_command_on_path(name: &str) -> bool { let Ok(system_path) = env::var("PATH") else { diff --git a/crates/system-env/src/pm.rs b/crates/system-env/src/pm.rs index 40a2dbad5..408c72b00 100644 --- a/crates/system-env/src/pm.rs +++ b/crates/system-env/src/pm.rs @@ -3,6 +3,7 @@ use crate::pm_vendor::*; use serde::{Deserialize, Serialize}; use std::fmt; +/// Package manager of the host environment. #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] #[cfg_attr(feature = "schematic", derive(schematic::Schematic))] #[serde(rename_all = "kebab-case")] @@ -29,6 +30,12 @@ pub enum SystemPackageManager { } impl SystemPackageManager { + /// Detect the package manager from the current system environment + /// using the following rules: + /// + /// - On Linux, parses `/etc/os-release`. + /// - On MacOS and BSD, checks for commands on `PATH`. + /// - On Windows, checks for programs on `PATH`, using `PATHEXT`. pub fn detect() -> Result { #[cfg(target_os = "linux")] { @@ -90,7 +97,8 @@ impl SystemPackageManager { Err(Error::MissingPackageManager) } - pub fn get_config(&self) -> PackageVendorConfig { + /// Return vendor configuration for the current package manager. + pub fn get_config(&self) -> PackageManagerConfig { match self { Self::Apk => apk(), Self::Apt => apt(), diff --git a/crates/system-env/src/pm_vendor.rs b/crates/system-env/src/pm_vendor.rs index 9b69cc666..69b5d5c33 100644 --- a/crates/system-env/src/pm_vendor.rs +++ b/crates/system-env/src/pm_vendor.rs @@ -8,157 +8,186 @@ macro_rules! string_vec { }}; } +/// The types of commands that are currently supported by package managers. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum Command { +pub enum CommandType { + /// Installs a system dependency. InstallPackage, + + /// Updates the registry index. UpdateIndex, } +/// The CLI argument format for enabling interactive mode. #[derive(Clone, Debug)] pub enum PromptArgument { + /// Does not support interactive. None, - // -i + /// Enables interactive mode: `-i` Interactive(String), - // -y + /// Disables interactive mode: `-y` Skip(String), } +/// The CLI argument format for including the package version to install. #[derive(Clone, Debug)] pub enum VersionArgument { + /// Does not support versions. None, - // pkg=1.2.3 + /// In the same argument with the package name: `pkg=1.2.3` Inline(String), - // pkg --version 1.2.3 + /// As a separate argument: `pkg --version 1.2.3` Separate(String), } +/// Configuration for a specific package manager vendor. +/// The fields define commands and arguments for common operations. #[derive(Clone, Debug)] -pub struct PackageVendorConfig { - pub commands: HashMap>, +pub struct PackageManagerConfig { + /// Mapping of command types to CLI arguments. + pub commands: HashMap>, + + /// How interactive/prompt arguments are handled. pub prompt_arg: PromptArgument, - pub prompt_for: Vec, + + /// List of commands that support prompts. + pub prompt_for: Vec, + + /// How version arguments are handled. pub version_arg: VersionArgument, } -pub fn apk() -> PackageVendorConfig { - PackageVendorConfig { +pub(crate) fn apk() -> PackageManagerConfig { + PackageManagerConfig { commands: HashMap::from_iter([ - (Command::InstallPackage, string_vec!["apk", "add", "$"]), - (Command::UpdateIndex, string_vec!["apk", "update"]), + (CommandType::InstallPackage, string_vec!["apk", "add", "$"]), + (CommandType::UpdateIndex, string_vec!["apk", "update"]), ]), prompt_arg: PromptArgument::Interactive("-i".into()), - prompt_for: vec![Command::InstallPackage, Command::UpdateIndex], + prompt_for: vec![CommandType::InstallPackage, CommandType::UpdateIndex], version_arg: VersionArgument::Inline("=".into()), } } -pub fn apt() -> PackageVendorConfig { - PackageVendorConfig { +pub(crate) fn apt() -> PackageManagerConfig { + PackageManagerConfig { commands: HashMap::from_iter([ ( - Command::InstallPackage, + CommandType::InstallPackage, string_vec!["apt", "install", "--install-recommends", "$"], ), - (Command::UpdateIndex, string_vec!["apt", "update"]), + (CommandType::UpdateIndex, string_vec!["apt", "update"]), ]), prompt_arg: PromptArgument::Skip("-y".into()), - prompt_for: vec![Command::InstallPackage, Command::UpdateIndex], + prompt_for: vec![CommandType::InstallPackage, CommandType::UpdateIndex], version_arg: VersionArgument::Inline("=".into()), } } -pub fn brew() -> PackageVendorConfig { - PackageVendorConfig { +pub(crate) fn brew() -> PackageManagerConfig { + PackageManagerConfig { commands: HashMap::from_iter([ - (Command::InstallPackage, string_vec!["brew", "install", "$"]), - (Command::UpdateIndex, string_vec!["brew", "update"]), + ( + CommandType::InstallPackage, + string_vec!["brew", "install", "$"], + ), + (CommandType::UpdateIndex, string_vec!["brew", "update"]), ]), prompt_arg: PromptArgument::Interactive("-i".into()), - prompt_for: vec![Command::InstallPackage], + prompt_for: vec![CommandType::InstallPackage], version_arg: VersionArgument::Inline("@".into()), } } -pub fn choco() -> PackageVendorConfig { - PackageVendorConfig { +pub(crate) fn choco() -> PackageManagerConfig { + PackageManagerConfig { commands: HashMap::from_iter([( - Command::InstallPackage, + CommandType::InstallPackage, string_vec!["choco", "install", "$"], )]), prompt_arg: PromptArgument::Skip("-y".into()), - prompt_for: vec![Command::InstallPackage], + prompt_for: vec![CommandType::InstallPackage], version_arg: VersionArgument::Separate("--version".into()), } } -pub fn dnf() -> PackageVendorConfig { - PackageVendorConfig { +pub(crate) fn dnf() -> PackageManagerConfig { + PackageManagerConfig { commands: HashMap::from_iter([ - (Command::InstallPackage, string_vec!["dnf", "install", "$"]), - (Command::UpdateIndex, string_vec!["dnf", "check-update"]), + ( + CommandType::InstallPackage, + string_vec!["dnf", "install", "$"], + ), + (CommandType::UpdateIndex, string_vec!["dnf", "check-update"]), ]), prompt_arg: PromptArgument::Skip("-y".into()), - prompt_for: vec![Command::InstallPackage, Command::UpdateIndex], + prompt_for: vec![CommandType::InstallPackage, CommandType::UpdateIndex], version_arg: VersionArgument::Inline("-".into()), } } -pub fn pacman() -> PackageVendorConfig { - PackageVendorConfig { +pub(crate) fn pacman() -> PackageManagerConfig { + PackageManagerConfig { commands: HashMap::from_iter([ - (Command::InstallPackage, string_vec!["pacman", "-S", "$"]), - (Command::UpdateIndex, string_vec!["pacman", "-Syy"]), + ( + CommandType::InstallPackage, + string_vec!["pacman", "-S", "$"], + ), + (CommandType::UpdateIndex, string_vec!["pacman", "-Syy"]), ]), prompt_arg: PromptArgument::Skip("--noconfirm".into()), - prompt_for: vec![Command::InstallPackage], + prompt_for: vec![CommandType::InstallPackage], version_arg: VersionArgument::Inline(">=".into()), } } -pub fn pkg() -> PackageVendorConfig { - PackageVendorConfig { +pub(crate) fn pkg() -> PackageManagerConfig { + PackageManagerConfig { commands: HashMap::from_iter([ - (Command::InstallPackage, string_vec!["pkg", "install", "$"]), - (Command::UpdateIndex, string_vec!["pkg", "update"]), + ( + CommandType::InstallPackage, + string_vec!["pkg", "install", "$"], + ), + (CommandType::UpdateIndex, string_vec!["pkg", "update"]), ]), prompt_arg: PromptArgument::Skip("-y".into()), - prompt_for: vec![Command::InstallPackage], + prompt_for: vec![CommandType::InstallPackage], version_arg: VersionArgument::None, } } -pub fn pkg_alt() -> PackageVendorConfig { - PackageVendorConfig { - commands: HashMap::from_iter([(Command::InstallPackage, string_vec!["pkg_add", "$"])]), - prompt_arg: PromptArgument::Skip("-I".into()), - prompt_for: vec![Command::InstallPackage], - version_arg: VersionArgument::None, - } -} +// pub(crate) fn pkg_alt() -> PackageVendorConfig { +// PackageVendorConfig { +// commands: HashMap::from_iter([(Command::InstallPackage, string_vec!["pkg_add", "$"])]), +// prompt_arg: PromptArgument::Skip("-I".into()), +// prompt_for: vec![Command::InstallPackage], +// version_arg: VersionArgument::None, +// } +// } -pub fn pkgin() -> PackageVendorConfig { - PackageVendorConfig { +pub(crate) fn pkgin() -> PackageManagerConfig { + PackageManagerConfig { commands: HashMap::from_iter([ ( - Command::InstallPackage, + CommandType::InstallPackage, string_vec!["pkgin", "install", "$"], ), - (Command::UpdateIndex, string_vec!["pkgin", "update"]), + (CommandType::UpdateIndex, string_vec!["pkgin", "update"]), ]), prompt_arg: PromptArgument::Skip("-y".into()), - prompt_for: vec![Command::InstallPackage, Command::UpdateIndex], + prompt_for: vec![CommandType::InstallPackage, CommandType::UpdateIndex], version_arg: VersionArgument::Inline("-".into()), } } -pub fn scoop() -> PackageVendorConfig { - PackageVendorConfig { +pub(crate) fn scoop() -> PackageManagerConfig { + PackageManagerConfig { commands: HashMap::from_iter([ ( - Command::InstallPackage, + CommandType::InstallPackage, string_vec!["scoop", "install", "$"], ), - (Command::UpdateIndex, string_vec!["scoop", "update"]), + (CommandType::UpdateIndex, string_vec!["scoop", "update"]), ]), prompt_arg: PromptArgument::None, prompt_for: vec![], @@ -166,14 +195,17 @@ pub fn scoop() -> PackageVendorConfig { } } -pub fn yum() -> PackageVendorConfig { - PackageVendorConfig { +pub(crate) fn yum() -> PackageManagerConfig { + PackageManagerConfig { commands: HashMap::from_iter([ - (Command::InstallPackage, string_vec!["yum", "install", "$"]), - (Command::UpdateIndex, string_vec!["yum", "check-update"]), + ( + CommandType::InstallPackage, + string_vec!["yum", "install", "$"], + ), + (CommandType::UpdateIndex, string_vec!["yum", "check-update"]), ]), prompt_arg: PromptArgument::Skip("-y".into()), - prompt_for: vec![Command::InstallPackage], + prompt_for: vec![CommandType::InstallPackage], version_arg: VersionArgument::Inline("-".into()), } } diff --git a/crates/system-env/src/system.rs b/crates/system-env/src/system.rs index 49f3429cf..8c1e526ed 100644 --- a/crates/system-env/src/system.rs +++ b/crates/system-env/src/system.rs @@ -4,17 +4,26 @@ use crate::error::Error; use crate::pm::*; use crate::pm_vendor::*; +/// Represents the current system, including architecture, operating system, +/// and package manager information. pub struct System { + /// Platform architecture. pub arch: SystemArch, + + /// Package manager. pub manager: SystemPackageManager, + + /// Operating system. pub os: SystemOS, } impl System { + /// Create a new instance and detect system information. pub fn new() -> Result { Ok(System::with_manager(SystemPackageManager::detect()?)) } + /// Create a new instance with the provided package manager. pub fn with_manager(manager: SystemPackageManager) -> Self { System { arch: SystemArch::from_env(), @@ -23,6 +32,9 @@ impl System { } } + /// Return the command and arguments to "install a package" for the + /// current package manager. Will replace `$` in an argument with the + /// dependency name, derived from [`DependencyName`]. pub fn get_install_package_command( &self, dep_config: &DependencyConfig, @@ -35,7 +47,7 @@ impl System { for arg in pm_config .commands - .get(&Command::InstallPackage) + .get(&CommandType::InstallPackage) .cloned() .unwrap() { @@ -64,18 +76,25 @@ impl System { } } - self.append_interactive(Command::InstallPackage, &pm_config, &mut args, interactive); + self.append_interactive( + CommandType::InstallPackage, + &pm_config, + &mut args, + interactive, + ); Ok(args) } + /// Return the command and arguments to "update the registry index" + /// for the current package manager. pub fn get_update_index_command(&self, interactive: bool) -> Option> { let pm_config = self.manager.get_config(); - if let Some(args) = pm_config.commands.get(&Command::UpdateIndex) { + if let Some(args) = pm_config.commands.get(&CommandType::UpdateIndex) { let mut args = args.to_owned(); - self.append_interactive(Command::UpdateIndex, &pm_config, &mut args, interactive); + self.append_interactive(CommandType::UpdateIndex, &pm_config, &mut args, interactive); return Some(args); } @@ -83,6 +102,8 @@ impl System { None } + /// Resolve and reduce the dependencies to a list that's applicable + /// to the current system. pub fn resolve_dependencies(&self, deps: Vec) -> Vec { let mut configs = vec![]; @@ -105,8 +126,8 @@ impl System { fn append_interactive( &self, - command: Command, - config: &PackageVendorConfig, + command: CommandType, + config: &PackageManagerConfig, args: &mut Vec, interactive: bool, ) { diff --git a/crates/version-spec/src/lib.rs b/crates/version-spec/src/lib.rs index 1f76cf01f..42dfb43dc 100644 --- a/crates/version-spec/src/lib.rs +++ b/crates/version-spec/src/lib.rs @@ -1,12 +1,14 @@ mod resolved_spec; mod unresolved_spec; -use regex::Regex; - pub use resolved_spec::*; pub use unresolved_spec::*; -/// Aliases are words that map to version. For example, "latest" -> "1.2.3". +/// Returns true if the provided value is an alias. An alias is a word that +/// maps to version, for example, "latest" -> "1.2.3". +/// +/// Is considered an alias if the string is alpha-numeric, starts with a +/// character, and supports `-`, `_`, `/`, `.`, and `*` characters. pub fn is_alias_name>(value: T) -> bool { let value = value.as_ref(); @@ -24,19 +26,35 @@ pub fn is_alias_name>(value: T) -> bool { }) } +/// Cleans a potential version string by removing a leading `v` or `V`, +/// removing each occurence of `.*`, and removing invalid spaces. pub fn clean_version_string>(value: T) -> String { - let value = value.as_ref().trim().replace(".*", ""); - let mut version = value.as_str(); + let value = value.as_ref().trim(); + + if value.contains("||") { + return value + .split("||") + .map(clean_version_string) + .collect::>() + .join(" || "); + } + + let mut version = value.replace(".*", "").replace("&&", ","); // Remove a leading "v" or "V" from a version string. if version.starts_with('v') || version.starts_with('V') { - version = &version[1..]; + version = version[1..].to_owned(); } // Remove invalid space after <, <=, >, >=. - Regex::new(r"([><]=?)[ ]+([0-9])") + let version = regex::Regex::new(r"([><]=?)[ ]+([0-9])") .unwrap() - .replace_all(version, "$1$2") + .replace_all(&version, "$1$2"); + + // Replace spaces with commas + regex::Regex::new("[, ]+") + .unwrap() + .replace_all(&version, ",") .to_string() } @@ -44,11 +62,54 @@ pub fn clean_version_string>(value: T) -> String { mod tests { use super::*; + #[test] + fn checks_alias() { + assert!(is_alias_name("foo")); + assert!(is_alias_name("foo.bar")); + assert!(is_alias_name("foo/bar")); + assert!(is_alias_name("foo-bar")); + assert!(is_alias_name("foo_bar-baz")); + assert!(is_alias_name("alpha.1")); + assert!(is_alias_name("beta-0")); + assert!(is_alias_name("rc-1.2.3")); + assert!(is_alias_name("next-2023")); + + assert!(!is_alias_name("1.2.3")); + assert!(!is_alias_name("1.2")); + assert!(!is_alias_name("1")); + assert!(!is_alias_name("1-3")); + } + #[test] fn cleans_string() { + assert_eq!(clean_version_string("v1.2.3"), "1.2.3"); + assert_eq!(clean_version_string("V1.2.3"), "1.2.3"); + + assert_eq!(clean_version_string("1.2.*"), "1.2"); + assert_eq!(clean_version_string("1.*.*"), "1"); + assert_eq!(clean_version_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"); + + assert_eq!(clean_version_string("1.2, 3"), "1.2,3"); + assert_eq!(clean_version_string("1,3, 4"), "1,3,4"); + assert_eq!(clean_version_string("1 2"), "1,2"); + assert_eq!(clean_version_string("1 && 2"), "1,2"); + } + + #[test] + fn handles_commas() { + assert_eq!(clean_version_string("1 2"), "1,2"); + assert_eq!(clean_version_string("1 2"), "1,2"); + assert_eq!(clean_version_string("1 2"), "1,2"); + assert_eq!(clean_version_string("1,2"), "1,2"); + assert_eq!(clean_version_string("1 ,2"), "1,2"); + assert_eq!(clean_version_string("1, 2"), "1,2"); + assert_eq!(clean_version_string("1 , 2"), "1,2"); + assert_eq!(clean_version_string("1 , 2"), "1,2"); + assert_eq!(clean_version_string("1, 2"), "1,2"); } } diff --git a/crates/version-spec/src/resolved_spec.rs b/crates/version-spec/src/resolved_spec.rs index 0482cfeae..56bf3a580 100644 --- a/crates/version-spec/src/resolved_spec.rs +++ b/crates/version-spec/src/resolved_spec.rs @@ -6,19 +6,30 @@ use serde::{Deserialize, Serialize}; use std::fmt::{Debug, Display}; use std::str::FromStr; +/// Represents a resolved version or alias. #[derive(Clone, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] #[serde(untagged, into = "String", try_from = "String")] pub enum VersionSpec { + /// A special canary target. Canary, + /// An alias that is used as a map to a version. Alias(String), + /// A fully-qualified semantic version. Version(Version), } impl VersionSpec { + /// Parse the provided string into a resolved specification based + /// on the following rules, in order: + /// + /// - If the value "canary", map as `Canary` variant. + /// - If an alpha-numeric value that starts with a character, map as `Alias`. + /// - Else parse with [`Version`], and map as `Version`. pub fn parse>(value: T) -> Result { Self::from_str(value.as_ref()) } + /// Return true if the provided alias matches the current specification. pub fn is_alias>(&self, name: A) -> bool { match self { Self::Alias(alias) => alias == name.as_ref(), @@ -26,6 +37,7 @@ impl VersionSpec { } } + /// Return true if the current specification is canary. pub fn is_canary(&self) -> bool { match self { Self::Canary => true, @@ -34,6 +46,7 @@ impl VersionSpec { } } + /// Return true if the current specification is the "latest" or "stable" alias. pub fn is_latest(&self) -> bool { match self { Self::Alias(alias) => alias == "latest" || alias == "stable", @@ -41,6 +54,7 @@ impl VersionSpec { } } + /// Convert the current resolved specification to an unresolved specification. pub fn to_unresolved_spec(&self) -> UnresolvedVersionSpec { match self { Self::Canary => UnresolvedVersionSpec::Canary, @@ -51,6 +65,7 @@ impl VersionSpec { } impl Default for VersionSpec { + /// Returns a `latest` alias. fn default() -> Self { Self::Alias("latest".into()) } @@ -60,6 +75,10 @@ impl FromStr for VersionSpec { type Err = Error; fn from_str(value: &str) -> Result { + if value == "canary" { + return Ok(VersionSpec::Canary); + } + let value = clean_version_string(value); if is_alias_name(&value) { diff --git a/crates/version-spec/src/unresolved_spec.rs b/crates/version-spec/src/unresolved_spec.rs index f87e7d7de..237f85da6 100644 --- a/crates/version-spec/src/unresolved_spec.rs +++ b/crates/version-spec/src/unresolved_spec.rs @@ -7,21 +7,40 @@ use serde::{Deserialize, Serialize}; use std::fmt::{Debug, Display}; use std::str::FromStr; +/// Represents an unresolved version or alias that must be resolved +/// to a fully-qualified and semantic result. #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] #[serde(untagged, into = "String", try_from = "String")] pub enum UnresolvedVersionSpec { + /// A special canary target. Canary, + /// An alias that is used as a map to a version. Alias(String), + /// A partial version, requirement, or range (`^`, `~`, etc). Req(VersionReq), + /// A list of requirements to match any against (joined by `||`). ReqAny(Vec), + /// A fully-qualified semantic version. Version(Version), } impl UnresolvedVersionSpec { + /// Parse the provided string into an unresolved specification based + /// on the following rules, in order: + /// + /// - If the value "canary", map as `Canary` variant. + /// - If an alpha-numeric value that starts with a character, map as `Alias`. + /// - If contains `||`, split and parse each item with [`VersionReq`], + /// and map as `ReqAny`. + /// - If contains `,` or ` ` (space), parse with [`VersionReq`], and map as `Req`. + /// - If starts with `=`, `^`, `~`, `>`, `<`, or `*`, parse with [`VersionReq`], + /// and map as `Req`. + /// - Else parse with [`Version`], and map as `Version`. pub fn parse>(value: T) -> Result { Self::from_str(value.as_ref()) } + /// Return true if the provided alias matches the current specification. pub fn is_alias>(&self, name: A) -> bool { match self { Self::Alias(alias) => alias == name.as_ref(), @@ -29,6 +48,7 @@ impl UnresolvedVersionSpec { } } + /// Return true if the current specification is canary. pub fn is_canary(&self) -> bool { match self { Self::Canary => true, @@ -37,6 +57,7 @@ impl UnresolvedVersionSpec { } } + /// Return true if the current specification is the "latest" or "stable" alias. pub fn is_latest(&self) -> bool { match self { Self::Alias(alias) => alias == "latest" || alias == "stable", @@ -44,6 +65,12 @@ impl UnresolvedVersionSpec { } } + /// Convert the current unresolved specification to a resolved specification. + /// Note that this *does not* actually resolve or validate against a manifest, + /// and instead simply constructs the [`VersionSpec`]. + /// + /// Furthermore, the `Req` and `ReqAny` variants will panic, as they are not + /// resolved or valid versions. pub fn to_resolved_spec(&self) -> VersionSpec { match self { Self::Canary => VersionSpec::Canary, @@ -55,6 +82,7 @@ impl UnresolvedVersionSpec { } impl Default for UnresolvedVersionSpec { + /// Returns a `latest` alias. fn default() -> Self { Self::Alias("latest".into()) } @@ -64,12 +92,12 @@ impl FromStr for UnresolvedVersionSpec { type Err = Error; fn from_str(value: &str) -> Result { - let value = clean_version_string(value); - if value == "canary" { return Ok(UnresolvedVersionSpec::Canary); } + let value = clean_version_string(value); + if is_alias_name(&value) { return Ok(UnresolvedVersionSpec::Alias(value)); } @@ -92,10 +120,6 @@ impl FromStr for UnresolvedVersionSpec { // AND requirements if value.contains(',') { return Ok(UnresolvedVersionSpec::Req(VersionReq::parse(&value)?)); - } else if value.contains(' ') { - return Ok(UnresolvedVersionSpec::Req(VersionReq::parse( - &value.replace(' ', ", "), - )?)); } Ok(match value.chars().next().unwrap() { diff --git a/crates/version-spec/tests/resolved_spec_test.rs b/crates/version-spec/tests/resolved_spec_test.rs new file mode 100644 index 000000000..f23a9f14c --- /dev/null +++ b/crates/version-spec/tests/resolved_spec_test.rs @@ -0,0 +1,61 @@ +use semver::Version; +use version_spec::VersionSpec; + +mod resolved_spec { + use super::*; + + #[test] + fn canary() { + assert_eq!(VersionSpec::parse("canary").unwrap(), VersionSpec::Canary); + } + + #[test] + fn aliases() { + assert_eq!( + VersionSpec::parse("latest").unwrap(), + VersionSpec::Alias("latest".to_owned()) + ); + assert_eq!( + VersionSpec::parse("stable").unwrap(), + VersionSpec::Alias("stable".to_owned()) + ); + assert_eq!( + VersionSpec::parse("legacy-2023").unwrap(), + VersionSpec::Alias("legacy-2023".to_owned()) + ); + assert_eq!( + VersionSpec::parse("future/202x").unwrap(), + VersionSpec::Alias("future/202x".to_owned()) + ); + } + + #[test] + fn versions() { + assert_eq!( + VersionSpec::parse("v1.2.3").unwrap(), + VersionSpec::Version(Version::new(1, 2, 3)) + ); + assert_eq!( + VersionSpec::parse("1.2.3").unwrap(), + VersionSpec::Version(Version::new(1, 2, 3)) + ); + } + + #[test] + #[should_panic(expected = "unexpected end of input while parsing minor version number")] + fn error_missing_patch() { + VersionSpec::parse("1.2").unwrap(); + } + + #[test] + #[should_panic(expected = "unexpected end of input while parsing major version number")] + fn error_missing_minor() { + VersionSpec::parse("1").unwrap(); + } + + #[test] + #[should_panic(expected = "unexpected character '%' while parsing major version number")] + fn error_invalid_char() { + VersionSpec::parse("%").unwrap(); + } +} diff --git a/crates/version-spec/tests/unresolved_spec_test.rs b/crates/version-spec/tests/unresolved_spec_test.rs new file mode 100644 index 000000000..df96a24b8 --- /dev/null +++ b/crates/version-spec/tests/unresolved_spec_test.rs @@ -0,0 +1,100 @@ +use semver::Version; +use version_spec::UnresolvedVersionSpec; + +mod unresolved_spec { + use semver::VersionReq; + + use super::*; + + #[test] + fn canary() { + assert_eq!( + UnresolvedVersionSpec::parse("canary").unwrap(), + UnresolvedVersionSpec::Canary + ); + } + + #[test] + fn aliases() { + assert_eq!( + UnresolvedVersionSpec::parse("latest").unwrap(), + UnresolvedVersionSpec::Alias("latest".to_owned()) + ); + assert_eq!( + UnresolvedVersionSpec::parse("stable").unwrap(), + UnresolvedVersionSpec::Alias("stable".to_owned()) + ); + assert_eq!( + UnresolvedVersionSpec::parse("legacy-2023").unwrap(), + UnresolvedVersionSpec::Alias("legacy-2023".to_owned()) + ); + assert_eq!( + UnresolvedVersionSpec::parse("future/202x").unwrap(), + UnresolvedVersionSpec::Alias("future/202x".to_owned()) + ); + } + + #[test] + fn versions() { + assert_eq!( + UnresolvedVersionSpec::parse("v1.2.3").unwrap(), + UnresolvedVersionSpec::Version(Version::new(1, 2, 3)) + ); + assert_eq!( + UnresolvedVersionSpec::parse("1.2.3").unwrap(), + UnresolvedVersionSpec::Version(Version::new(1, 2, 3)) + ); + } + + #[test] + fn requirements() { + assert_eq!( + UnresolvedVersionSpec::parse("1.2").unwrap(), + UnresolvedVersionSpec::Req(VersionReq::parse("~1.2").unwrap()) + ); + assert_eq!( + UnresolvedVersionSpec::parse("1").unwrap(), + UnresolvedVersionSpec::Req(VersionReq::parse("~1").unwrap()) + ); + assert_eq!( + UnresolvedVersionSpec::parse("1.2.*").unwrap(), + UnresolvedVersionSpec::Req(VersionReq::parse("~1.2").unwrap()) + ); + assert_eq!( + UnresolvedVersionSpec::parse("1.*").unwrap(), + UnresolvedVersionSpec::Req(VersionReq::parse("~1").unwrap()) + ); + assert_eq!( + UnresolvedVersionSpec::parse(">1").unwrap(), + UnresolvedVersionSpec::Req(VersionReq::parse(">1").unwrap()) + ); + assert_eq!( + UnresolvedVersionSpec::parse("<=1").unwrap(), + UnresolvedVersionSpec::Req(VersionReq::parse("<=1").unwrap()) + ); + assert_eq!( + UnresolvedVersionSpec::parse("1, 2").unwrap(), + UnresolvedVersionSpec::Req(VersionReq::parse("1, 2").unwrap()) + ); + assert_eq!( + UnresolvedVersionSpec::parse("1,2").unwrap(), + UnresolvedVersionSpec::Req(VersionReq::parse("1,2").unwrap()) + ); + assert_eq!( + UnresolvedVersionSpec::parse("1 2").unwrap(), + UnresolvedVersionSpec::Req(VersionReq::parse("1, 2").unwrap()) + ); + } + + #[test] + fn any_requirements() { + assert_eq!( + UnresolvedVersionSpec::parse("^1.2 || ~1 || 3,4").unwrap(), + UnresolvedVersionSpec::ReqAny(vec![ + VersionReq::parse("~1").unwrap(), + VersionReq::parse("^1.2").unwrap(), + VersionReq::parse("3,4").unwrap(), + ]) + ); + } +} From e4bfcafc49b701a6ef43c1fcd445fbe249374865 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Tue, 7 Nov 2023 21:39:38 -0800 Subject: [PATCH 5/7] internal: Bring back parent executable concept. (#277) --- CHANGELOG.md | 4 +- Cargo.lock | 32 ++--- Cargo.toml | 4 +- crates/cli/Cargo.toml | 6 +- crates/cli/src/commands/run.rs | 130 +++++++++++++----- crates/core/Cargo.toml | 8 +- crates/core/src/shimmer.rs | 4 +- crates/core/src/tool.rs | 31 ++++- crates/core/templates/bash_global.tpl | 4 +- crates/core/templates/cmd_global.tpl | 4 +- crates/core/templates/pwsh_global.tpl | 4 +- crates/core/tests/shimmer_test.rs | 10 +- .../shimmer_test__shimmer__alt_global.snap | 4 +- ...r_test__shimmer__alt_global_with_args.snap | 4 +- crates/pdk-api/Cargo.toml | 8 +- crates/pdk-api/src/api.rs | 20 ++- crates/pdk-test-utils/Cargo.toml | 6 +- crates/pdk/Cargo.toml | 4 +- crates/system-env/Cargo.toml | 2 +- crates/version-spec/Cargo.toml | 2 +- crates/warpgate-api/Cargo.toml | 2 +- crates/warpgate/Cargo.toml | 4 +- 22 files changed, 197 insertions(+), 100 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29dfb4ae9..b10189727 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,9 +45,7 @@ - **Node** - Updated the `npm` tool to create the `npx` shim instead of the `node` tool. - - Updated executable detection for package managers to use the shell scripts instead of the source `.js` files (when applicable). - - Previously we would execute the JS file with node: `node ./bin/npm-cli.js` - - Now we execute the shell script: `./bin/npm` (unix), `./bin/npm.cmd` (windows) + - Updated symlinked binaries to use the shell scripts instead of the source `.js` files (when applicable). #### ⚙️ Internal diff --git a/Cargo.lock b/Cargo.lock index 50077a1c0..92efe9523 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -296,9 +296,9 @@ checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cached" -version = "0.46.0" +version = "0.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cead8ece0da6b744b2ad8ef9c58a4cdc7ef2921e60a6ddfb9eaaa86839b5fc5" +checksum = "c7c8c50262271cdf5abc979a5f76515c234e764fa025d1ba4862c0f0bcda0e95" dependencies = [ "ahash", "cached_proc_macro", @@ -311,9 +311,9 @@ dependencies = [ [[package]] name = "cached_proc_macro" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8245dd5f576a41c3b76247b54c15b0e43139ceeb4f732033e15be7c005176" +checksum = "c878c71c2821aa2058722038a59a67583a4240524687c6028571c9b395ded61f" dependencies = [ "darling 0.14.4", "proc-macro2", @@ -2333,7 +2333,7 @@ dependencies = [ [[package]] name = "proto_core" -version = "0.22.1" +version = "0.22.3" dependencies = [ "cached", "extism", @@ -2363,7 +2363,7 @@ dependencies = [ [[package]] name = "proto_pdk" -version = "0.10.1" +version = "0.10.2" dependencies = [ "anyhow", "extism-pdk", @@ -2373,7 +2373,7 @@ dependencies = [ [[package]] name = "proto_pdk_api" -version = "0.10.1" +version = "0.10.2" dependencies = [ "anyhow", "semver", @@ -2387,7 +2387,7 @@ dependencies = [ [[package]] name = "proto_pdk_test_utils" -version = "0.10.1" +version = "0.10.2" dependencies = [ "extism", "proto_core", @@ -2862,9 +2862,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.190" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" dependencies = [ "serde_derive", ] @@ -2881,9 +2881,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.190" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", @@ -3260,7 +3260,7 @@ dependencies = [ [[package]] name = "system_env" -version = "0.1.3" +version = "0.1.4" dependencies = [ "schematic", "serde", @@ -3795,7 +3795,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "version_spec" -version = "0.1.4" +version = "0.1.5" dependencies = [ "human-sort", "regex", @@ -3834,7 +3834,7 @@ dependencies = [ [[package]] name = "warpgate" -version = "0.5.13" +version = "0.5.14" dependencies = [ "extism", "miette", @@ -3857,7 +3857,7 @@ dependencies = [ [[package]] name = "warpgate_api" -version = "0.1.4" +version = "0.1.5" dependencies = [ "serde", ] diff --git a/Cargo.toml b/Cargo.toml index 278fb3c26..d137a16bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ members = ["crates/*"] default-members = ["crates/cli"] [workspace.dependencies] -cached = "0.46.0" +cached = "0.46.1" clap = "4.4.7" clap_complete = "4.4.4" convert_case = "0.6.0" @@ -20,7 +20,7 @@ schematic = { version = "0.12.7", default-features = false, features = [ "schema", ] } semver = "1.0.20" -serde = { version = "1.0.190", features = ["derive"] } +serde = { version = "1.0.192", features = ["derive"] } serde_json = "1.0.108" sha2 = "0.10.8" starbase = "0.2.9" diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index a15b80550..0f375ecde 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.22.1", path = "../core" } -proto_pdk_api = { version = "0.10.1", path = "../pdk-api" } -system_env = { version = "0.1.3", path = "../system-env" } +proto_core = { version = "0.22.3", path = "../core" } +proto_pdk_api = { version = "0.10.2", path = "../pdk-api" } +system_env = { version = "0.1.4", path = "../system-env" } chrono = "0.4.31" clap = { workspace = true, features = ["derive", "env"] } clap_complete = { workspace = true } diff --git a/crates/cli/src/commands/run.rs b/crates/cli/src/commands/run.rs index 39c2deb7f..85184e212 100644 --- a/crates/cli/src/commands/run.rs +++ b/crates/cli/src/commands/run.rs @@ -3,10 +3,9 @@ use crate::error::ProtoCliError; use clap::Args; use miette::IntoDiagnostic; use proto_core::{detect_version, load_tool, Id, ProtoError, Tool, UnresolvedVersionSpec}; -use proto_pdk_api::RunHook; +use proto_pdk_api::{ExecutableConfig, 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; @@ -20,7 +19,10 @@ pub struct RunArgs { #[arg(help = "Version or alias of tool")] spec: Option, - #[arg(long, help = "Path to a relative alternate binary to run")] + #[arg(long, help = "Name of an alternate (secondary) binary to run")] + alt: Option, + + #[arg(long, help = "Path to a tool directory relative file to run")] bin: Option, // Passthrough args (after --) @@ -49,7 +51,75 @@ fn is_trying_to_self_upgrade(tool: &Tool, args: &[String]) -> bool { false } -fn create_command(exe_path: &Path, args: &[String]) -> Command { +fn get_executable(tool: &Tool, args: &RunArgs) -> miette::Result { + let tool_dir = tool.get_tool_dir(); + + // Run a file relative from the tool directory + if let Some(alt_bin) = &args.bin { + let alt_path = tool_dir.join(alt_bin); + + debug!(bin = alt_bin, path = ?alt_path, "Received a relative binary to run with"); + + if alt_path.exists() { + return Ok(ExecutableConfig { + exe_path: Some(alt_path), + ..ExecutableConfig::default() + }); + } else { + return Err(ProtoCliError::MissingRunAltBin { + bin: alt_bin.to_owned(), + path: alt_path, + } + .into()); + } + } + + // Run an alternate executable (via shim) + if let Some(alt_name) = &args.alt { + for location in tool.get_shim_locations()? { + if location.name == *alt_name { + let Some(exe_path) = &location.config.exe_path else { + continue; + }; + + let alt_path = tool_dir.join(exe_path); + + if alt_path.exists() { + debug!( + bin = alt_name, + path = ?alt_path, + "Received an alternate binary to run with", + ); + + return Ok(ExecutableConfig { + exe_path: Some(alt_path), + ..location.config + }); + } + } + } + + return Err(ProtoCliError::MissingRunAltBin { + bin: alt_name.to_owned(), + path: tool_dir, + } + .into()); + } + + // Otherwise use the primary + let mut config = tool + .get_exe_location()? + .expect("Required executable information missing!") + .config; + + config.exe_path = Some(tool_dir.join(config.exe_path.as_ref().unwrap())); + + Ok(config) +} + +fn create_command(exe_config: &ExecutableConfig, args: &[String]) -> Command { + let exe_path = exe_config.exe_path.as_ref().unwrap(); + 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") { @@ -58,17 +128,28 @@ fn create_command(exe_path: &Path, args: &[String]) -> Command { "powershell" }); cmd.arg("-Command"); - cmd.arg(format!( - "{} {}", - exe_path.display(), - shell_words::join(args) - )); + cmd.arg( + format!( + "{} {} {}", + exe_config.parent_exe_name.clone().unwrap_or_default(), + exe_path.display(), + shell_words::join(args) + ) + .trim(), + ); cmd } _ => { - let mut cmd = Command::new(exe_path); - cmd.args(args); - cmd + if let Some(parent_exe) = &exe_config.parent_exe_name { + let mut cmd = Command::new(parent_exe); + cmd.arg(exe_path); + cmd.args(args); + cmd + } else { + let mut cmd = Command::new(exe_path); + cmd.args(args); + cmd + } } } } @@ -117,26 +198,9 @@ pub async fn run(args: ArgsRef) -> SystemResult { } // Determine the binary path to execute - let exe_path = match &args.bin { - Some(alt_bin) => { - let alt_path = tool.get_tool_dir().join(alt_bin); - - debug!(bin = alt_bin, path = ?alt_path, "Received an alternate binary to run with"); - - if alt_path.exists() { - alt_path - } else { - return Err(ProtoCliError::MissingRunAltBin { - bin: alt_bin.to_owned(), - path: alt_path, - } - .into()); - } - } - None => tool.get_exe_path()?.to_path_buf(), - }; + let exe_config = get_executable(&tool, args)?; - debug!(bin = ?exe_path, args = ?args.passthrough, "Running {}", tool.get_name()); + debug!(bin = ?exe_config.exe_path, args = ?args.passthrough, "Running {}", tool.get_name()); // Run before hook tool.run_hook("pre_run", || RunHook { @@ -145,14 +209,14 @@ pub async fn run(args: ArgsRef) -> SystemResult { })?; // Run the command - let status = create_command(&exe_path, &args.passthrough) + let status = create_command(&exe_config, &args.passthrough) .env( format!("{}_VERSION", tool.get_env_var_prefix()), tool.get_resolved_version().to_string(), ) .env( format!("{}_BIN", tool.get_env_var_prefix()), - exe_path.to_string_lossy().to_string(), + exe_config.exe_path.unwrap().to_string_lossy().to_string(), ) .spawn() .into_diagnostic()? diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 082e338d7..7282aa588 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proto_core" -version = "0.22.1" +version = "0.22.3" edition = "2021" license = "MIT" description = "Core proto APIs." @@ -8,9 +8,9 @@ homepage = "https://moonrepo.dev/proto" repository = "https://github.com/moonrepo/proto" [dependencies] -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" } +proto_pdk_api = { version = "0.10.2", path = "../pdk-api" } +version_spec = { version = "0.1.5", path = "../version-spec" } +warpgate = { version = "0.5.14", path = "../warpgate" } cached = { workspace = true } extism = { workspace = true } human-sort = { workspace = true } diff --git a/crates/core/src/shimmer.rs b/crates/core/src/shimmer.rs index b8a123901..c675475ca 100644 --- a/crates/core/src/shimmer.rs +++ b/crates/core/src/shimmer.rs @@ -16,8 +16,8 @@ pub struct ShimContext<'tool> { /// 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. Uses `proto run --bin`. - pub bin_path: Option, + /// Name of the alternate binary to execute. Uses `proto run --alt`. + pub alt_bin: Option<&'tool str>, /// Args to prepend to user-provided args. pub before_args: Option<&'tool str>, diff --git a/crates/core/src/tool.rs b/crates/core/src/tool.rs index 3a42c1846..cbbacf52c 100644 --- a/crates/core/src/tool.rs +++ b/crates/core/src/tool.rs @@ -25,10 +25,10 @@ use std::path::{Path, PathBuf}; use std::process::Command; use std::sync::Arc; use std::time::{Duration, SystemTime}; -use tracing::{debug, trace}; +use tracing::{debug, trace, warn}; use warpgate::{download_from_url_to_file, Id, PluginContainer, PluginLocator, VirtualPath}; -#[derive(Debug)] +#[derive(Debug, Default)] pub struct ExecutableLocation { pub config: ExecutableConfig, pub name: String, @@ -1188,7 +1188,7 @@ impl Tool { let mut add = |name: &str, config: ExecutableConfig, primary: bool| { if !config.no_bin { - if let Some(exe_path) = &config.exe_path { + if let Some(exe_path) = config.exe_link_path.as_ref().or(config.exe_path.as_ref()) { locations.push(ExecutableLocation { path: self.proto.bin_dir.join(match exe_path.extension() { Some(ext) => format!("{name}.{}", ext.to_string_lossy()), @@ -1378,8 +1378,9 @@ impl Tool { context.before_args = location.config.shim_before_args.as_deref(); context.after_args = location.config.shim_after_args.as_deref(); - if !location.primary { - context.bin_path = location.config.exe_path; + // Only use --alt when the secondary executable exists + if !location.primary && location.config.exe_path.is_some() { + context.alt_bin = Some(&location.name); } context.create_shim(&location.path, find_only)?; @@ -1414,9 +1415,27 @@ impl Tool { let mut event = CreatedBinariesEvent { bins: vec![] }; for location in bins { - let input_path = tool_dir.join(location.config.exe_path.unwrap()); + let input_path = tool_dir.join( + location + .config + .exe_link_path + .as_ref() + .or(location.config.exe_path.as_ref()) + .unwrap(), + ); let output_path = location.path; + if !input_path.exists() { + warn!( + tool = self.id.as_str(), + source = ?input_path, + target = ?output_path, + "Unable to symlink binary, source file does not exist" + ); + + continue; + } + if output_path.exists() && !force { continue; } diff --git a/crates/core/templates/bash_global.tpl b/crates/core/templates/bash_global.tpl index a8e6b1e92..b815e557b 100644 --- a/crates/core/templates/bash_global.tpl +++ b/crates/core/templates/bash_global.tpl @@ -1,5 +1,5 @@ -{{ if bin_path }} -exec proto run {bin} --bin "{bin_path}" -- {before_args} "$@" {after_args} +{{ if alt_bin }} +exec proto run {bin} --alt "{alt_bin}" -- {before_args} "$@" {after_args} {{ else }} exec proto run {bin} -- {before_args} "$@" {after_args} {{ endif }} diff --git a/crates/core/templates/cmd_global.tpl b/crates/core/templates/cmd_global.tpl index 05f712a41..fd65f193e 100644 --- a/crates/core/templates/cmd_global.tpl +++ b/crates/core/templates/cmd_global.tpl @@ -1,5 +1,5 @@ -{{ if bin_path }} -proto.exe run {bin} --bin "{bin_path}" -- {before_args} %* {after_args} +{{ if alt_bin }} +proto.exe run {bin} --alt "{alt_bin}" -- {before_args} %* {after_args} {{ else }} proto.exe run {bin} -- {before_args} %* {after_args} {{ endif }} diff --git a/crates/core/templates/pwsh_global.tpl b/crates/core/templates/pwsh_global.tpl index 0fab03b8c..aeda838db 100644 --- a/crates/core/templates/pwsh_global.tpl +++ b/crates/core/templates/pwsh_global.tpl @@ -1,5 +1,5 @@ -{{ if bin_path }} -& proto.exe run {bin} --bin "{bin_path}" -- {before_args} $args {after_args} +{{ if alt_bin }} +& proto.exe run {bin} --alt "{alt_bin}" -- {before_args} $args {after_args} {{ else }} & proto.exe run {bin} -- {before_args} $args {after_args} {{ endif }} diff --git a/crates/core/tests/shimmer_test.rs b/crates/core/tests/shimmer_test.rs index 7350b8bb2..af90b72c3 100644 --- a/crates/core/tests/shimmer_test.rs +++ b/crates/core/tests/shimmer_test.rs @@ -4,7 +4,7 @@ mod shimmer { use proto_core::{ProtoEnvironment, ShimContext}; use starbase_sandbox::{assert_snapshot, create_empty_sandbox}; use std::fs; - use std::path::{Path, PathBuf}; + use std::path::Path; fn create_context<'l>(id: &'l str, proto: &'l ProtoEnvironment) -> ShimContext<'l> { ShimContext { @@ -68,9 +68,9 @@ mod shimmer { let sandbox = create_empty_sandbox(); let proto = ProtoEnvironment::new_testing(sandbox.path()); - let bin_path = PathBuf::from("other/bin/path"); + let bin_path = "other/bin/path"; let mut context = create_context("primary", &proto); - context.bin_path = Some(bin_path); + context.alt_bin = Some(bin_path); let shim_path = proto.shims_dir.join("secondary"); context.create_shim(&shim_path, false).unwrap(); @@ -83,9 +83,9 @@ mod shimmer { let sandbox = create_empty_sandbox(); let proto = ProtoEnvironment::new_testing(sandbox.path()); - let bin_path = PathBuf::from("other/bin/path"); + let bin_path = "other/bin/path"; let mut context = create_context("primary", &proto); - context.bin_path = Some(bin_path); + context.alt_bin = Some(bin_path); context.before_args = Some("--a -b"); context.after_args = Some("./file"); diff --git a/crates/core/tests/snapshots/shimmer_test__shimmer__alt_global.snap b/crates/core/tests/snapshots/shimmer_test__shimmer__alt_global.snap index 31aa274d1..774991833 100644 --- a/crates/core/tests/snapshots/shimmer_test__shimmer__alt_global.snap +++ b/crates/core/tests/snapshots/shimmer_test__shimmer__alt_global.snap @@ -1,6 +1,6 @@ --- source: crates/core/tests/shimmer_test.rs -expression: "read_shim(&shim, sandbox.path())" +expression: "read_shim(&shim_path, sandbox.path())" --- #!/usr/bin/env bash set -e @@ -8,6 +8,6 @@ set -e -exec proto run primary --bin "other/bin/path" -- "$@" +exec proto run primary --alt "other/bin/path" -- "$@" diff --git a/crates/core/tests/snapshots/shimmer_test__shimmer__alt_global_with_args.snap b/crates/core/tests/snapshots/shimmer_test__shimmer__alt_global_with_args.snap index f846573bd..3bba69e83 100644 --- a/crates/core/tests/snapshots/shimmer_test__shimmer__alt_global_with_args.snap +++ b/crates/core/tests/snapshots/shimmer_test__shimmer__alt_global_with_args.snap @@ -1,6 +1,6 @@ --- source: crates/core/tests/shimmer_test.rs -expression: "read_shim(&shim, sandbox.path())" +expression: "read_shim(&shim_path, sandbox.path())" --- #!/usr/bin/env bash set -e @@ -8,6 +8,6 @@ set -e -exec proto run primary --bin "other/bin/path" -- --a -b "$@" ./file +exec proto run primary --alt "other/bin/path" -- --a -b "$@" ./file diff --git a/crates/pdk-api/Cargo.toml b/crates/pdk-api/Cargo.toml index 4912dd5e3..2d56f0e9c 100644 --- a/crates/pdk-api/Cargo.toml +++ b/crates/pdk-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proto_pdk_api" -version = "0.10.1" +version = "0.10.2" edition = "2021" license = "MIT" description = "Core APIs for creating proto WASM plugins." @@ -8,9 +8,9 @@ homepage = "https://moonrepo.dev/proto" repository = "https://github.com/moonrepo/proto" [dependencies] -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" } +system_env = { version = "0.1.4", path = "../system-env" } +version_spec = { version = "0.1.5", path = "../version-spec" } +warpgate_api = { version = "0.1.5", path = "../warpgate-api" } anyhow = "1.0.75" semver = { workspace = true, features = ["serde"] } serde = { workspace = true } diff --git a/crates/pdk-api/src/api.rs b/crates/pdk-api/src/api.rs index 02b93a6a9..2e39217d4 100644 --- a/crates/pdk-api/src/api.rs +++ b/crates/pdk-api/src/api.rs @@ -340,7 +340,7 @@ json_struct!( json_struct!( /// Configuration for generated shim and symlinked binary files. pub struct ExecutableConfig { - /// The binary file to execute, relative from the tool directory. + /// The file to execute, relative from the tool directory. /// Does *not* support virtual paths. /// /// The following scenarios are powered by this field: @@ -348,10 +348,15 @@ json_struct!( /// - 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`. + /// - For secondary shims, the file to execute. #[serde(skip_serializing_if = "Option::is_none")] pub exe_path: Option, + /// The executable path to use for symlinking binaries instead of `exe_path`. + /// This should only be used when `exe_path` is a non-standard executable. + #[serde(skip_serializing_if = "Option::is_none")] + pub exe_link_path: Option, + /// Do not symlink a binary in `~/.proto/bin`. #[serde(skip_serializing_if = "is_false")] pub no_bin: bool, @@ -360,6 +365,9 @@ json_struct!( #[serde(skip_serializing_if = "is_false")] pub no_shim: bool, + /// The parent executable name required to execute the local executable path. + pub parent_exe_name: Option, + /// Custom args to prepend to user-provided args within the generated shim. #[serde(skip_serializing_if = "Option::is_none")] pub shim_before_args: Option, @@ -377,6 +385,14 @@ impl ExecutableConfig { ..ExecutableConfig::default() } } + + pub fn with_parent, P: AsRef>(exe_path: T, parent_exe: P) -> Self { + Self { + exe_path: Some(PathBuf::from(exe_path.as_ref())), + parent_exe_name: Some(parent_exe.as_ref().to_owned()), + ..ExecutableConfig::default() + } + } } json_struct!( diff --git a/crates/pdk-test-utils/Cargo.toml b/crates/pdk-test-utils/Cargo.toml index 1dcef6fd4..4fd05d291 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.10.1" +version = "0.10.2" edition = "2021" license = "MIT" description = "Utilities for testing proto WASM plugins." @@ -8,8 +8,8 @@ homepage = "https://moonrepo.dev/proto" repository = "https://github.com/moonrepo/proto" [dependencies] -proto_core = { version = "0.22.1", path = "../core" } -proto_pdk_api = { version = "0.10.1", path = "../pdk-api" } +proto_core = { version = "0.22.3", path = "../core" } +proto_pdk_api = { version = "0.10.2", path = "../pdk-api" } extism = { workspace = true } serde_json = { workspace = true } toml = { version = "0.8.6", optional = true } diff --git a/crates/pdk/Cargo.toml b/crates/pdk/Cargo.toml index 1eb0de330..3ddb61f06 100644 --- a/crates/pdk/Cargo.toml +++ b/crates/pdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proto_pdk" -version = "0.10.1" +version = "0.10.2" 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.10.1", path = "../pdk-api" } +proto_pdk_api = { version = "0.10.2", path = "../pdk-api" } anyhow = "1.0.75" extism-pdk = { workspace = true } serde = { workspace = true } diff --git a/crates/system-env/Cargo.toml b/crates/system-env/Cargo.toml index da1aab836..f6641b37f 100644 --- a/crates/system-env/Cargo.toml +++ b/crates/system-env/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "system_env" -version = "0.1.3" +version = "0.1.4" edition = "2021" license = "MIT" description = "Information about the system environment: operating system, architecture, package manager, etc." diff --git a/crates/version-spec/Cargo.toml b/crates/version-spec/Cargo.toml index e7056b191..25ebef0aa 100644 --- a/crates/version-spec/Cargo.toml +++ b/crates/version-spec/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "version_spec" -version = "0.1.4" +version = "0.1.5" edition = "2021" license = "MIT" description = "A specification for working with partial, full, or aliased versions." diff --git a/crates/warpgate-api/Cargo.toml b/crates/warpgate-api/Cargo.toml index bdc126cc8..48f0b7cfb 100644 --- a/crates/warpgate-api/Cargo.toml +++ b/crates/warpgate-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "warpgate_api" -version = "0.1.4" +version = "0.1.5" edition = "2021" license = "MIT" description = "APIs for working with Warpgate plugins." diff --git a/crates/warpgate/Cargo.toml b/crates/warpgate/Cargo.toml index 4524211a6..96831063a 100644 --- a/crates/warpgate/Cargo.toml +++ b/crates/warpgate/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "warpgate" -version = "0.5.13" +version = "0.5.14" edition = "2021" license = "MIT" description = "Download, resolve, and manage Extism WASM plugins at runtime." repository = "https://github.com/moonrepo/proto" [dependencies] -warpgate_api = { version = "0.1.4", path = "../warpgate-api" } +warpgate_api = { version = "0.1.5", path = "../warpgate-api" } extism = { workspace = true } miette = { workspace = true } once_cell = { workspace = true } From 612d5b6bb2fe98055e694003af63a4f111134bc2 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Wed, 8 Nov 2023 09:43:20 -0800 Subject: [PATCH 6/7] deps: Pin plugin versions. --- CHANGELOG.md | 7 +++++++ crates/core/src/tools_config.rs | 16 ++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b10189727..04a6ee6e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,13 @@ #### 🧩 Plugins +- Updated `bun_plugin` to v0.5. +- Updated `deno_plugin` to v0.5. +- Updated `go_plugin` to v0.5. +- Updated `node_plugin` and `node_depman_plugin` to v0.5. +- Updated `python_plugin` to v0.2. +- Updated `rust_plugin` to v0.4. +- Updated `schema_plugin` (TOML) to v0.5. - **Node** - Updated the `npm` tool to create the `npx` shim instead of the `node` tool. - Updated symlinked binaries to use the shell scripts instead of the source `.js` files (when applicable). diff --git a/crates/core/src/tools_config.rs b/crates/core/src/tools_config.rs index 66aaf2d31..ce649d58e 100644 --- a/crates/core/src/tools_config.rs +++ b/crates/core/src/tools_config.rs @@ -126,7 +126,7 @@ impl ToolsConfig { self.plugins.insert( Id::raw("bun"), PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/bun-plugin/releases/download/v0.5.0-alpha.1/bun_plugin.wasm".into() + url: "https://github.com/moonrepo/bun-plugin/releases/download/v0.5.0/bun_plugin.wasm".into() } ); } @@ -135,7 +135,7 @@ impl ToolsConfig { self.plugins.insert( Id::raw("deno"), PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/deno-plugin/releases/download/v0.5.0-alpha.1/deno_plugin.wasm".into() + url: "https://github.com/moonrepo/deno-plugin/releases/download/v0.5.0/deno_plugin.wasm".into() } ); } @@ -144,7 +144,7 @@ impl ToolsConfig { self.plugins.insert( Id::raw("go"), PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/go-plugin/releases/download/v0.5.0-alpha.1/go_plugin.wasm".into() + url: "https://github.com/moonrepo/go-plugin/releases/download/v0.5.0/go_plugin.wasm".into() } ); } @@ -153,7 +153,7 @@ impl ToolsConfig { self.plugins.insert( Id::raw("node"), PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/node-plugin/releases/download/v0.5.0-alpha.1/node_plugin.wasm".into() + url: "https://github.com/moonrepo/node-plugin/releases/download/v0.5.0/node_plugin.wasm".into() } ); } @@ -163,7 +163,7 @@ impl ToolsConfig { self.plugins.insert( Id::raw(depman), PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/node-plugin/releases/download/v0.5.0-alpha.1/node_depman_plugin.wasm".into() + url: "https://github.com/moonrepo/node-plugin/releases/download/v0.5.0/node_depman_plugin.wasm".into() } ); } @@ -173,7 +173,7 @@ impl ToolsConfig { self.plugins.insert( Id::raw("python"), PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/python-plugin/releases/download/v0.2.0-alpha.1/python_plugin.wasm".into() + url: "https://github.com/moonrepo/python-plugin/releases/download/v0.2.0/python_plugin.wasm".into() } ); } @@ -182,7 +182,7 @@ impl ToolsConfig { self.plugins.insert( Id::raw("rust"), PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/rust-plugin/releases/download/v0.4.0-alpha.1/rust_plugin.wasm".into() + url: "https://github.com/moonrepo/rust-plugin/releases/download/v0.4.0/rust_plugin.wasm".into() } ); } @@ -191,7 +191,7 @@ impl ToolsConfig { self.plugins.insert( Id::raw(SCHEMA_PLUGIN_KEY), PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/schema-plugin/releases/download/v0.5.0-alpha.1/schema_plugin.wasm".into() + url: "https://github.com/moonrepo/schema-plugin/releases/download/v0.5.0/schema_plugin.wasm".into() } ); } From ca5f9a663d6d619e7bfb5807e73fb0a184a67ab6 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Wed, 8 Nov 2023 11:22:47 -0800 Subject: [PATCH 7/7] test: Fix failing windows path check. --- crates/cli/tests/bin_test.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/cli/tests/bin_test.rs b/crates/cli/tests/bin_test.rs index f04db6871..82cfacb7f 100644 --- a/crates/cli/tests/bin_test.rs +++ b/crates/cli/tests/bin_test.rs @@ -33,9 +33,11 @@ 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.cmd")); + assert.stdout(predicate::str::contains( + "tools\\npm\\9.0.0\\bin/npm-cli.js", + )); } else { - assert.stdout(predicate::str::contains("tools/npm/9.0.0/bin/npm")); + assert.stdout(predicate::str::contains("tools/npm/9.0.0/bin/npm-cli.js")); } }