diff --git a/CHANGELOG.md b/CHANGELOG.md index b9754b2b8..4cace8127 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,14 @@ - [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 + +- WASM API + - Added a `ExecutableConfig.shim_env_vars` field. + - Updated `ExecutableConfig.shim_before_args` and `ExecutableConfig.shim_after_args` to support a list of strings. + ## 0.25.0 #### 🚀 Updates diff --git a/Cargo.lock b/Cargo.lock index cb6089a58..2bf5d17d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2477,7 +2477,7 @@ dependencies = [ [[package]] name = "proto_cli_shim" -version = "0.0.1" +version = "0.25.0" dependencies = [ "ctrlc", "dirs 5.0.1", @@ -2506,6 +2506,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "shell-words", "starbase_archive", "starbase_events", "starbase_sandbox", diff --git a/Cargo.toml b/Cargo.toml index c80e742f0..0e90c0cdd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ semver = "1.0.20" serde = { version = "1.0.193", features = ["derive"] } serde_json = "1.0.108" sha2 = "0.10.8" +shell-words = "1.1.0" starbase = "0.2.10" starbase_archive = { version = "0.2.5", features = [ "tar-gz", diff --git a/crates/cli-shim/Cargo.toml b/crates/cli-shim/Cargo.toml index 7398bd421..a2a7a6a99 100644 --- a/crates/cli-shim/Cargo.toml +++ b/crates/cli-shim/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proto_cli_shim" -version = "0.0.1" +version = "0.25.0" edition = "2021" publish = false @@ -16,3 +16,6 @@ serde_json = { workspace = true } shared_child = "1.0.0" sigpipe = "0.1.3" starbase = { workspace = true } + +[package.metadata.dist] +dist = true diff --git a/crates/cli-shim/src/main.rs b/crates/cli-shim/src/main.rs index 87fa6c5c0..a16e8c090 100644 --- a/crates/cli-shim/src/main.rs +++ b/crates/cli-shim/src/main.rs @@ -9,12 +9,14 @@ use std::process::{Command, Stdio}; use std::sync::Arc; use std::{env, fs, io, process}; +// Keep in sync with crates/core/src/shim_registry.rs #[derive(Default, Deserialize)] #[serde(default)] struct Shim { after_args: Vec, alt_for: Option, before_args: Vec, + env_vars: HashMap, } fn get_proto_home() -> Result { @@ -75,6 +77,8 @@ fn create_command(mut args: VecDeque, shim_name: &str) -> Result, + + #[serde(skip_serializing_if = "Option::is_none")] + pub alt_for: Option, + + #[serde(skip_serializing_if = "Vec::is_empty")] + pub before_args: Vec, + + #[serde(skip_serializing_if = "HashMap::is_empty")] + pub env_vars: HashMap, +} + +pub type ShimsMap = BTreeMap; + +pub struct ShimRegistry; + +impl ShimRegistry { + pub fn update>(proto: P, entries: ShimsMap) -> miette::Result<()> { + if entries.is_empty() { + return Ok(()); + } + + let file = proto.as_ref().shims_dir.join("registry.json"); + + let mut config: ShimsMap = if file.exists() { + read_json_file_with_lock(&file)? + } else { + BTreeMap::default() + }; + + let mut mutated = false; + + for (key, value) in entries { + // Don't write the file if nothing has changed + if config + .get(&key) + .is_some_and(|current_value| current_value == &value) + { + continue; + } + + config.insert(key, value); + mutated = true; + } + + if mutated { + write_json_file_with_lock(file, &config)?; + } + + Ok(()) + } +} diff --git a/crates/core/src/shimmer.rs b/crates/core/src/shimmer.rs index 21f8d57d7..9a33eed53 100644 --- a/crates/core/src/shimmer.rs +++ b/crates/core/src/shimmer.rs @@ -17,10 +17,10 @@ pub struct ShimContext<'tool> { pub alt_bin: Option<&'tool str>, /// Args to prepend to user-provided args. - pub before_args: Option<&'tool str>, + pub before_args: Option, /// Args to append to user-provided args. - pub after_args: Option<&'tool str>, + pub after_args: Option, // TOOL INFO /// ID of the tool, for logging purposes. diff --git a/crates/core/src/tool.rs b/crates/core/src/tool.rs index eac155b97..025bd8ce6 100644 --- a/crates/core/src/tool.rs +++ b/crates/core/src/tool.rs @@ -7,6 +7,7 @@ use crate::helpers::{ use crate::host_funcs::{create_host_functions, HostData}; use crate::proto::ProtoEnvironment; use crate::proto_config::ProtoConfig; +use crate::shim_registry::{Shim, ShimRegistry, ShimsMap}; use crate::shimmer::{get_shim_file_names, ShimContext, SHIM_VERSION}; use crate::tool_manifest::{ToolManifest, ToolManifestVersion}; use crate::version_resolver::VersionResolver; @@ -1400,23 +1401,58 @@ impl Tool { local: vec![], }; + let mut registry: ShimsMap = BTreeMap::new(); + registry.insert(self.id.to_string(), Shim::default()); + for location in shims { + let mut shim_entry = Shim::default(); 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(); - // Only use --alt when the secondary executable exists - if !location.primary && location.config.exe_path.is_some() { - context.alt_bin = Some(&location.name); + // Handle before and after args + if let Some(before_args) = &location.config.shim_before_args { + context.before_args = Some(before_args.as_string()); + + shim_entry.before_args = match before_args { + StringOrVec::String(value) => shell_words::split(value).into_diagnostic()?, + StringOrVec::Vec(value) => value.to_owned(), + }; + } + + if let Some(after_args) = &location.config.shim_after_args { + context.after_args = Some(after_args.as_string()); + + shim_entry.after_args = match after_args { + StringOrVec::String(value) => shell_words::split(value).into_diagnostic()?, + StringOrVec::Vec(value) => value.to_owned(), + }; + } + + if let Some(env_vars) = &location.config.shim_env_vars { + shim_entry.env_vars.extend(env_vars.to_owned()); + } + + if !location.primary { + shim_entry.alt_for = Some(self.id.to_string()); + + // Only use --alt when the secondary executable exists + if location.config.exe_path.is_some() { + context.alt_bin = Some(&location.name); + } } context.create_shim(&location.path, find_only)?; + // Update the registry + registry.insert(location.name.clone(), shim_entry); + + // Add to the event event.global.push(location.name); } self.on_created_shims.emit(event).await?; + ShimRegistry::update(&self.proto, registry)?; + Ok(()) } diff --git a/crates/core/tests/shimmer_test.rs b/crates/core/tests/shimmer_test.rs index 97b0a0320..c55ae9291 100644 --- a/crates/core/tests/shimmer_test.rs +++ b/crates/core/tests/shimmer_test.rs @@ -52,8 +52,8 @@ mod shimmer { let proto = ProtoEnvironment::new_testing(sandbox.path()); let mut context = create_context("primary"); - context.before_args = Some("--a -b"); - context.after_args = Some("./file"); + context.before_args = Some("--a -b".into()); + context.after_args = Some("./file".into()); let shim_path = proto.shims_dir.join("primary"); context.create_shim(&shim_path, false).unwrap(); @@ -84,8 +84,8 @@ mod shimmer { let bin_path = "other/bin/path"; let mut context = create_context("primary"); context.alt_bin = Some(bin_path); - context.before_args = Some("--a -b"); - context.after_args = Some("./file"); + context.before_args = Some("--a -b".into()); + context.after_args = Some("./file".into()); let shim_path = proto.shims_dir.join("secondary"); context.create_shim(&shim_path, false).unwrap(); diff --git a/crates/pdk-api/src/api.rs b/crates/pdk-api/src/api.rs index 4475ff124..d0fe7005a 100644 --- a/crates/pdk-api/src/api.rs +++ b/crates/pdk-api/src/api.rs @@ -1,4 +1,6 @@ use crate::host_funcs::ExecCommandOutput; +use crate::shapes::StringOrVec; +use crate::{json_enum, json_struct}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::path::PathBuf; @@ -8,37 +10,10 @@ use warpgate_api::VirtualPath; pub use semver::{Version, VersionReq}; -fn is_empty_map(value: &HashMap) -> bool { - value.is_empty() -} - -fn is_empty_vec(value: &[T]) -> bool { - value.is_empty() -} - fn is_false(value: &bool) -> bool { !(*value) } -#[doc(hidden)] -#[macro_export] -macro_rules! json_struct { - ($struct:item) => { - #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] - #[serde(default)] - $struct - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! json_enum { - ($struct:item) => { - #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] - $struct - }; -} - json_struct!( /// Represents an empty input. pub struct EmptyInput {} @@ -111,7 +86,7 @@ json_struct!( /// Names of commands that will self-upgrade the tool, /// and should be blocked from happening. - #[serde(skip_serializing_if = "is_empty_vec")] + #[serde(skip_serializing_if = "Vec::is_empty")] pub self_upgrade_commands: Vec, /// Type of the tool. @@ -126,11 +101,11 @@ json_struct!( /// Output returned by the `detect_version_files` function. pub struct DetectVersionOutput { /// List of files that should be checked for version information. - #[serde(skip_serializing_if = "is_empty_vec")] + #[serde(skip_serializing_if = "Vec::is_empty")] pub files: Vec, /// List of path patterns to ignore when traversing directories. - #[serde(skip_serializing_if = "is_empty_vec")] + #[serde(skip_serializing_if = "Vec::is_empty")] pub ignore: Vec, } ); @@ -255,12 +230,12 @@ json_struct!( /// List of instructions to execute to build the tool, after system /// dependencies have been installed. - #[serde(skip_serializing_if = "is_empty_vec")] + #[serde(skip_serializing_if = "Vec::is_empty")] pub instructions: Vec, /// List of system dependencies that are required for building from source. /// If a dependency does not exist, it will be installed. - #[serde(skip_serializing_if = "is_empty_vec")] + #[serde(skip_serializing_if = "Vec::is_empty")] pub system_dependencies: Vec, } ); @@ -387,11 +362,15 @@ json_struct!( /// Custom args to prepend to user-provided args within the generated shim. #[serde(skip_serializing_if = "Option::is_none")] - pub shim_before_args: Option, + 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, + pub shim_after_args: Option, + + /// Custom environment variables to set when executing the shim. + #[serde(skip_serializing_if = "Option::is_none")] + pub shim_env_vars: Option>, } ); @@ -417,7 +396,7 @@ json_struct!( pub struct LocateExecutablesOutput { /// List of directory paths to find the globals installation directory. /// Each path supports environment variable expansion. - #[serde(skip_serializing_if = "is_empty_vec")] + #[serde(skip_serializing_if = "Vec::is_empty")] pub globals_lookup_dirs: Vec, /// A string that all global binaries are prefixed with, and will be removed @@ -432,7 +411,7 @@ json_struct!( /// Configures secondary/additional executables to create. /// The map key is the name of the shim/binary file. - #[serde(skip_serializing_if = "is_empty_map")] + #[serde(skip_serializing_if = "HashMap::is_empty")] pub secondary: HashMap, } ); @@ -543,11 +522,11 @@ json_struct!( pub latest: Option, /// Mapping of aliases (channels, etc) to a version. - #[serde(skip_serializing_if = "is_empty_map")] + #[serde(skip_serializing_if = "HashMap::is_empty")] pub aliases: HashMap, /// List of available production versions to install. - #[serde(skip_serializing_if = "is_empty_vec")] + #[serde(skip_serializing_if = "Vec::is_empty")] pub versions: Vec, } ); diff --git a/crates/pdk-api/src/lib.rs b/crates/pdk-api/src/lib.rs index ccb23f786..288c8fcb8 100644 --- a/crates/pdk-api/src/lib.rs +++ b/crates/pdk-api/src/lib.rs @@ -4,6 +4,7 @@ mod error; mod hooks; mod host; mod host_funcs; +mod shapes; pub use api::*; pub use api_deprecated::*; @@ -11,6 +12,7 @@ pub use error::*; pub use hooks::*; pub use host::*; pub use host_funcs::*; +pub use shapes::*; pub use system_env::{DependencyConfig, DependencyName, SystemDependency, SystemPackageManager}; pub use version_spec::*; pub use warpgate_api::*; diff --git a/crates/pdk-api/src/shapes.rs b/crates/pdk-api/src/shapes.rs new file mode 100644 index 000000000..e9872f856 --- /dev/null +++ b/crates/pdk-api/src/shapes.rs @@ -0,0 +1,37 @@ +use serde::{Deserialize, Serialize}; + +#[doc(hidden)] +#[macro_export] +macro_rules! json_struct { + ($struct:item) => { + #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] + #[serde(default)] + $struct + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! json_enum { + ($struct:item) => { + #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] + $struct + }; +} + +json_enum!( + #[serde(untagged)] + pub enum StringOrVec { + String(String), + Vec(Vec), + } +); + +impl StringOrVec { + pub fn as_string(&self) -> String { + match self { + Self::String(value) => value.to_owned(), + Self::Vec(value) => value.to_vec().join(" "), + } + } +} diff --git a/crates/system-env/Cargo.toml b/crates/system-env/Cargo.toml index bcd47632a..817e6eee9 100644 --- a/crates/system-env/Cargo.toml +++ b/crates/system-env/Cargo.toml @@ -11,7 +11,7 @@ repository = "https://github.com/moonrepo/proto" schematic = { workspace = true, optional = true, features = ["schema"] } serde = { workspace = true } serde_json = { workspace = true } -shell-words = "1.1.0" +shell-words = { workspace = true } thiserror = { workspace = true } [features] diff --git a/plugins/Cargo.lock b/plugins/Cargo.lock index f802a32c3..0b98988e4 100644 --- a/plugins/Cargo.lock +++ b/plugins/Cargo.lock @@ -2184,7 +2184,7 @@ dependencies = [ [[package]] name = "proto_core" -version = "0.24.2" +version = "0.25.0" dependencies = [ "cached", "extism", @@ -2200,6 +2200,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "shell-words", "starbase_archive", "starbase_events", "starbase_styles", @@ -2215,7 +2216,7 @@ dependencies = [ [[package]] name = "proto_pdk" -version = "0.11.1" +version = "0.11.2" dependencies = [ "anyhow", "extism-pdk", @@ -2225,7 +2226,7 @@ dependencies = [ [[package]] name = "proto_pdk_api" -version = "0.11.1" +version = "0.11.2" dependencies = [ "anyhow", "semver", @@ -2239,7 +2240,7 @@ dependencies = [ [[package]] name = "proto_pdk_test_utils" -version = "0.12.1" +version = "0.13.0" dependencies = [ "extism", "proto_core", @@ -2598,9 +2599,9 @@ dependencies = [ [[package]] name = "schematic" -version = "0.12.10" +version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710a6af816c586a37dc9b8b304370f36d8b90751785bbb245942bed0ae00a69d" +checksum = "c5ed74b468686fb5551b8f29c8339dcf0b7ac4e0f475490f6f49f6a2aadcd392" dependencies = [ "garde", "indexmap 2.1.0", @@ -2888,9 +2889,9 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "starbase_archive" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "935856c30da2f62a73bcc57eecb5508bedd313287bc0e00e7e7c4b7a07fb5995" +checksum = "b4a0d1c404d4e987597b018bee76fb8e79937f3b2ab0cf5963ce043031092694" dependencies = [ "flate2", "miette", @@ -3549,7 +3550,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "version_spec" -version = "0.1.6" +version = "0.1.7" dependencies = [ "human-sort", "regex", @@ -3588,7 +3589,7 @@ dependencies = [ [[package]] name = "warpgate" -version = "0.6.0" +version = "0.7.0" dependencies = [ "extism", "miette",