diff --git a/.prototools b/.prototools index 923096da7..7f744b36a 100644 --- a/.prototools +++ b/.prototools @@ -1,3 +1,10 @@ [plugins] moon-test = "source:https://raw.githubusercontent.com/moonrepo/moon/master/proto-plugin.toml" wasm-test = "source:./plugins/target/wasm32-wasi/debug/proto_wasm_test.wasm" + +# [tools.wasm-test] +# number = 123 +# string = "foo" +# boolean = true +# list = ["a", "b", "c"] +# map = { "a" = 123 } diff --git a/CHANGELOG.md b/CHANGELOG.md index 169db8cd6..4616f089e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,59 @@ - [Rust](https://github.com/moonrepo/rust-plugin/blob/master/CHANGELOG.md) - [TOML schema](https://github.com/moonrepo/schema-plugin/blob/master/CHANGELOG.md) +## Unreleased + +#### 💥 Breaking + +> To ease the migration process, we've added a new migrate command. Simply run `proto migrate v0.24` after upgrading proto! + +- Standardized configuration files. + - Merged `~/.proto/config.toml` functionality into `.prototools` under a new `[settings]` table. This means settings like `auto-clean` can be defined anywhere now. + - Removed `~/.proto/config.toml`. Use `~/.proto/.prototools` instead, which is now the new global config (via `--global` arg). + - Moved `node-intercept-globals` setting to `tools.node.intercept-globals`. +- Reworked user configured aliases and default/global version. + - Moved values to `.prototools` (user managed) from `~/.proto/tools//manifest.json` (internally managed). + - Aliases are now stored in the `[tools.]`, while the default version is at the root. + ```toml + node = "20.10.0" + [tools.node.aliases] + work = "^18" + ``` +- Updated `proto alias` and `proto unalias` to longer write to the global config by default. Now requires a `--global` flag. + - This change was made to align with `proto pin`, `proto tool add`, and `proto tool remove`. + +#### 🚀 Updates + +- Added a `proto migrate v0.24` command for migrating configs. We'll also log a warning if we detect the old configuration. + - For some scenarios, we'll attempt to auto-migrate under the hood when applicable. +- Added support for defining configuration that can be passed to WASM plugins. + - Can be added to `.prototools` under a `[tools.]` table. + - Moved Node.js specific settings into this new format. + ```toml + [tools.node] + bundled-npm = false + intercept-globals = false + ``` +- Updated non-latest plugins to be cached for 30 days, instead of forever. +- Updated cleaning to also remove old proto versions from `~/.proto/tools/proto`. +- WASM API + - Added a `get_tool_config` function. Can be typed with a serde compatible struct. + - Deprecated the `get_proto_user_config` function. + +#### 🐞 Fixes + +- Fixed an issue where resolving canary versions wouldn't work correctly. + +#### 🧩 Plugins + +- Updated `bun_plugin` to v0.6. +- Updated `deno_plugin` to v0.6. +- Updated `go_plugin` to v0.6. +- Updated `node_plugin` and `node_depman_plugin` to v0.6. +- Updated `python_plugin` to v0.4. +- Updated `rust_plugin` to v0.5. +- Updated `schema_plugin` (TOML) to v0.6. + ## 0.23.8 #### 🚀 Updates diff --git a/Cargo.lock b/Cargo.lock index b88382da5..31f69b4d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -390,6 +390,15 @@ dependencies = [ "winx", ] +[[package]] +name = "castaway" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" +dependencies = [ + "rustversion", +] + [[package]] name = "cbindgen" version = "0.24.3" @@ -556,6 +565,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "compact_str" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "ryu", + "static_assertions", +] + [[package]] name = "console" version = "0.15.5" @@ -1417,6 +1439,31 @@ dependencies = [ "serde_json", ] +[[package]] +name = "garde" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f0bde634ca1248cfcd67a29f7b3798997d328406020f5e19b502b0a0d5e8cae" +dependencies = [ + "compact_str", + "garde_derive", + "once_cell", + "regex", + "smallvec", +] + +[[package]] +name = "garde_derive" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feafd106c73dd61d6f10f2fa782cdb6212b3422aee5b953a094818e9575078aa" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.39", +] + [[package]] name = "generic-array" version = "0.14.6" @@ -2427,9 +2474,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] @@ -2470,7 +2517,7 @@ dependencies = [ [[package]] name = "proto_core" -version = "0.23.7" +version = "0.24.0" dependencies = [ "cached", "extism", @@ -2481,6 +2528,7 @@ dependencies = [ "proto_pdk_api", "regex", "reqwest", + "schematic", "semver", "serde", "serde_json", @@ -2502,7 +2550,7 @@ dependencies = [ [[package]] name = "proto_pdk" -version = "0.10.3" +version = "0.11.1" dependencies = [ "anyhow", "extism-pdk", @@ -2512,7 +2560,7 @@ dependencies = [ [[package]] name = "proto_pdk_api" -version = "0.10.4" +version = "0.11.1" dependencies = [ "anyhow", "semver", @@ -2526,7 +2574,7 @@ dependencies = [ [[package]] name = "proto_pdk_test_utils" -version = "0.11.1" +version = "0.12.0" dependencies = [ "extism", "proto_core", @@ -2902,6 +2950,12 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "ryu" version = "1.0.13" @@ -2928,23 +2982,28 @@ dependencies = [ [[package]] name = "schematic" -version = "0.12.8" +version = "0.12.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca989126cfccad7f637f2bb609445b07aab446d2e559552252a62c3ecc1212a7" +checksum = "710a6af816c586a37dc9b8b304370f36d8b90751785bbb245942bed0ae00a69d" dependencies = [ + "garde", "indexmap 2.1.0", "miette", "schematic_macros", "schematic_types", + "serde", + "serde_path_to_error", + "starbase_styles", "thiserror", + "toml 0.8.8", "tracing", ] [[package]] name = "schematic_macros" -version = "0.12.6" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "417806a27d5f35bf427c0283e7c94593b4130abca4d18363d2c48d7ba4c02dfd" +checksum = "10e8b482f96eaca0fdc4fe3441c2eda44f5aeeb1a93b653d214ee2ecf4975be4" dependencies = [ "convert_case", "darling 0.20.3", @@ -2955,9 +3014,9 @@ dependencies = [ [[package]] name = "schematic_types" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ef6073b16a3870336d3cbbc69ab15d3abb089afc0c53944866079aa2578f59" +checksum = "c38cdd0968bde35f7e093600c90e0306da6af5ffd1b3cb175dbfde1146debcf2" [[package]] name = "scopeguard" @@ -3054,6 +3113,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_spanned" version = "0.6.4" @@ -3340,6 +3409,12 @@ dependencies = [ "wax", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" @@ -3435,7 +3510,7 @@ dependencies = [ [[package]] name = "system_env" -version = "0.1.6" +version = "0.1.7" dependencies = [ "schematic", "serde", @@ -4046,10 +4121,11 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "version_spec" -version = "0.1.5" +version = "0.1.6" dependencies = [ "human-sort", "regex", + "schematic", "semver", "serde", ] @@ -4085,7 +4161,7 @@ dependencies = [ [[package]] name = "warpgate" -version = "0.5.15" +version = "0.6.0" dependencies = [ "extism", "miette", @@ -4093,6 +4169,7 @@ dependencies = [ "once_map", "regex", "reqwest", + "schematic", "serde", "serde_json", "sha2", @@ -4108,8 +4185,9 @@ dependencies = [ [[package]] name = "warpgate_api" -version = "0.1.5" +version = "0.1.6" dependencies = [ + "schematic", "serde", ] diff --git a/Cargo.toml b/Cargo.toml index d72e9dc32..f6b27b54d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,9 +16,7 @@ once_cell = "1.18.0" once_map = "0.4.13" regex = { version = "1.10.2", default-features = false, features = ["std"] } reqwest = { version = "0.11.22", default-features = false } -schematic = { version = "0.12.8", default-features = false, features = [ - "schema", -] } +schematic = { version = "0.12.10", default-features = false } semver = "1.0.20" serde = { version = "1.0.193", features = ["derive"] } serde_json = "1.0.108" diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 714ebcb7b..68516ed4b 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.23.7", path = "../core" } -proto_pdk_api = { version = "0.10.4", path = "../pdk-api" } -system_env = { version = "0.1.6", path = "../system-env" } +proto_core = { version = "0.24.0", path = "../core" } +proto_pdk_api = { version = "0.11.1", path = "../pdk-api" } +system_env = { version = "0.1.7", path = "../system-env" } chrono = "0.4.31" clap = { workspace = true, features = ["derive", "env"] } clap_complete = { workspace = true } diff --git a/crates/cli/src/app.rs b/crates/cli/src/app.rs index 8cf9b68c1..036a46588 100644 --- a/crates/cli/src/app.rs +++ b/crates/cli/src/app.rs @@ -85,7 +85,7 @@ pub enum Commands { alias = "ap", name = "add-plugin", about = "Add a plugin.", - long_about = "Add a plugin to the local .prototools config, or global ~/.proto/config.toml config.", + long_about = "Add a plugin to the local .prototools config, or global ~/.proto/.prototools config.", hide = true )] AddPlugin(AddToolArgs), @@ -188,7 +188,7 @@ pub enum Commands { alias = "rp", name = "remove-plugin", about = "Remove a plugin.", - long_about = "Remove a plugin from the local .prototools config, or global ~/.proto/config.toml config.", + long_about = "Remove a plugin from the local .prototools config, or global ~/.proto/.prototools config.", hide = true )] RemovePlugin(RemoveToolArgs), @@ -255,7 +255,7 @@ pub enum ToolCommands { #[command( name = "add", about = "Add a tool plugin.", - long_about = "Add a plugin to the local .prototools config, or global ~/.proto/config.toml config." + long_about = "Add a plugin to the local .prototools config, or global ~/.proto/.prototools config." )] Add(AddToolArgs), @@ -278,7 +278,7 @@ pub enum ToolCommands { #[command( name = "remove", about = "Remove a tool plugin.", - long_about = "Remove a plugin from the local .prototools config, or global ~/.proto/config.toml config." + long_about = "Remove a plugin from the local .prototools config, or global ~/.proto/.prototools config." )] Remove(RemoveToolArgs), } diff --git a/crates/cli/src/commands/alias.rs b/crates/cli/src/commands/alias.rs index 6509c2fe0..5fd51dc8d 100644 --- a/crates/cli/src/commands/alias.rs +++ b/crates/cli/src/commands/alias.rs @@ -1,6 +1,7 @@ use crate::error::ProtoCliError; +use crate::helpers::ProtoResource; use clap::Args; -use proto_core::{is_alias_name, load_tool, Id, UnresolvedVersionSpec}; +use proto_core::{is_alias_name, Id, ProtoConfig, UnresolvedVersionSpec}; use starbase::system; use starbase_styles::color; use tracing::info; @@ -15,10 +16,16 @@ pub struct AliasArgs { #[arg(required = true, help = "Version or alias to associate with")] spec: UnresolvedVersionSpec, + + #[arg( + long, + help = "Add to the global .prototools instead of local .prototools" + )] + global: bool, } #[system] -pub async fn alias(args: ArgsRef) { +pub async fn alias(args: ArgsRef, proto: ResourceRef) { if let UnresolvedVersionSpec::Alias(inner_alias) = &args.spec { if &args.alias == inner_alias { return Err(ProtoCliError::NoMatchingAliasToVersion.into()); @@ -32,12 +39,17 @@ pub async fn alias(args: ArgsRef) { .into()); } - let mut tool = load_tool(&args.id).await?; + let tool = proto.load_tool(&args.id).await?; + + ProtoConfig::update(tool.proto.get_config_dir(args.global), |config| { + let tool_configs = config.tools.get_or_insert(Default::default()); + let tool_config = tool_configs.entry(tool.id.clone()).or_default(); - tool.manifest - .aliases - .insert(args.alias.clone(), args.spec.clone()); - tool.manifest.save()?; + tool_config + .aliases + .get_or_insert(Default::default()) + .insert(args.alias.clone(), args.spec.clone()); + })?; info!( "Added alias {} ({}) for {}", diff --git a/crates/cli/src/commands/bin.rs b/crates/cli/src/commands/bin.rs index 2ee93b9f4..8812e2aaa 100644 --- a/crates/cli/src/commands/bin.rs +++ b/crates/cli/src/commands/bin.rs @@ -1,5 +1,6 @@ +use crate::helpers::ProtoResource; use clap::Args; -use proto_core::{detect_version, load_tool, Id, UnresolvedVersionSpec}; +use proto_core::{detect_version, Id, UnresolvedVersionSpec}; use starbase::system; #[derive(Args, Clone, Debug)] @@ -18,8 +19,8 @@ pub struct BinArgs { } #[system] -pub async fn bin(args: ArgsRef) { - let mut tool = load_tool(&args.id).await?; +pub async fn bin(args: ArgsRef, proto: ResourceRef) { + let mut tool = proto.load_tool(&args.id).await?; let version = detect_version(&tool, args.spec.clone()).await?; tool.resolve_version(&version, true).await?; diff --git a/crates/cli/src/commands/clean.rs b/crates/cli/src/commands/clean.rs index 092388150..6a06b373b 100644 --- a/crates/cli/src/commands/clean.rs +++ b/crates/cli/src/commands/clean.rs @@ -1,9 +1,7 @@ -use crate::helpers::ToolsLoader; +use crate::helpers::ProtoResource; use clap::Args; use dialoguer::Confirm; -use proto_core::{ - get_plugins_dir, get_temp_dir, load_tool, remove_bin_file, Id, ProtoError, Tool, VersionSpec, -}; +use proto_core::{remove_bin_file, Id, ProtoError, Tool, VersionSpec}; use starbase::diagnostics::IntoDiagnostic; use starbase::{system, SystemResult}; use starbase_styles::color; @@ -164,11 +162,11 @@ pub async fn clean_tool(mut tool: Tool, now: u128, days: u8, yes: bool) -> miett Ok(clean_count) } -pub async fn clean_plugins(days: u64) -> miette::Result { +pub async fn clean_plugins(proto: &ProtoResource, days: u64) -> miette::Result { let duration = Duration::from_secs(86400 * days); let mut clean_count = 0; - for file in fs::read_dir(get_plugins_dir()?)? { + for file in fs::read_dir(&proto.env.plugins_dir)? { let path = file.path(); if path.is_file() { @@ -189,8 +187,33 @@ pub async fn clean_plugins(days: u64) -> miette::Result { Ok(clean_count) } -pub async fn purge_tool(id: &Id, yes: bool) -> SystemResult { - let tool = load_tool(id).await?; +pub async fn clean_proto(proto: &ProtoResource, days: u64) -> miette::Result { + let duration = Duration::from_secs(86400 * days); + let mut clean_count = 0; + + for file in fs::read_dir_all(proto.env.tools_dir.join("proto"))? { + let path = file.path(); + + if path.is_file() { + let bytes = fs::remove_file_if_older_than(&path, duration)?; + + if bytes > 0 { + debug!( + "proto version {} hasn't been used in over {} days, removing", + color::path(&path), + days + ); + + clean_count += 1; + } + } + } + + Ok(clean_count) +} + +pub async fn purge_tool(proto: &ProtoResource, id: &Id, yes: bool) -> SystemResult { + let tool = proto.load_tool(id).await?; let inventory_dir = tool.get_inventory_dir(); if yes @@ -222,19 +245,19 @@ pub async fn purge_tool(id: &Id, yes: bool) -> SystemResult { Ok(()) } -pub async fn purge_plugins(yes: bool) -> SystemResult { - let plugins_dir = get_plugins_dir()?; +pub async fn purge_plugins(proto: &ProtoResource, yes: bool) -> SystemResult { + let plugins_dir = &proto.env.plugins_dir; if yes || Confirm::new() .with_prompt(format!( "Purge all plugins in {}?", - color::path(&plugins_dir) + color::path(plugins_dir) )) .interact() .into_diagnostic()? { - fs::remove_dir_all(&plugins_dir)?; + fs::remove_dir_all(plugins_dir)?; fs::create_dir_all(plugins_dir)?; info!("Purged all downloaded plugins"); @@ -243,7 +266,7 @@ pub async fn purge_plugins(yes: bool) -> SystemResult { Ok(()) } -pub async fn internal_clean(args: &CleanArgs) -> SystemResult { +pub async fn internal_clean(proto: &ProtoResource, args: &CleanArgs) -> SystemResult { let days = args.days.unwrap_or(30); let now = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) @@ -253,19 +276,19 @@ pub async fn internal_clean(args: &CleanArgs) -> SystemResult { debug!("Finding installed tools to clean up..."); - let tools_loader = ToolsLoader::new()?; - - for tool in tools_loader.load_tools().await? { + for tool in proto.load_tools().await? { clean_count += clean_tool(tool, now, days, args.yes).await?; } + clean_count += clean_proto(proto, days as u64).await?; + if clean_count > 0 { info!("Successfully cleaned up {} versions", clean_count); } debug!("Finding installed plugins to clean up..."); - clean_count = clean_plugins(days as u64).await?; + clean_count = clean_plugins(proto, days as u64).await?; if clean_count > 0 { info!("Successfully cleaned up {} plugins", clean_count); @@ -273,7 +296,7 @@ pub async fn internal_clean(args: &CleanArgs) -> SystemResult { debug!("Cleaning temporary directory..."); - let results = fs::remove_dir_stale_contents(get_temp_dir()?, Duration::from_secs(86400))?; + let results = fs::remove_dir_stale_contents(&proto.env.temp_dir, Duration::from_secs(86400))?; if results.files_deleted > 0 { info!( @@ -286,14 +309,14 @@ pub async fn internal_clean(args: &CleanArgs) -> SystemResult { } #[system] -pub async fn clean(args: ArgsRef) { +pub async fn clean(args: ArgsRef, proto: ResourceRef) { if let Some(id) = &args.purge { - return purge_tool(id, args.yes).await; + return purge_tool(proto, id, args.yes).await; } if args.purge_plugins { - return purge_plugins(args.yes).await; + return purge_plugins(proto, args.yes).await; } - internal_clean(args).await?; + internal_clean(proto, args).await?; } diff --git a/crates/cli/src/commands/install.rs b/crates/cli/src/commands/install.rs index b0cd69792..650c8ffc0 100644 --- a/crates/cli/src/commands/install.rs +++ b/crates/cli/src/commands/install.rs @@ -1,10 +1,10 @@ use super::clean::clean_plugins; use super::pin::{internal_pin, PinArgs}; -use crate::helpers::{create_progress_bar, disable_progress_bars}; +use crate::helpers::{create_progress_bar, disable_progress_bars, ProtoResource}; use crate::shell; use clap::Args; use miette::IntoDiagnostic; -use proto_core::{load_tool, Id, PinType, Tool, UnresolvedVersionSpec}; +use proto_core::{Id, PinType, Tool, UnresolvedVersionSpec}; use proto_pdk_api::{InstallHook, SyncShellProfileInput, SyncShellProfileOutput}; use starbase::{system, SystemResult}; use starbase_styles::color; @@ -61,9 +61,9 @@ async fn pin_version( // via `pin-latest` setting if initial_version.is_latest() { - let user_config = tool.proto.load_user_config()?; + let config = tool.proto.load_config()?; - if let Some(pin_type) = user_config.pin_latest { + if let Some(pin_type) = &config.settings.pin_latest { args.global = matches!(pin_type, PinType::Global); return internal_pin(tool, &args, true).await; @@ -73,10 +73,14 @@ async fn pin_version( Ok(()) } -pub async fn internal_install(args: InstallArgs, tool: Option) -> miette::Result { +pub async fn internal_install( + proto: &ProtoResource, + args: InstallArgs, + tool: Option, +) -> miette::Result { let mut tool = match tool { Some(tool) => tool, - None => load_tool(&args.id).await?, + None => proto.load_tool(&args.id).await?, }; let version = if args.canary { @@ -159,7 +163,7 @@ pub async fn internal_install(args: InstallArgs, tool: Option) -> miette:: // Clean plugins debug!("Auto-cleaning plugins"); - clean_plugins(7).await?; + clean_plugins(proto, 7).await?; Ok(tool) } @@ -216,6 +220,6 @@ fn update_shell(tool: &Tool, passthrough_args: Vec) -> miette::Result<() } #[system] -pub async fn install(args: ArgsRef) { - internal_install(args.to_owned(), None).await?; +pub async fn install(args: ArgsRef, proto: ResourceRef) { + internal_install(proto, args.to_owned(), None).await?; } diff --git a/crates/cli/src/commands/install_all.rs b/crates/cli/src/commands/install_all.rs index 421d8fe5e..72ad7cfdd 100644 --- a/crates/cli/src/commands/install_all.rs +++ b/crates/cli/src/commands/install_all.rs @@ -1,5 +1,5 @@ use crate::helpers::{ - create_progress_bar, disable_progress_bars, enable_progress_bars, ToolsLoader, + create_progress_bar, disable_progress_bars, enable_progress_bars, ProtoResource, }; use crate::{ commands::clean::{internal_clean, CleanArgs}, @@ -8,28 +8,26 @@ use crate::{ use miette::IntoDiagnostic; use starbase::system; use starbase_styles::color; -use std::{env, process}; +use std::process; use tracing::{debug, info}; #[system] -pub async fn install_all() { - let working_dir = env::current_dir().expect("Missing current directory."); - +pub async fn install_all(proto: ResourceRef) { debug!("Loading tools and plugins from .prototools"); - let loader = ToolsLoader::new()?; - let tools = loader.load_tools().await?; + let tools = proto.load_tools().await?; debug!("Detecting tool versions to install"); - let mut versions = loader.tools_config.tools.clone(); + let config = proto.env.load_config()?; + let mut versions = config.versions.to_owned(); for tool in &tools { if versions.contains_key(&tool.id) { continue; } - if let Some(candidate) = tool.detect_version_from(&working_dir).await? { + if let Some(candidate) = tool.detect_version_from(&proto.env.cwd).await? { debug!("Detected version {} for {}", candidate, tool.get_name()); versions.insert(tool.id.clone(), candidate); @@ -58,8 +56,11 @@ pub async fn install_all() { for tool in tools { if let Some(version) = versions.remove(&tool.id) { - futures.push(tokio::spawn(async { + let proto_clone = proto.clone(); + + futures.push(tokio::spawn(async move { internal_install( + &proto_clone, InstallArgs { canary: false, id: tool.id.clone(), @@ -84,13 +85,16 @@ pub async fn install_all() { info!("Successfully installed tools"); - if loader.user_config.auto_clean { + if config.settings.auto_clean { info!("Auto-clean enabled, starting clean"); - internal_clean(&CleanArgs { - yes: true, - ..Default::default() - }) + internal_clean( + proto, + &CleanArgs { + yes: true, + ..Default::default() + }, + ) .await?; } } diff --git a/crates/cli/src/commands/install_global.rs b/crates/cli/src/commands/install_global.rs index 416c6dad9..c07b69c4b 100644 --- a/crates/cli/src/commands/install_global.rs +++ b/crates/cli/src/commands/install_global.rs @@ -1,7 +1,7 @@ use crate::error::ProtoCliError; -use crate::helpers::create_progress_bar; +use crate::helpers::{create_progress_bar, ProtoResource}; use clap::Args; -use proto_core::{detect_version, load_tool, Id}; +use proto_core::{detect_version, Id}; use starbase::system; use starbase_styles::color; use std::env; @@ -17,8 +17,8 @@ pub struct InstallGlobalArgs { } #[system] -pub async fn install_global(args: ArgsRef) { - let mut tool = load_tool(&args.id).await?; +pub async fn install_global(args: ArgsRef, proto: ResourceRef) { + let mut tool = proto.load_tool(&args.id).await?; let version = detect_version(&tool, None).await?; // Resolve a version as some tools install to a versioned folder diff --git a/crates/cli/src/commands/list.rs b/crates/cli/src/commands/list.rs index a871e481f..89457b255 100644 --- a/crates/cli/src/commands/list.rs +++ b/crates/cli/src/commands/list.rs @@ -1,5 +1,6 @@ +use crate::helpers::ProtoResource; use clap::Args; -use proto_core::{load_tool, Id}; +use proto_core::Id; use starbase::system; use std::process; use tracing::debug; @@ -11,8 +12,8 @@ pub struct ListArgs { } #[system] -pub async fn list(args: ArgsRef) { - let tool = load_tool(&args.id).await?; +pub async fn list(args: ArgsRef, proto: ResourceRef) { + let tool = proto.load_tool(&args.id).await?; debug!(manifest = ?tool.manifest.path, "Using versions from manifest"); diff --git a/crates/cli/src/commands/list_global.rs b/crates/cli/src/commands/list_global.rs index 66df4de10..b7730cdf6 100644 --- a/crates/cli/src/commands/list_global.rs +++ b/crates/cli/src/commands/list_global.rs @@ -1,6 +1,7 @@ use crate::error::ProtoCliError; +use crate::helpers::ProtoResource; use clap::Args; -use proto_core::{detect_version, load_tool, Id}; +use proto_core::{detect_version, Id}; use starbase::diagnostics::IntoDiagnostic; use starbase::system; use starbase_styles::color; @@ -15,8 +16,8 @@ pub struct ListGlobalArgs { } #[system] -pub async fn list_global(args: ArgsRef) { - let mut tool = load_tool(&args.id).await?; +pub async fn list_global(args: ArgsRef, proto: ResourceRef) { + let mut tool = proto.load_tool(&args.id).await?; let version = detect_version(&tool, None).await?; // Resolve a version as some tools install to a versioned folder diff --git a/crates/cli/src/commands/list_remote.rs b/crates/cli/src/commands/list_remote.rs index 30f8ffc2c..344b60872 100644 --- a/crates/cli/src/commands/list_remote.rs +++ b/crates/cli/src/commands/list_remote.rs @@ -1,5 +1,6 @@ +use crate::helpers::ProtoResource; use clap::Args; -use proto_core::{load_tool, Id, UnresolvedVersionSpec}; +use proto_core::{Id, UnresolvedVersionSpec}; use starbase::system; use std::process; use tracing::debug; @@ -11,8 +12,8 @@ pub struct ListRemoteArgs { } #[system] -pub async fn list_remote(args: ArgsRef) { - let mut tool = load_tool(&args.id).await?; +pub async fn list_remote(args: ArgsRef, proto: ResourceRef) { + let mut tool = proto.load_tool(&args.id).await?; tool.disable_caching(); debug!("Loading versions"); diff --git a/crates/cli/src/commands/migrate/mod.rs b/crates/cli/src/commands/migrate/mod.rs index 258fe1f22..2775ccc68 100644 --- a/crates/cli/src/commands/migrate/mod.rs +++ b/crates/cli/src/commands/migrate/mod.rs @@ -1,6 +1,8 @@ mod v0_20; +mod v0_24; use crate::error::ProtoCliError; +use crate::helpers::ProtoResource; use clap::Args; use starbase::system; @@ -11,10 +13,13 @@ pub struct MigrateArgs { } #[system] -pub async fn migrate(args: ArgsRef) { +pub async fn migrate(args: ArgsRef, proto: ResourceRef) { match args.operation.as_str() { "v0.20" => { - v0_20::migrate().await?; + v0_20::migrate(proto).await?; + } + "v0.24" => { + v0_24::migrate(proto).await?; } unknown => { return Err(ProtoCliError::UnknownMigration { diff --git a/crates/cli/src/commands/migrate/v0_20.rs b/crates/cli/src/commands/migrate/v0_20.rs index 6f82acf9d..6bdaef98c 100644 --- a/crates/cli/src/commands/migrate/v0_20.rs +++ b/crates/cli/src/commands/migrate/v0_20.rs @@ -1,14 +1,16 @@ -use crate::helpers::load_configured_tools; +#![allow(deprecated)] + +use crate::helpers::ProtoResource; use crate::shell; use proto_core::get_bin_dir; use starbase::SystemResult; use starbase_utils::fs; use tracing::{debug, info}; -pub async fn migrate() -> SystemResult { +pub async fn migrate(proto: &ProtoResource) -> SystemResult { info!("Loading tools..."); - let tools = load_configured_tools().await?; + let tools = proto.load_tools().await?; // Skips tools/plugins that are not in use let mut tools = tools diff --git a/crates/cli/src/commands/migrate/v0_24.rs b/crates/cli/src/commands/migrate/v0_24.rs new file mode 100644 index 000000000..1736d65dc --- /dev/null +++ b/crates/cli/src/commands/migrate/v0_24.rs @@ -0,0 +1,66 @@ +#![allow(deprecated)] + +use crate::helpers::ProtoResource; +use proto_core::ProtoConfig; +use starbase::SystemResult; +use starbase_styles::color; +use std::mem; +use tracing::{debug, info}; + +pub async fn migrate(proto: &ProtoResource) -> SystemResult { + info!("Loading tools..."); + + let tools = proto.load_tools().await?; + let mut config = ProtoConfig::load_from(proto.env.get_config_dir(true), false)?; + let mut updated_config = false; + + info!("Migrating configs..."); + + for tool in tools { + debug!("Checking {}", color::id(&tool.id)); + + let mut manifest = tool.manifest.clone(); + let mut updated_manifest = false; + + if !manifest.aliases.is_empty() { + debug!("Found aliases"); + + let tool_config = config + .tools + .get_or_insert(Default::default()) + .entry(tool.id.clone()) + .or_default(); + + tool_config + .aliases + .get_or_insert(Default::default()) + .extend(mem::take(&mut manifest.aliases)); + + updated_manifest = true; + } + + if manifest.default_version.is_some() { + debug!("Found default version"); + + config + .versions + .get_or_insert(Default::default()) + .insert(tool.id.clone(), manifest.default_version.take().unwrap()); + + updated_manifest = true; + } + + if updated_manifest { + updated_config = true; + manifest.save()?; + } + } + + if updated_config { + ProtoConfig::save_to(proto.env.get_config_dir(true), config)?; + } + + info!("Migration complete!"); + + Ok(()) +} diff --git a/crates/cli/src/commands/outdated.rs b/crates/cli/src/commands/outdated.rs index 6217900a9..da5ae7f81 100644 --- a/crates/cli/src/commands/outdated.rs +++ b/crates/cli/src/commands/outdated.rs @@ -1,7 +1,8 @@ use crate::error::ProtoCliError; +use crate::helpers::ProtoResource; use clap::Args; use miette::IntoDiagnostic; -use proto_core::{load_tool, ToolsConfig, UnresolvedVersionSpec, VersionSpec}; +use proto_core::{ProtoConfig, UnresolvedVersionSpec, VersionSpec}; use serde::Serialize; use starbase::system; use starbase_styles::color::{self, OwoStyle}; @@ -33,27 +34,23 @@ pub struct OutdatedItem { } #[system] -pub async fn outdated(args: ArgsRef) { - let mut tools_config = ToolsConfig::load_closest()?; - let initial_version = UnresolvedVersionSpec::default(); // latest +pub async fn outdated(args: ArgsRef, proto: ResourceRef) { + let config = proto.env.load_config()?; - if tools_config.tools.is_empty() { - return Err(ProtoCliError::NoConfiguredTools { - path: tools_config.path, - } - .into()); + if config.versions.is_empty() { + return Err(ProtoCliError::NoConfiguredTools.into()); } if !args.json { info!("Checking for newer versions..."); - info!("Loading {}", color::path(&tools_config.path)); } let mut items = HashMap::new(); let mut tool_versions = HashMap::new(); + let initial_version = UnresolvedVersionSpec::default(); // latest - for (tool_id, config_version) in &tools_config.tools { - let mut tool = load_tool(tool_id).await?; + for (tool_id, config_version) in &config.versions { + let mut tool = proto.load_tool(tool_id).await?; tool.disable_caching(); debug!("Checking {}", tool.get_name()); @@ -119,8 +116,12 @@ pub async fn outdated(args: ArgsRef) { } if args.update { - tools_config.tools.extend(tool_versions); - tools_config.save()?; + ProtoConfig::update(&proto.env.cwd, |config| { + config + .versions + .get_or_insert(Default::default()) + .extend(tool_versions); + })?; } if args.json { diff --git a/crates/cli/src/commands/pin.rs b/crates/cli/src/commands/pin.rs index 164b7e2a5..84e622683 100644 --- a/crates/cli/src/commands/pin.rs +++ b/crates/cli/src/commands/pin.rs @@ -1,7 +1,9 @@ +use crate::helpers::ProtoResource; use clap::Args; -use proto_core::{load_tool, Id, Tool, ToolsConfig, UnresolvedVersionSpec}; +use proto_core::{Id, ProtoConfig, Tool, UnresolvedVersionSpec}; use starbase::{system, SystemResult}; use starbase_styles::color; +use std::collections::BTreeMap; use tracing::{debug, info}; #[derive(Args, Clone, Debug)] @@ -14,44 +16,36 @@ pub struct PinArgs { #[arg( long, - help = "Add to the global user config instead of local .prototools" + help = "Pin to the global .prototools instead of local .prototools" )] pub global: bool, } 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()?; - - debug!( - version = args.spec.to_string(), - manifest = ?tool.manifest.path, - "Wrote the global version", - ); - - // Create symlink to this new version - if link { - tool.symlink_bins(true).await?; - } - } else { - let mut config = ToolsConfig::load()?; - config.tools.insert(args.id.clone(), args.spec.clone()); - config.save()?; - - debug!( - version = args.spec.to_string(), - config = ?config.path, - "Wrote the local version", - ); + // Create symlink to this new version + if args.global && link { + tool.symlink_bins(true).await?; } + let path = ProtoConfig::update(tool.proto.get_config_dir(args.global), |config| { + config + .versions + .get_or_insert(BTreeMap::default()) + .insert(args.id.clone(), args.spec.clone()); + })?; + + debug!( + version = args.spec.to_string(), + config = ?path, + "Pinned the version", + ); + Ok(()) } #[system] -pub async fn pin(args: ArgsRef) -> SystemResult { - let mut tool = load_tool(&args.id).await?; +pub async fn pin(args: ArgsRef, proto: ResourceRef) -> SystemResult { + let mut tool = proto.load_tool(&args.id).await?; internal_pin(&mut tool, args, false).await?; diff --git a/crates/cli/src/commands/run.rs b/crates/cli/src/commands/run.rs index ce7427124..72f9daabf 100644 --- a/crates/cli/src/commands/run.rs +++ b/crates/cli/src/commands/run.rs @@ -1,8 +1,9 @@ use crate::commands::install::{internal_install, InstallArgs}; use crate::error::ProtoCliError; +use crate::helpers::ProtoResource; use clap::Args; use miette::IntoDiagnostic; -use proto_core::{detect_version, load_tool, Id, ProtoError, Tool, UnresolvedVersionSpec}; +use proto_core::{detect_version, Id, ProtoError, Tool, UnresolvedVersionSpec}; use proto_pdk_api::{ExecutableConfig, RunHook}; use starbase::system; use std::env; @@ -142,8 +143,7 @@ fn create_command, A: AsRef>( if !parent_exe.ends_with(".exe") { use std::ffi::OsString; - let mut config = proto_core::ToolsConfig::load_upwards()?; - config.inherit_builtin_plugins(); + let config = tool.proto.load_config()?; // Attempt to use `proto run ` first instead of a hard-coded .exe. // This way we rely on proto's executable discovery functionality. @@ -179,8 +179,8 @@ fn create_command, A: AsRef>( } #[system] -pub async fn run(args: ArgsRef) -> SystemResult { - let mut tool = load_tool(&args.id).await?; +pub async fn run(args: ArgsRef, proto: ResourceRef) -> SystemResult { + let mut tool = proto.load_tool(&args.id).await?; // Avoid running the tool's native self-upgrade as it conflicts with proto if is_trying_to_self_upgrade(&tool, &args.passthrough) { @@ -192,11 +192,12 @@ pub async fn run(args: ArgsRef) -> SystemResult { } let version = detect_version(&tool, args.spec.clone()).await?; - let user_config = tool.proto.load_user_config()?; // Check if installed or install if !tool.is_setup(&version).await? { - if !user_config.auto_install { + let config = tool.proto.load_config()?; + + if !config.settings.auto_install { return Err(ProtoError::MissingToolForRun { tool: tool.get_name().to_owned(), version: version.to_string(), @@ -209,6 +210,7 @@ pub async fn run(args: ArgsRef) -> SystemResult { debug!("Auto-install setting is configured, attempting to install"); tool = internal_install( + proto, InstallArgs { canary: false, id: args.id.clone(), diff --git a/crates/cli/src/commands/setup.rs b/crates/cli/src/commands/setup.rs index 7adab809f..ba0beafba 100644 --- a/crates/cli/src/commands/setup.rs +++ b/crates/cli/src/commands/setup.rs @@ -1,7 +1,7 @@ +use crate::helpers::ProtoResource; use crate::shell::detect_shell; use clap::Args; use clap_complete::Shell; -use proto_core::ProtoEnvironment; use starbase::system; use std::env; use std::path::PathBuf; @@ -17,20 +17,23 @@ pub struct SetupArgs { } #[system] -pub async fn setup(args: ArgsRef) { +pub async fn setup(args: ArgsRef, proto: ResourceRef) { let shell = detect_shell(args.shell); - let proto = ProtoEnvironment::new()?; let paths = env::var("PATH").expect("Missing PATH!"); let paths = env::split_paths(&paths).collect::>(); - if paths.contains(&proto.shims_dir) || paths.contains(&proto.bin_dir) { - debug!("Skipping setup, PROTO_HOME already exists in PATH."); + if paths.contains(&proto.env.shims_dir) || paths.contains(&proto.env.bin_dir) { + debug!("Skipping setup, PROTO_HOME already exists in PATH"); return Ok(()); } - do_setup(shell, vec![proto.shims_dir, proto.bin_dir], args.profile)?; + do_setup( + shell, + vec![proto.env.shims_dir.clone(), proto.env.bin_dir.clone()], + args.profile, + )?; } // For other shells, write environment variable(s) to an applicable profile! diff --git a/crates/cli/src/commands/tool/add.rs b/crates/cli/src/commands/tool/add.rs index 7fc6540da..e569eb6de 100644 --- a/crates/cli/src/commands/tool/add.rs +++ b/crates/cli/src/commands/tool/add.rs @@ -1,5 +1,6 @@ +use crate::helpers::ProtoResource; use clap::Args; -use proto_core::{Id, PluginLocator, ToolsConfig, UserConfig}; +use proto_core::{Id, PluginLocator, ProtoConfig}; use starbase::system; use starbase_styles::color; use tracing::info; @@ -14,36 +15,23 @@ pub struct AddToolArgs { #[arg( long, - help = "Add to the global user config instead of local .prototools" + help = "Add to the global .prototools instead of local .prototools" )] global: bool, } #[system] -pub async fn add(args: ArgsRef) { - if args.global { - let mut user_config = UserConfig::load()?; - user_config +pub async fn add(args: ArgsRef, proto: ResourceRef) { + let config_path = ProtoConfig::update(proto.env.get_config_dir(args.global), |config| { + config .plugins + .get_or_insert(Default::default()) .insert(args.id.clone(), args.plugin.clone()); - user_config.save()?; - - info!( - "Added plugin {} to global {}", - color::id(&args.id), - color::path(&user_config.path), - ); - - return Ok(()); - } - - let mut config = ToolsConfig::load()?; - config.plugins.insert(args.id.clone(), args.plugin.clone()); - config.save()?; + })?; info!( - "Added plugin {} to local {}", + "Added plugin {} to config {}", color::id(&args.id), - color::path(&config.path) + color::path(config_path) ); } diff --git a/crates/cli/src/commands/tool/info.rs b/crates/cli/src/commands/tool/info.rs index cfd5dfa26..f352583b6 100644 --- a/crates/cli/src/commands/tool/info.rs +++ b/crates/cli/src/commands/tool/info.rs @@ -1,7 +1,8 @@ +use crate::helpers::ProtoResource; use crate::printer::Printer; use clap::Args; use miette::IntoDiagnostic; -use proto_core::{detect_version, load_tool, ExecutableLocation, Id, PluginLocator}; +use proto_core::{detect_version, ExecutableLocation, Id, PluginLocator}; use proto_pdk_api::ToolMetadataOutput; use serde::Serialize; use starbase::system; @@ -33,8 +34,8 @@ pub struct ToolInfoArgs { } #[system] -pub async fn info(args: ArgsRef) { - let mut tool = load_tool(&args.id).await?; +pub async fn info(args: ArgsRef, proto: ResourceRef) { + let mut tool = proto.load_tool(&args.id).await?; let version = detect_version(&tool, None).await?; tool.resolve_version(&version, false).await?; diff --git a/crates/cli/src/commands/tool/list.rs b/crates/cli/src/commands/tool/list.rs index c6dfbaa95..f15510402 100644 --- a/crates/cli/src/commands/tool/list.rs +++ b/crates/cli/src/commands/tool/list.rs @@ -1,16 +1,23 @@ use crate::error::ProtoCliError; -use crate::helpers::load_configured_tools_with_filters; +use crate::helpers::ProtoResource; use crate::printer::Printer; use chrono::{DateTime, NaiveDateTime}; use clap::Args; use miette::IntoDiagnostic; -use proto_core::Id; +use proto_core::{Id, ProtoToolConfig, ToolManifest}; +use serde::Serialize; use starbase::system; use starbase_styles::color; use starbase_utils::json; use std::collections::{HashMap, HashSet}; use tracing::info; +#[derive(Serialize)] +pub struct ToolItem<'a> { + manifest: ToolManifest, + config: Option<&'a ProtoToolConfig>, +} + #[derive(Args, Clone, Debug)] pub struct ListToolsArgs { #[arg(help = "ID of tools to list")] @@ -21,12 +28,14 @@ pub struct ListToolsArgs { } #[system] -pub async fn list(args: ArgsRef) { +pub async fn list(args: ArgsRef, proto: ResourceRef) { if !args.json { info!("Loading tools..."); } - let tools = load_configured_tools_with_filters(HashSet::from_iter(&args.ids)).await?; + let tools = proto + .load_tools_with_filters(HashSet::from_iter(&args.ids)) + .await?; let mut tools = tools .into_iter() @@ -39,10 +48,22 @@ pub async fn list(args: ArgsRef) { return Err(ProtoCliError::NoInstalledTools.into()); } + let config = proto.env.load_config()?.to_owned(); + if args.json { let items = tools .into_iter() - .map(|t| (t.id, t.manifest)) + .map(|t| { + let tool_config = config.tools.get(&t.id); + + ( + t.id, + ToolItem { + manifest: t.manifest, + config: tool_config, + }, + ) + }) .collect::>(); println!("{}", json::to_string_pretty(&items).into_diagnostic()?); @@ -53,6 +74,8 @@ pub async fn list(args: ArgsRef) { let mut printer = Printer::new(); for tool in tools { + let tool_config = config.tools.get(&tool.id); + printer.line(); printer.header(&tool.id, &tool.metadata.name); @@ -61,8 +84,9 @@ pub async fn list(args: ArgsRef) { p.entry_map( "Aliases", - tool.manifest - .aliases + tool_config + .map(|cfg| cfg.aliases.clone()) + .unwrap_or_default() .iter() .map(|(k, v)| (color::hash(v.to_string()), color::label(k))) .collect::>(), @@ -92,10 +116,9 @@ pub async fn list(args: ArgsRef) { } } - if tool - .manifest - .default_version - .as_ref() + if config + .versions + .get(&tool.id) .is_some_and(|dv| *dv == version.to_unresolved_spec()) { comments.push("default version".into()); diff --git a/crates/cli/src/commands/tool/list_plugins.rs b/crates/cli/src/commands/tool/list_plugins.rs index 635a62912..0cc064137 100644 --- a/crates/cli/src/commands/tool/list_plugins.rs +++ b/crates/cli/src/commands/tool/list_plugins.rs @@ -1,4 +1,4 @@ -use crate::helpers::load_configured_tools; +use crate::helpers::ProtoResource; use crate::printer::Printer; use clap::Args; use miette::IntoDiagnostic; @@ -23,12 +23,12 @@ pub struct ListToolPluginsArgs { } #[system] -pub async fn list_plugins(args: ArgsRef) { +pub async fn list_plugins(args: ArgsRef, proto: ResourceRef) { if !args.json { info!("Loading plugins..."); } - let tools = load_configured_tools().await?; + let tools = proto.load_tools().await?; let mut items = tools .into_iter() diff --git a/crates/cli/src/commands/tool/remove.rs b/crates/cli/src/commands/tool/remove.rs index 72fd376c4..83e8d8963 100644 --- a/crates/cli/src/commands/tool/remove.rs +++ b/crates/cli/src/commands/tool/remove.rs @@ -1,10 +1,9 @@ use crate::error::ProtoCliError; +use crate::helpers::ProtoResource; use clap::Args; -use proto_core::{Id, ToolsConfig, UserConfig, TOOLS_CONFIG_NAME}; +use proto_core::{Id, ProtoConfig, PROTO_CONFIG_NAME}; use starbase::system; use starbase_styles::color; -use std::env; -use std::path::PathBuf; use tracing::info; #[derive(Args, Clone, Debug)] @@ -14,41 +13,30 @@ pub struct RemoveToolArgs { #[arg( long, - help = "Remove from the global user config instead of local .prototools" + help = "Remove from the global .prototools instead of local .prototools" )] global: bool, } #[system] -pub async fn remove(args: ArgsRef) { - if args.global { - let mut user_config = UserConfig::load()?; - user_config.plugins.remove(&args.id); - user_config.save()?; +pub async fn remove(args: ArgsRef, proto: ResourceRef) { + if !args.global { + let config_path = proto.env.cwd.join(PROTO_CONFIG_NAME); - info!( - "Removed plugin {} from global {}", - color::id(&args.id), - color::path(&user_config.path), - ); - - return Ok(()); - } - - let local_path = env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); - let config_path = local_path.join(TOOLS_CONFIG_NAME); - - if !config_path.exists() { - return Err(ProtoCliError::MissingToolsConfigInCwd { path: config_path }.into()); + if !config_path.exists() { + return Err(ProtoCliError::MissingToolsConfigInCwd { path: config_path }.into()); + } } - let mut config = ToolsConfig::load_from(local_path)?; - config.plugins.remove(&args.id); - config.save()?; + let config_path = ProtoConfig::update(proto.env.get_config_dir(args.global), |config| { + if let Some(plugins) = &mut config.plugins { + plugins.remove(&args.id); + } + })?; info!( - "Removed plugin {} from local {}", + "Removed plugin {} from config {}", color::id(&args.id), - color::path(&config.path) + color::path(config_path) ); } diff --git a/crates/cli/src/commands/unalias.rs b/crates/cli/src/commands/unalias.rs index b711263e2..ff9deaaba 100644 --- a/crates/cli/src/commands/unalias.rs +++ b/crates/cli/src/commands/unalias.rs @@ -1,5 +1,6 @@ +use crate::helpers::ProtoResource; use clap::Args; -use proto_core::{load_tool, Id}; +use proto_core::{Id, ProtoConfig}; use starbase::system; use starbase_styles::color; use tracing::info; @@ -11,14 +12,28 @@ pub struct UnaliasArgs { #[arg(required = true, help = "Alias name")] alias: String, + + #[arg( + long, + help = "Remove from the global .prototools instead of local .prototools" + )] + global: bool, } #[system] -pub async fn unalias(args: ArgsRef) { - let mut tool = load_tool(&args.id).await?; +pub async fn unalias(args: ArgsRef, proto: ResourceRef) { + let tool = proto.load_tool(&args.id).await?; + let mut value = None; - let value = tool.manifest.aliases.remove(&args.alias); - tool.manifest.save()?; + ProtoConfig::update(tool.proto.get_config_dir(args.global), |config| { + if let Some(tool_configs) = &mut config.tools { + if let Some(tool_config) = tool_configs.get_mut(&tool.id) { + if let Some(aliases) = &mut tool_config.aliases { + value = aliases.remove(&args.alias); + } + } + } + })?; if let Some(version) = value { info!( diff --git a/crates/cli/src/commands/uninstall.rs b/crates/cli/src/commands/uninstall.rs index 0d1a54e5a..e0d01ee74 100644 --- a/crates/cli/src/commands/uninstall.rs +++ b/crates/cli/src/commands/uninstall.rs @@ -1,7 +1,7 @@ use crate::commands::clean::purge_tool; -use crate::helpers::{create_progress_bar, disable_progress_bars}; +use crate::helpers::{create_progress_bar, disable_progress_bars, ProtoResource}; use clap::Args; -use proto_core::{load_tool, Id, UnresolvedVersionSpec}; +use proto_core::{Id, UnresolvedVersionSpec}; use starbase::system; use tracing::{debug, info}; @@ -18,16 +18,16 @@ pub struct UninstallArgs { } #[system] -pub async fn uninstall(args: ArgsRef) { +pub async fn uninstall(args: ArgsRef, proto: ResourceRef) { // Uninstall everything let Some(spec) = &args.semver else { - purge_tool(&args.id, args.yes).await?; + purge_tool(proto, &args.id, args.yes).await?; return Ok(()); }; // Uninstall a tool by version - let mut tool = load_tool(&args.id).await?; + let mut tool = proto.load_tool(&args.id).await?; if !tool.is_setup(spec).await? { info!( diff --git a/crates/cli/src/commands/uninstall_global.rs b/crates/cli/src/commands/uninstall_global.rs index 6d3e5effe..04500754f 100644 --- a/crates/cli/src/commands/uninstall_global.rs +++ b/crates/cli/src/commands/uninstall_global.rs @@ -1,7 +1,7 @@ use crate::error::ProtoCliError; -use crate::helpers::create_progress_bar; +use crate::helpers::{create_progress_bar, ProtoResource}; use clap::Args; -use proto_core::{detect_version, load_tool, Id}; +use proto_core::{detect_version, Id}; use starbase::system; use starbase_styles::color; use tracing::{debug, info}; @@ -16,8 +16,11 @@ pub struct UninstallGlobalArgs { } #[system] -pub async fn uninstall_global(args: ArgsRef) { - let mut tool = load_tool(&args.id).await?; +pub async fn uninstall_global( + args: ArgsRef, + proto: ResourceRef, +) { + let mut tool = proto.load_tool(&args.id).await?; let version = detect_version(&tool, None).await?; // Resolve a version as some tools install to a versioned folder diff --git a/crates/cli/src/commands/upgrade.rs b/crates/cli/src/commands/upgrade.rs index 6f37c6b4a..4998d3776 100644 --- a/crates/cli/src/commands/upgrade.rs +++ b/crates/cli/src/commands/upgrade.rs @@ -1,7 +1,7 @@ use crate::error::ProtoCliError; -use crate::helpers::download_to_temp_with_progress_bar; +use crate::helpers::{download_to_temp_with_progress_bar, ProtoResource}; use miette::IntoDiagnostic; -use proto_core::{is_offline, ProtoEnvironment}; +use proto_core::is_offline; use semver::Version; use starbase::system; use starbase_archive::Archiver; @@ -35,12 +35,11 @@ pub fn is_musl() -> bool { } #[system] -pub async fn upgrade() { +pub async fn upgrade(proto: ResourceRef) { if is_offline() { return Err(ProtoCliError::UpgradeRequiresInternet.into()); } - let proto = ProtoEnvironment::new()?; let current_version = env!("CARGO_PKG_VERSION"); let latest_version = fetch_version().await?; @@ -83,7 +82,7 @@ pub async fn upgrade() { "https://github.com/moonrepo/proto/releases/download/v{latest_version}/{download_file}" ); let temp_file = download_to_temp_with_progress_bar(&download_url, &download_file).await?; - let temp_dir = proto.temp_dir.join(&target_file); + let temp_dir = proto.env.temp_dir.join(&target_file); // Unpack the downloaded file Archiver::new(&temp_dir, &temp_file).unpack_from_ext()?; @@ -93,10 +92,11 @@ pub async fn upgrade() { let bin_path = match env::var("PROTO_INSTALL_DIR") { Ok(dir) => PathBuf::from(dir).join(bin_name), - Err(_) => proto.bin_dir.join(bin_name), + Err(_) => proto.env.bin_dir.join(bin_name), }; let relocate_path = proto + .env .tools_dir .join("proto") .join(current_version) diff --git a/crates/cli/src/error.rs b/crates/cli/src/error.rs index c3f4f8049..bb41a6e66 100644 --- a/crates/cli/src/error.rs +++ b/crates/cli/src/error.rs @@ -1,5 +1,5 @@ use miette::Diagnostic; -use proto_core::TOOLS_CONFIG_NAME; +use proto_core::PROTO_CONFIG_NAME; use starbase_styles::{Style, Stylize}; use std::path::PathBuf; use thiserror::Error; @@ -17,7 +17,7 @@ pub enum ProtoCliError { #[diagnostic(code(proto::cli::missing_tools_config))] #[error( "No {} has been found in current directory. Attempted to find at {}.", - TOOLS_CONFIG_NAME.style(Style::File), + PROTO_CONFIG_NAME.style(Style::File), .path.style(Style::Path), )] MissingToolsConfigInCwd { path: PathBuf }, @@ -31,8 +31,8 @@ pub enum ProtoCliError { MissingRunAltBin { bin: String, path: PathBuf }, #[diagnostic(code(proto::cli::no_configured_tools))] - #[error("No tools have been configured in {}.", .path.style(Style::Path))] - NoConfiguredTools { path: PathBuf }, + #[error("No tools have been configured in {}.", PROTO_CONFIG_NAME.style(Style::File),)] + NoConfiguredTools, #[diagnostic(code(proto::cli::no_installed_tools))] #[error("No tools have been installed.")] diff --git a/crates/cli/src/helpers.rs b/crates/cli/src/helpers.rs index f6316e6a6..01ed463a3 100644 --- a/crates/cli/src/helpers.rs +++ b/crates/cli/src/helpers.rs @@ -2,12 +2,13 @@ use futures::StreamExt; use indicatif::{ProgressBar, ProgressStyle}; use miette::IntoDiagnostic; use proto_core::{ - get_temp_dir, load_schema_plugin, load_tool_from_locator, Id, ProtoEnvironment, ProtoError, - Tool, ToolsConfig, UserConfig, SCHEMA_PLUGIN_KEY, + get_temp_dir, load_schema_plugin_with_proto, load_tool_from_locator, load_tool_with_proto, Id, + ProtoEnvironment, ProtoError, Tool, SCHEMA_PLUGIN_KEY, }; +use starbase::Resource; use starbase_utils::fs; use std::cmp; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; use std::env; use std::io::Write; use std::path::PathBuf; @@ -90,54 +91,39 @@ pub async fn download_to_temp_with_progress_bar( Ok(temp_file) } -pub async fn load_configured_tools() -> miette::Result> { - ToolsLoader::new()?.load_tools().await +#[derive(Clone, Resource)] +pub struct ProtoResource { + pub env: Arc, } -pub async fn load_configured_tools_with_filters(filter: HashSet<&Id>) -> miette::Result> { - ToolsLoader::new()?.load_tools_with_filters(filter).await -} - -pub struct ToolsLoader { - pub proto: Arc, - pub tools_config: ToolsConfig, - pub user_config: Arc, -} - -impl ToolsLoader { +impl ProtoResource { pub fn new() -> miette::Result { - let proto = ProtoEnvironment::new()?; - let user_config = proto.load_user_config()?; - - let mut tools_config = ToolsConfig::load_upwards_from(&proto.cwd, false)?; - tools_config.inherit_builtin_plugins(); - Ok(Self { - proto: Arc::new(proto), - tools_config, - user_config: Arc::new(user_config), + env: Arc::new(ProtoEnvironment::new()?), }) } + pub async fn load_tool(&self, id: &Id) -> miette::Result { + load_tool_with_proto(id, &self.env).await + } + pub async fn load_tools(&self) -> miette::Result> { self.load_tools_with_filters(HashSet::new()).await } pub async fn load_tools_with_filters(&self, filter: HashSet<&Id>) -> miette::Result> { - let mut plugins = HashMap::new(); - plugins.extend(&self.user_config.plugins); - plugins.extend(&self.tools_config.plugins); + let config = self.env.load_config()?; // Download the schema plugin before loading plugins. // We must do this here, otherwise when multiple schema // based tools are installed in parallel, they will // collide when attempting to download the schema plugin! - load_schema_plugin(&self.proto, &self.user_config).await?; + load_schema_plugin_with_proto(&self.env).await?; let mut futures = vec![]; let mut tools = vec![]; - for (id, locator) in plugins { + for (id, locator) in &config.plugins { if !filter.is_empty() && !filter.contains(id) { continue; } @@ -149,11 +135,10 @@ impl ToolsLoader { let id = id.to_owned(); let locator = locator.to_owned(); - let proto = Arc::clone(&self.proto); - let user_config = Arc::clone(&self.user_config); + let proto = Arc::clone(&self.env); futures.push(tokio::spawn(async move { - load_tool_from_locator(id, proto, locator, &user_config).await + load_tool_from_locator(id, proto, locator).await })); } diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index e12f867ca..4dd96fce7 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -4,6 +4,7 @@ mod error; mod helpers; mod printer; mod shell; +mod systems; use app::{App as CLI, Commands, ToolCommands}; use clap::Parser; @@ -47,6 +48,9 @@ async fn main() -> MainResult { ); let mut app = App::new(); + app.startup(systems::detect_proto_env); + app.startup(systems::migrate_user_config); + app.analyze(systems::load_proto_configs); match cli.command { Commands::AddPlugin(args) => app.execute_with_args(commands::add_plugin_old, args), diff --git a/crates/cli/src/systems.rs b/crates/cli/src/systems.rs new file mode 100644 index 000000000..3c5baea99 --- /dev/null +++ b/crates/cli/src/systems.rs @@ -0,0 +1,70 @@ +#![allow(deprecated)] + +use crate::helpers::ProtoResource; +use proto_core::{Id, ProtoConfig, UserConfig, PROTO_CONFIG_NAME, USER_CONFIG_NAME}; +use starbase::system; +use starbase_utils::fs; +use starbase_utils::json::JsonValue; +use tracing::debug; + +// STARTUP + +#[system] +pub fn detect_proto_env(resources: ResourcesMut) { + resources.set(ProtoResource::new()?); +} + +#[system] +pub fn migrate_user_config(proto: ResourceRef) { + let dir = proto.env.get_config_dir(true); + let old_file = dir.join(USER_CONFIG_NAME); + + if !old_file.exists() { + return Ok(()); + } + + let new_file = dir.join(PROTO_CONFIG_NAME); + + debug!( + old_file = ?old_file, + new_file = ?new_file, + "Detected legacy user config file, migrating to new format", + ); + + let user_config = UserConfig::load_from(dir)?; + + ProtoConfig::update(dir, |config| { + let settings = config.settings.get_or_insert(Default::default()); + settings.auto_clean = user_config.auto_clean; + settings.auto_install = user_config.auto_install; + settings.detect_strategy = user_config.detect_strategy; + settings.http = user_config.http; + settings.pin_latest = user_config.pin_latest; + + if !user_config.plugins.is_empty() { + let plugins = config.plugins.get_or_insert(Default::default()); + plugins.extend(user_config.plugins); + } + + if let Some(node_intercept_globals) = user_config.node_intercept_globals { + let tools = config.tools.get_or_insert(Default::default()); + let node_config = tools.entry(Id::raw("node")).or_default(); + + node_config.config.get_or_insert(Default::default()).insert( + "intercept-globals".into(), + JsonValue::Bool(node_intercept_globals), + ); + } + })?; + + debug!(file = ?old_file, "Migrated, deleting legacy user config file"); + + fs::remove_file(old_file)?; +} + +// ANALYZE + +#[system] +pub fn load_proto_configs(proto: ResourceMut) { + proto.env.load_config()?; +} diff --git a/crates/cli/tests/alias_test.rs b/crates/cli/tests/alias_test.rs index 2f3cb7a19..58a6f50ae 100644 --- a/crates/cli/tests/alias_test.rs +++ b/crates/cli/tests/alias_test.rs @@ -1,11 +1,11 @@ mod utils; -use proto_core::{ToolManifest, UnresolvedVersionSpec}; +use proto_core::{Id, PartialProtoToolConfig, ProtoConfig, UnresolvedVersionSpec}; use starbase_sandbox::predicates::prelude::*; use std::collections::BTreeMap; use utils::*; -mod alias { +mod alias_local { use super::*; #[test] @@ -24,26 +24,27 @@ mod alias { } #[test] - fn updates_manifest_file() { + fn updates_config_file() { let sandbox = create_empty_sandbox(); - let manifest_file = sandbox.path().join("tools/node/manifest.json"); + let config_file = sandbox.path().join(".prototools"); - assert!(!manifest_file.exists()); + assert!(!config_file.exists()); let mut cmd = create_proto_command(sandbox.path()); cmd.arg("alias") .arg("node") .arg("example") .arg("19.0.0") + .current_dir(sandbox.path()) .assert() .success(); - assert!(manifest_file.exists()); + assert!(config_file.exists()); - let manifest = ToolManifest::load(manifest_file).unwrap(); + let config = load_config(sandbox.path()); assert_eq!( - manifest.aliases, + config.tools.get("node").unwrap().aliases, BTreeMap::from_iter([( "example".into(), UnresolvedVersionSpec::parse("19.0.0").unwrap() @@ -54,14 +55,20 @@ mod alias { #[test] fn can_overwrite_existing_alias() { let sandbox = create_empty_sandbox(); - let manifest_file = sandbox.path().join("tools/node/manifest.json"); - let mut manifest = ToolManifest::load(&manifest_file).unwrap(); - manifest.aliases.insert( - "example".into(), - UnresolvedVersionSpec::parse("19.0.0").unwrap(), - ); - manifest.save().unwrap(); + ProtoConfig::update(sandbox.path(), |config| { + config.tools.get_or_insert(Default::default()).insert( + Id::raw("node"), + PartialProtoToolConfig { + aliases: Some(BTreeMap::from_iter([( + "example".into(), + UnresolvedVersionSpec::parse("19.0.0").unwrap(), + )])), + ..Default::default() + }, + ); + }) + .unwrap(); let mut cmd = create_proto_command(sandbox.path()); cmd.arg("alias") @@ -71,10 +78,10 @@ mod alias { .assert() .success(); - let manifest = ToolManifest::load(&manifest_file).unwrap(); + let config = load_config(sandbox.path()); assert_eq!( - manifest.aliases, + config.tools.get("node").unwrap().aliases, BTreeMap::from_iter([( "example".into(), UnresolvedVersionSpec::parse("20.0.0").unwrap() @@ -85,9 +92,6 @@ mod alias { #[test] fn errors_when_using_version() { let sandbox = create_empty_sandbox(); - let manifest_file = sandbox.path().join("tools/node/manifest.json"); - - assert!(!manifest_file.exists()); let mut cmd = create_proto_command(sandbox.path()); let assert = cmd @@ -105,9 +109,6 @@ mod alias { #[test] fn errors_when_aliasing_self() { let sandbox = create_empty_sandbox(); - let manifest_file = sandbox.path().join("tools/node/manifest.json"); - - assert!(!manifest_file.exists()); let mut cmd = create_proto_command(sandbox.path()); let assert = cmd @@ -120,3 +121,36 @@ mod alias { assert.stderr(predicate::str::contains("Cannot map an alias to itself.")); } } + +mod alias_global { + use super::*; + + #[test] + fn updates_config_file() { + let sandbox = create_empty_sandbox(); + let config_file = sandbox.path().join(".proto/.prototools"); + + assert!(!config_file.exists()); + + let mut cmd = create_proto_command(sandbox.path()); + cmd.arg("alias") + .arg("node") + .arg("example") + .arg("19.0.0") + .arg("--global") + .assert() + .success(); + + assert!(config_file.exists()); + + let config = load_config(sandbox.path().join(".proto")); + + assert_eq!( + config.tools.get("node").unwrap().aliases, + BTreeMap::from_iter([( + "example".into(), + UnresolvedVersionSpec::parse("19.0.0").unwrap() + )]) + ); + } +} diff --git a/crates/cli/tests/clean_test.rs b/crates/cli/tests/clean_test.rs index b4da45aba..21e711a7f 100644 --- a/crates/cli/tests/clean_test.rs +++ b/crates/cli/tests/clean_test.rs @@ -16,8 +16,8 @@ mod clean { #[test] fn purges_tool_inventory() { let sandbox = create_empty_sandbox(); - sandbox.create_file("tools/node/1.2.3/index.js", ""); - sandbox.create_file("tools/node/4.5.6/index.js", ""); + sandbox.create_file(".proto/tools/node/1.2.3/index.js", ""); + sandbox.create_file(".proto/tools/node/4.5.6/index.js", ""); let mut cmd = create_proto_command(sandbox.path()); cmd.arg("clean") @@ -27,25 +27,31 @@ mod clean { .assert() .success(); - assert!(!sandbox.path().join("tools/node/1.2.3/index.js").exists()); - assert!(!sandbox.path().join("tools/node/4.5.6/index.js").exists()); + assert!(!sandbox + .path() + .join(".proto/tools/node/1.2.3/index.js") + .exists()); + assert!(!sandbox + .path() + .join(".proto/tools/node/4.5.6/index.js") + .exists()); } #[cfg(not(windows))] #[test] fn purges_tool_bin() { let sandbox = create_empty_sandbox(); - sandbox.create_file("tools/node/fake/file", ""); - sandbox.create_file("bin/other", ""); + sandbox.create_file(".proto/tools/node/fake/file", ""); + sandbox.create_file(".proto/bin/other", ""); - let bin = sandbox.path().join(if cfg!(windows) { + let bin = sandbox.path().join(".proto").join(if cfg!(windows) { "bin/node.exe" } else { "bin/node" }); #[allow(deprecated)] - std::fs::soft_link(sandbox.path().join("tools/node/fake/file"), &bin).unwrap(); + std::fs::soft_link(sandbox.path().join(".proto/tools/node/fake/file"), &bin).unwrap(); let mut cmd = create_proto_command(sandbox.path()); cmd.arg("clean") @@ -62,10 +68,10 @@ mod clean { #[test] fn purges_tool_shims() { let sandbox = create_empty_sandbox(); - sandbox.create_file("shims/npm", ""); - sandbox.create_file("shims/npm.cmd", ""); - sandbox.create_file("shims/npx", ""); - sandbox.create_file("shims/npx.cmd", ""); + sandbox.create_file(".proto/shims/npm", ""); + sandbox.create_file(".proto/shims/npm.cmd", ""); + sandbox.create_file(".proto/shims/npx", ""); + sandbox.create_file(".proto/shims/npx.cmd", ""); let mut cmd = create_proto_command(sandbox.path()); cmd.arg("clean") @@ -76,19 +82,19 @@ mod clean { .success(); if cfg!(windows) { - assert!(!sandbox.path().join("shims/npm.cmd").exists()); - assert!(!sandbox.path().join("shims/npx.cmd").exists()); + assert!(!sandbox.path().join(".proto/shims/npm.cmd").exists()); + assert!(!sandbox.path().join(".proto/shims/npx.cmd").exists()); } else { - assert!(!sandbox.path().join("shims/npm").exists()); - assert!(!sandbox.path().join("shims/npx").exists()); + assert!(!sandbox.path().join(".proto/shims/npm").exists()); + assert!(!sandbox.path().join(".proto/shims/npx").exists()); } } #[test] fn purges_plugins() { let sandbox = create_empty_sandbox(); - sandbox.create_file("plugins/node_plugin.wasm", ""); - sandbox.create_file("plugins/npm_plugin.wasm", ""); + sandbox.create_file(".proto/plugins/node_plugin.wasm", ""); + sandbox.create_file(".proto/plugins/npm_plugin.wasm", ""); let mut cmd = create_proto_command(sandbox.path()); cmd.arg("clean") @@ -97,7 +103,13 @@ mod clean { .assert() .success(); - assert!(!sandbox.path().join("plugins/node_plugin.wasm").exists()); - assert!(!sandbox.path().join("plugins/npm_plugin.wasm").exists()); + assert!(!sandbox + .path() + .join(".proto/plugins/node_plugin.wasm") + .exists()); + assert!(!sandbox + .path() + .join(".proto/plugins/npm_plugin.wasm") + .exists()); } } diff --git a/crates/cli/tests/install_uninstall_test.rs b/crates/cli/tests/install_uninstall_test.rs index 07dffe552..2402a953d 100644 --- a/crates/cli/tests/install_uninstall_test.rs +++ b/crates/cli/tests/install_uninstall_test.rs @@ -1,6 +1,6 @@ mod utils; -use proto_core::{ToolManifest, ToolsConfig, UnresolvedVersionSpec, VersionSpec}; +use proto_core::{Id, PinType, ProtoConfig, ToolManifest, UnresolvedVersionSpec, VersionSpec}; use starbase_sandbox::predicates::prelude::*; use std::collections::HashSet; use utils::*; @@ -10,9 +10,9 @@ mod install_uninstall { #[test] fn installs_without_patch() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("install") .arg("node") .arg("18.12") @@ -21,14 +21,14 @@ mod install_uninstall { .assert() .success(); - assert!(temp.path().join("tools/node/18.12.1").exists()); + assert!(sandbox.path().join(".proto/tools/node/18.12.1").exists()); } #[test] fn installs_without_minor() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("install") .arg("node") .arg("17") @@ -37,14 +37,14 @@ mod install_uninstall { .assert() .success(); - assert!(temp.path().join("tools/node/17.9.1").exists()); + assert!(sandbox.path().join(".proto/tools/node/17.9.1").exists()); } #[test] fn installs_from_alias() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("install") .arg("node") .arg("gallium") @@ -53,18 +53,18 @@ mod install_uninstall { .assert() .success(); - assert!(temp.path().join("tools/node/16.20.2").exists()); + assert!(sandbox.path().join(".proto/tools/node/16.20.2").exists()); } #[test] fn installs_and_uninstalls_tool() { - let temp = create_empty_sandbox(); - let tool_dir = temp.path().join("tools/node/19.0.0"); + let sandbox = create_empty_sandbox(); + let tool_dir = sandbox.path().join(".proto/tools/node/19.0.0"); assert!(!tool_dir.exists()); // Install - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); let assert = cmd .arg("install") .arg("node") @@ -78,7 +78,7 @@ mod install_uninstall { assert.stderr(predicate::str::contains("Node.js has been installed")); // Uninstall - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); let assert = cmd.arg("uninstall").arg("node").arg("19.0.0").assert(); assert!(!tool_dir.exists()); @@ -90,9 +90,9 @@ mod install_uninstall { #[test] fn doesnt_install_tool_if_exists() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("install") .arg("node") .arg("19.0.0") @@ -101,7 +101,7 @@ mod install_uninstall { .assert() .success(); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); let assert = cmd .arg("install") .arg("node") @@ -117,9 +117,9 @@ mod install_uninstall { #[test] fn creates_all_shims() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("install") .arg("node") .arg("19.0.0") @@ -128,21 +128,21 @@ mod install_uninstall { .assert(); if cfg!(windows) { - assert!(temp.path().join("shims/node").exists()); - assert!(temp.path().join("shims/node.cmd").exists()); - assert!(temp.path().join("shims/node.ps1").exists()); + assert!(sandbox.path().join(".proto/shims/node").exists()); + assert!(sandbox.path().join(".proto/shims/node.cmd").exists()); + assert!(sandbox.path().join(".proto/shims/node.ps1").exists()); } else { - assert!(temp.path().join("shims/node").exists()); + assert!(sandbox.path().join(".proto/shims/node").exists()); } } #[test] fn updates_the_manifest_when_installing() { - let temp = create_empty_sandbox(); - let manifest_file = temp.path().join("tools/node/manifest.json"); + let sandbox = create_empty_sandbox(); + let manifest_file = sandbox.path().join(".proto/tools/node/manifest.json"); // Install - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("install") .arg("node") .arg("19.0.0") @@ -152,10 +152,11 @@ mod install_uninstall { .success(); let manifest = ToolManifest::load(&manifest_file).unwrap(); + let config = load_config(sandbox.path().join(".proto")); assert_eq!( - manifest.default_version, - Some(UnresolvedVersionSpec::parse("19.0.0").unwrap()) + config.versions.get("node").unwrap(), + &UnresolvedVersionSpec::parse("19.0.0").unwrap() ); assert_eq!( manifest.installed_versions, @@ -166,7 +167,7 @@ mod install_uninstall { .contains_key(&VersionSpec::parse("19.0.0").unwrap())); // Uninstall - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("uninstall") .arg("node") .arg("19.0.0") @@ -174,241 +175,300 @@ mod install_uninstall { .success(); let manifest = ToolManifest::load(&manifest_file).unwrap(); + let config = load_config(sandbox.path().join(".proto")); - assert_eq!(manifest.default_version, None); + assert_eq!(config.versions.get("node"), None); assert_eq!(manifest.installed_versions, HashSet::default()); assert!(!manifest .versions .contains_key(&VersionSpec::parse("19.0.0").unwrap())); } - #[test] - fn can_pin_when_installing() { - let temp = create_empty_sandbox(); - let manifest_file = temp.path().join("tools/node/manifest.json"); - - let mut manifest = ToolManifest::load(&manifest_file).unwrap(); - manifest.default_version = Some(UnresolvedVersionSpec::parse("18.0.0").unwrap()); - manifest - .installed_versions - .insert(VersionSpec::parse("18.0.0").unwrap()); - manifest.save().unwrap(); - - let mut cmd = create_proto_command(temp.path()); - cmd.arg("install") - .arg("node") - .arg("19.0.0") - .arg("--pin") - .arg("--") - .arg("--no-bundled-npm") - .assert(); - - let manifest = ToolManifest::load(&manifest_file).unwrap(); - - assert_eq!( - manifest.default_version, - Some(UnresolvedVersionSpec::parse("19.0.0").unwrap()) - ); - assert_eq!( - manifest.installed_versions, - HashSet::from_iter([ - VersionSpec::parse("18.0.0").unwrap(), - VersionSpec::parse("19.0.0").unwrap(), - ]) - ); - } - - #[test] - fn can_pin_when_already_installed() { - let temp = create_empty_sandbox(); - let manifest_file = temp.path().join("tools/node/manifest.json"); - - let mut cmd = create_proto_command(temp.path()); - cmd.arg("install") - .arg("node") - .arg("19.0.0") - .arg("--") - .arg("--no-bundled-npm") - .assert(); - - // Manually change it to something else - let mut manifest = ToolManifest::load(&manifest_file).unwrap(); - manifest.default_version = Some(UnresolvedVersionSpec::parse("18.0.0").unwrap()); - manifest.save().unwrap(); - - let mut cmd = create_proto_command(temp.path()); - cmd.arg("install") - .arg("node") - .arg("19.0.0") - .arg("--pin") - .arg("--") - .arg("--no-bundled-npm") - .assert(); - - let manifest = ToolManifest::load(&manifest_file).unwrap(); - - assert_eq!( - manifest.default_version, - Some(UnresolvedVersionSpec::parse("19.0.0").unwrap()) - ); - } - - #[test] - fn can_pin_latest_locally_using_setting() { - let temp = create_empty_sandbox(); - temp.create_file("config.toml", "pin-latest = \"local\""); - - let manifest_file = temp.path().join("tools/node/manifest.json"); - - // We set a default version here to compare against later - let mut manifest = ToolManifest::load(&manifest_file).unwrap(); - manifest.default_version = Some(UnresolvedVersionSpec::parse("18.0.0").unwrap()); - manifest.save().unwrap(); - - let mut cmd = create_proto_command(temp.path()); - cmd.arg("install") - .arg("node") - .arg("--") - .arg("--no-bundled-npm") - .assert(); - - let manifest = ToolManifest::load(&manifest_file).unwrap(); - - assert_eq!( - manifest.default_version.unwrap(), - UnresolvedVersionSpec::parse("18.0.0").unwrap() - ); - - let tools = ToolsConfig::load_from(temp.path()).unwrap(); - - assert!(tools.tools.get("node").is_some()); - } - - #[test] - fn can_pin_latest_globally_using_setting() { - let temp = create_empty_sandbox(); - temp.create_file("config.toml", "pin-latest = \"global\""); - - let manifest_file = temp.path().join("tools/node/manifest.json"); - - // We set a default version here to compare against later - let mut manifest = ToolManifest::load(&manifest_file).unwrap(); - manifest.default_version = Some(UnresolvedVersionSpec::parse("18.0.0").unwrap()); - manifest.save().unwrap(); + mod pin { + use super::*; + + #[test] + fn can_pin_when_installing() { + let sandbox = create_empty_sandbox(); + let manifest_file = sandbox.path().join(".proto/tools/node/manifest.json"); + + ProtoConfig::update(sandbox.path().join(".proto"), |config| { + config.versions.get_or_insert(Default::default()).insert( + Id::raw("node"), + UnresolvedVersionSpec::parse("18.0.0").unwrap(), + ); + }) + .unwrap(); + + let mut manifest = ToolManifest::load(&manifest_file).unwrap(); + manifest + .installed_versions + .insert(VersionSpec::parse("18.0.0").unwrap()); + manifest.save().unwrap(); + + let mut cmd = create_proto_command(sandbox.path()); + cmd.arg("install") + .arg("node") + .arg("19.0.0") + .arg("--pin") + .arg("--") + .arg("--no-bundled-npm") + .assert(); + + let manifest = ToolManifest::load(&manifest_file).unwrap(); + let config = load_config(sandbox.path().join(".proto")); + + assert_eq!( + config.versions.get("node").unwrap(), + &UnresolvedVersionSpec::parse("19.0.0").unwrap() + ); + assert_eq!( + manifest.installed_versions, + HashSet::from_iter([ + VersionSpec::parse("18.0.0").unwrap(), + VersionSpec::parse("19.0.0").unwrap(), + ]) + ); + } - let mut cmd = create_proto_command(temp.path()); - cmd.arg("install") - .arg("node") - .arg("--") - .arg("--no-bundled-npm") - .assert(); + #[test] + fn can_pin_when_already_installed() { + let sandbox = create_empty_sandbox(); + + let mut cmd = create_proto_command(sandbox.path()); + cmd.arg("install") + .arg("node") + .arg("19.0.0") + .arg("--") + .arg("--no-bundled-npm") + .assert(); + + // Manually change it to something else + ProtoConfig::update(sandbox.path().join(".proto"), |config| { + config.versions.get_or_insert(Default::default()).insert( + Id::raw("node"), + UnresolvedVersionSpec::parse("18.0.0").unwrap(), + ); + }) + .unwrap(); + + let mut cmd = create_proto_command(sandbox.path()); + cmd.arg("install") + .arg("node") + .arg("19.0.0") + .arg("--pin") + .arg("--") + .arg("--no-bundled-npm") + .assert(); + + let config = load_config(sandbox.path().join(".proto")); + + assert_eq!( + config.versions.get("node").unwrap(), + &UnresolvedVersionSpec::parse("19.0.0").unwrap() + ); + } - let manifest = ToolManifest::load(&manifest_file).unwrap(); + #[test] + fn can_pin_latest_locally_using_setting() { + let sandbox = create_empty_sandbox(); + + // Local + ProtoConfig::update(sandbox.path(), |config| { + config.settings.get_or_insert(Default::default()).pin_latest = Some(PinType::Local); + + config.versions.get_or_insert(Default::default()).insert( + Id::raw("node"), + UnresolvedVersionSpec::parse("16.0.0").unwrap(), + ); + }) + .unwrap(); + + // Global + ProtoConfig::update(sandbox.path().join(".proto"), |config| { + config.versions.get_or_insert(Default::default()).insert( + Id::raw("node"), + UnresolvedVersionSpec::parse("18.0.0").unwrap(), + ); + }) + .unwrap(); + + let mut cmd = create_proto_command(sandbox.path()); + cmd.arg("install") + .arg("node") + .arg("--") + .arg("--no-bundled-npm") + .assert(); + + let global_config = load_config(sandbox.path().join(".proto")); + + assert_eq!( + global_config.versions.get("node").unwrap(), + &UnresolvedVersionSpec::parse("18.0.0").unwrap() + ); + + let local_config = load_config(sandbox.path()); + + assert_ne!( + local_config.versions.get("node").unwrap(), + &UnresolvedVersionSpec::parse("16.0.0").unwrap() + ); + } - assert_ne!( - manifest.default_version.unwrap(), - UnresolvedVersionSpec::parse("18.0.0").unwrap() - ); + #[test] + fn can_pin_latest_globally_using_setting() { + let sandbox = create_empty_sandbox(); + + // Local + ProtoConfig::update(sandbox.path(), |config| { + config.settings.get_or_insert(Default::default()).pin_latest = + Some(PinType::Global); + + config.versions.get_or_insert(Default::default()).insert( + Id::raw("node"), + UnresolvedVersionSpec::parse("16.0.0").unwrap(), + ); + }) + .unwrap(); + + // Global + ProtoConfig::update(sandbox.path().join(".proto"), |config| { + config.versions.get_or_insert(Default::default()).insert( + Id::raw("node"), + UnresolvedVersionSpec::parse("18.0.0").unwrap(), + ); + }) + .unwrap(); + + let mut cmd = create_proto_command(sandbox.path()); + cmd.arg("install") + .arg("node") + .arg("--") + .arg("--no-bundled-npm") + .assert(); + + let global_config = load_config(sandbox.path().join(".proto")); + + assert_ne!( + global_config.versions.get("node").unwrap(), + &UnresolvedVersionSpec::parse("18.0.0").unwrap() + ); + + let local_config = load_config(sandbox.path()); + + assert_eq!( + local_config.versions.get("node").unwrap(), + &UnresolvedVersionSpec::parse("16.0.0").unwrap() + ); + } - let tools = ToolsConfig::load_from(temp.path()).unwrap(); + #[test] + fn doesnt_pin_using_setting_if_not_latest() { + let sandbox = create_empty_sandbox(); - assert!(tools.tools.get("node").is_none()); - } + // Local + ProtoConfig::update(sandbox.path(), |config| { + config.settings.get_or_insert(Default::default()).pin_latest = Some(PinType::Local); + }) + .unwrap(); - #[test] - fn doesnt_pin_using_setting_if_not_latest() { - let temp = create_empty_sandbox(); - temp.create_file("config.toml", "pin-latest = \"local\""); + let mut cmd = create_proto_command(sandbox.path()); + cmd.arg("install") + .arg("node") + .arg("20.0.0") + .arg("--") + .arg("--no-bundled-npm") + .assert(); - let mut cmd = create_proto_command(temp.path()); - cmd.arg("install") - .arg("node") - .arg("20.0.0") - .arg("--") - .arg("--no-bundled-npm") - .assert(); + let local_config = load_config(sandbox.path()); - let tools = ToolsConfig::load_from(temp.path()).unwrap(); - - assert!(tools.tools.get("node").is_none()); + assert_eq!(local_config.versions.get("node"), None); + } } - #[cfg(not(windows))] - #[test] - fn symlinks_bin_when_pinning() { - let temp = create_empty_sandbox(); - - let mut cmd = create_proto_command(temp.path()); - cmd.arg("install") - .arg("node") - .arg("19.0.0") - .arg("--pin") - .arg("--") - .arg("--no-bundled-npm") - .assert(); - - let link = temp.path().join("bin").join("node"); - - assert!(link.exists()); - - assert_eq!( - std::fs::read_link(link).unwrap(), - temp.path().join("tools/node/19.0.0").join("bin/node") - ); - } + mod bins { + use super::*; + + #[cfg(not(windows))] + #[test] + fn symlinks_bin_when_pinning() { + let sandbox = create_empty_sandbox(); + + let mut cmd = create_proto_command(sandbox.path()); + cmd.arg("install") + .arg("node") + .arg("19.0.0") + .arg("--pin") + .arg("--") + .arg("--no-bundled-npm") + .assert(); + + let link = sandbox.path().join(".proto/bin").join("node"); + + assert!(link.exists()); + + assert_eq!( + std::fs::read_link(link).unwrap(), + sandbox + .path() + .join(".proto/tools/node/19.0.0") + .join("bin/node") + ); + } - #[cfg(not(windows))] - #[test] - fn symlinks_bin_on_first_install_without_pinning() { - let temp = create_empty_sandbox(); + #[cfg(not(windows))] + #[test] + fn symlinks_bin_on_first_install_without_pinning() { + let sandbox = create_empty_sandbox(); - let mut cmd = create_proto_command(temp.path()); - cmd.arg("install") - .arg("node") - .arg("19.0.0") - .arg("--") - .arg("--no-bundled-npm") - .assert(); + let mut cmd = create_proto_command(sandbox.path()); + cmd.arg("install") + .arg("node") + .arg("19.0.0") + .arg("--") + .arg("--no-bundled-npm") + .assert(); - let link = temp.path().join("bin").join("node"); + let link = sandbox.path().join(".proto/bin").join("node"); - assert!(link.exists()); - } + assert!(link.exists()); + } - #[cfg(windows)] - #[test] - fn creates_bin_when_pinning() { - let temp = create_empty_sandbox(); + #[cfg(windows)] + #[test] + fn creates_bin_when_pinning() { + let sandbox = create_empty_sandbox(); - let mut cmd = create_proto_command(temp.path()); - cmd.arg("install") - .arg("node") - .arg("19.0.0") - .arg("--pin") - .arg("--") - .arg("--no-bundled-npm") - .assert(); + let mut cmd = create_proto_command(sandbox.path()); + cmd.arg("install") + .arg("node") + .arg("19.0.0") + .arg("--pin") + .arg("--") + .arg("--no-bundled-npm") + .assert(); - let link = temp.path().join("bin").join("node.exe"); + let link = sandbox.path().join(".proto/bin").join("node.exe"); - assert!(link.exists()); - } + assert!(link.exists()); + } - #[cfg(windows)] - #[test] - fn creates_bin_on_first_install_without_pinning() { - let temp = create_empty_sandbox(); + #[cfg(windows)] + #[test] + fn creates_bin_on_first_install_without_pinning() { + let sandbox = create_empty_sandbox(); - let mut cmd = create_proto_command(temp.path()); - cmd.arg("install") - .arg("node") - .arg("19.0.0") - .arg("--") - .arg("--no-bundled-npm") - .assert(); + let mut cmd = create_proto_command(sandbox.path()); + cmd.arg("install") + .arg("node") + .arg("19.0.0") + .arg("--") + .arg("--no-bundled-npm") + .assert(); - let link = temp.path().join("bin").join("node.exe"); + let link = sandbox.path().join(".proto/bin").join("node.exe"); - assert!(link.exists()); + assert!(link.exists()); + } } } diff --git a/crates/cli/tests/list_remote_test.rs b/crates/cli/tests/list_remote_test.rs index 69a00721e..49465ccac 100644 --- a/crates/cli/tests/list_remote_test.rs +++ b/crates/cli/tests/list_remote_test.rs @@ -7,9 +7,9 @@ mod list_remote { #[test] fn lists_remote_versions() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); let assert = cmd.arg("list-remote").arg("npm").assert(); let output = output_to_string(&assert.get_output().stdout); diff --git a/crates/cli/tests/list_test.rs b/crates/cli/tests/list_test.rs index ea4df4021..cb1387ffa 100644 --- a/crates/cli/tests/list_test.rs +++ b/crates/cli/tests/list_test.rs @@ -1,6 +1,6 @@ mod utils; -use proto_core::{ToolManifest, UnresolvedVersionSpec, VersionSpec}; +use proto_core::{ToolManifest, VersionSpec}; use utils::*; mod list { @@ -8,11 +8,10 @@ mod list { #[test] fn lists_local_versions() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); let mut manifest = - ToolManifest::load(temp.path().join("tools/node/manifest.json")).unwrap(); - manifest.default_version = Some(UnresolvedVersionSpec::parse("19.0.0").unwrap()); + ToolManifest::load(sandbox.path().join(".proto/tools/node/manifest.json")).unwrap(); manifest .installed_versions .insert(VersionSpec::parse("19.0.0").unwrap()); @@ -24,7 +23,7 @@ mod list { .insert(VersionSpec::parse("17.0.0").unwrap()); manifest.save().unwrap(); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); let assert = cmd.arg("list").arg("node").assert(); let output = output_to_string(&assert.get_output().stdout); diff --git a/crates/cli/tests/pin_test.rs b/crates/cli/tests/pin_test.rs index 9f6b10d8c..ad08fe811 100644 --- a/crates/cli/tests/pin_test.rs +++ b/crates/cli/tests/pin_test.rs @@ -1,6 +1,6 @@ mod utils; -use proto_core::{ToolManifest, UnresolvedVersionSpec}; +use proto_core::UnresolvedVersionSpec; use std::fs; use utils::*; @@ -9,12 +9,12 @@ mod pin_local { #[test] fn writes_local_version_file() { - let temp = create_empty_sandbox(); - let version_file = temp.path().join(".prototools"); + let sandbox = create_empty_sandbox(); + let version_file = sandbox.path().join(".prototools"); assert!(!version_file.exists()); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("pin").arg("node").arg("19.0.0").assert().success(); assert!(version_file.exists()); @@ -26,13 +26,13 @@ mod pin_local { #[test] fn appends_multiple_tools() { - let temp = create_empty_sandbox(); - let version_file = temp.path().join(".prototools"); + let sandbox = create_empty_sandbox(); + let version_file = sandbox.path().join(".prototools"); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("pin").arg("node").arg("19.0.0").assert().success(); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("pin").arg("npm").arg("9.0.0").assert().success(); assert_eq!( @@ -45,17 +45,17 @@ npm = "9.0.0" #[test] fn will_overwrite_by_name() { - let temp = create_empty_sandbox(); - let version_file = temp.path().join(".prototools"); + let sandbox = create_empty_sandbox(); + let version_file = sandbox.path().join(".prototools"); - temp.create_file( + sandbox.create_file( ".prototools", r#"node = "16.0.0" npm = "9.0.0" "#, ); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("pin").arg("node").arg("19").assert().success(); assert_eq!( @@ -68,12 +68,12 @@ npm = "9.0.0" #[test] fn can_set_aliases() { - let temp = create_empty_sandbox(); - let version_file = temp.path().join(".prototools"); + let sandbox = create_empty_sandbox(); + let version_file = sandbox.path().join(".prototools"); assert!(!version_file.exists()); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("pin").arg("npm").arg("bundled").assert().success(); assert!(version_file.exists()); @@ -85,12 +85,12 @@ npm = "9.0.0" #[test] fn can_set_partial_version() { - let temp = create_empty_sandbox(); - let version_file = temp.path().join(".prototools"); + let sandbox = create_empty_sandbox(); + let version_file = sandbox.path().join(".prototools"); assert!(!version_file.exists()); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("pin").arg("npm").arg("1.2").assert().success(); assert!(version_file.exists()); @@ -106,12 +106,12 @@ mod pin_global { #[test] fn updates_manifest_file() { - let temp = create_empty_sandbox(); - let manifest_file = temp.path().join("tools/node/manifest.json"); + let sandbox = create_empty_sandbox(); + let config_file = sandbox.path().join(".proto/.prototools"); - assert!(!manifest_file.exists()); + assert!(!config_file.exists()); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("pin") .arg("--global") .arg("node") @@ -119,24 +119,24 @@ mod pin_global { .assert() .success(); - assert!(manifest_file.exists()); + assert!(config_file.exists()); - let manifest = ToolManifest::load(manifest_file).unwrap(); + let config = load_config(sandbox.path().join(".proto")); assert_eq!( - manifest.default_version, - Some(UnresolvedVersionSpec::parse("19.0.0").unwrap()) + config.versions.get("node").unwrap(), + &UnresolvedVersionSpec::parse("19.0.0").unwrap() ); } #[test] fn can_set_alias_as_default() { - let temp = create_empty_sandbox(); - let manifest_file = temp.path().join("tools/npm/manifest.json"); + let sandbox = create_empty_sandbox(); + let config_file = sandbox.path().join(".proto/.prototools"); - assert!(!manifest_file.exists()); + assert!(!config_file.exists()); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("pin") .arg("--global") .arg("npm") @@ -144,24 +144,24 @@ mod pin_global { .assert() .success(); - assert!(manifest_file.exists()); + assert!(config_file.exists()); - let manifest = ToolManifest::load(manifest_file).unwrap(); + let config = load_config(sandbox.path().join(".proto")); assert_eq!( - manifest.default_version, - Some(UnresolvedVersionSpec::Alias("bundled".into())) + config.versions.get("npm").unwrap(), + &UnresolvedVersionSpec::Alias("bundled".into()) ); } #[test] fn can_set_partial_version_as_default() { - let temp = create_empty_sandbox(); - let manifest_file = temp.path().join("tools/npm/manifest.json"); + let sandbox = create_empty_sandbox(); + let config_file = sandbox.path().join(".proto/.prototools"); - assert!(!manifest_file.exists()); + assert!(!config_file.exists()); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("pin") .arg("--global") .arg("npm") @@ -169,21 +169,21 @@ mod pin_global { .assert() .success(); - assert!(manifest_file.exists()); + assert!(config_file.exists()); - let manifest = ToolManifest::load(manifest_file).unwrap(); + let config = load_config(sandbox.path().join(".proto")); assert_eq!( - manifest.default_version, - Some(UnresolvedVersionSpec::parse("1.2").unwrap()) + config.versions.get("npm").unwrap(), + &UnresolvedVersionSpec::parse("1.2").unwrap() ); } #[test] fn doesnt_create_bin_symlink() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("pin") .arg("--global") .arg("node") @@ -191,10 +191,11 @@ mod pin_global { .assert() .success(); - let link = temp - .path() - .join("bin") - .join(if cfg!(windows) { "node.exe" } else { "node" }); + let link = + sandbox + .path() + .join(".proto/bin") + .join(if cfg!(windows) { "node.exe" } else { "node" }); assert!(!link.exists()); } diff --git a/crates/cli/tests/plugins_test.rs b/crates/cli/tests/plugins_test.rs index d22ae07b8..310dabdd1 100644 --- a/crates/cli/tests/plugins_test.rs +++ b/crates/cli/tests/plugins_test.rs @@ -3,7 +3,6 @@ mod utils; use futures::Future; use proto_core::{ load_tool_from_locator, Id, PluginLocator, ProtoEnvironment, Tool, UnresolvedVersionSpec, - UserConfig, }; use std::env; use std::path::{Path, PathBuf}; @@ -45,8 +44,6 @@ mod plugins { #[tokio::test] async fn downloads_and_installs_plugin_from_file() { - let user_config = UserConfig::default(); - run_tests(|root| { let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); @@ -57,7 +54,6 @@ mod plugins { file: "./tests/fixtures/moon-schema.toml".into(), path: root_dir.join("./tests/fixtures/moon-schema.toml"), }, - &user_config, ) }) .await; @@ -66,8 +62,6 @@ mod plugins { #[tokio::test] #[should_panic(expected = "does not exist")] async fn errors_for_missing_file() { - let user_config = UserConfig::default(); - run_tests(|root| { let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); @@ -78,7 +72,6 @@ mod plugins { file: "./some/fake/path.toml".into(), path: root_dir.join("./some/fake/path.toml"), }, - &user_config, ) }) .await; @@ -86,8 +79,6 @@ mod plugins { #[tokio::test] async fn downloads_and_installs_plugin_from_url() { - let user_config = UserConfig::default(); - run_tests(|root| { load_tool_from_locator( Id::raw("moon"), @@ -96,7 +87,6 @@ mod plugins { url: "https://raw.githubusercontent.com/moonrepo/moon/master/proto-plugin.toml" .into(), }, - &user_config, ) }) .await; @@ -105,8 +95,6 @@ mod plugins { #[tokio::test] #[should_panic(expected = "does not exist")] async fn errors_for_broken_url() { - let user_config = UserConfig::default(); - run_tests(|root| { load_tool_from_locator( Id::raw("moon"), @@ -115,7 +103,6 @@ mod plugins { url: "https://raw.githubusercontent.com/moonrepo/moon/some/fake/path.toml" .into(), }, - &user_config, ) }) .await; @@ -128,15 +115,15 @@ mod plugins { #[cfg(not(windows))] #[test] fn supports_bun() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); - create_proto_command(temp.path()) + create_proto_command(sandbox.path()) .arg("install") .arg("bun") .assert() .success(); - create_shim_command(temp.path(), "bun") + create_shim_command(sandbox.path(), "bun") .arg("--version") .assert() .success(); @@ -144,15 +131,15 @@ mod plugins { #[test] fn supports_deno() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); - create_proto_command(temp.path()) + create_proto_command(sandbox.path()) .arg("install") .arg("deno") .assert() .success(); - create_shim_command(temp.path(), "deno") + create_shim_command(sandbox.path(), "deno") .arg("--version") .assert() .success(); @@ -160,15 +147,15 @@ mod plugins { #[test] fn supports_go() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); - create_proto_command(temp.path()) + create_proto_command(sandbox.path()) .arg("install") .arg("go") .assert() .success(); - create_shim_command(temp.path(), "go") + create_shim_command(sandbox.path(), "go") .arg("version") .assert() .success(); @@ -176,9 +163,9 @@ mod plugins { #[test] fn supports_node() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); - create_proto_command(temp.path()) + create_proto_command(sandbox.path()) .arg("install") .arg("node") .arg("--") @@ -186,7 +173,7 @@ mod plugins { .assert() .success(); - create_shim_command(temp.path(), "node") + create_shim_command(sandbox.path(), "node") .arg("--version") .assert() .success(); @@ -194,15 +181,25 @@ mod plugins { #[test] fn supports_npm() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); - create_proto_command(temp.path()) + create_proto_command(sandbox.path()) + .arg("install") + .arg("node") + .arg("--") + .arg("--no-bundled-npm") + .assert() + .success(); + + create_proto_command(sandbox.path()) .arg("install") .arg("npm") .assert() .success(); - create_shim_command(temp.path(), "npm") + // TODO: revisit when we change shims + #[cfg(not(windows))] + create_shim_command(sandbox.path(), "npm") .arg("--version") .assert() .success(); @@ -210,15 +207,25 @@ mod plugins { #[test] fn supports_pnpm() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); - create_proto_command(temp.path()) + create_proto_command(sandbox.path()) + .arg("install") + .arg("node") + .arg("--") + .arg("--no-bundled-npm") + .assert() + .success(); + + create_proto_command(sandbox.path()) .arg("install") .arg("pnpm") .assert() .success(); - create_shim_command(temp.path(), "pnpm") + // TODO: revisit when we change shims + #[cfg(not(windows))] + create_shim_command(sandbox.path(), "pnpm") .arg("--version") .assert() .success(); @@ -226,15 +233,25 @@ mod plugins { #[test] fn supports_yarn() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); + + create_proto_command(sandbox.path()) + .arg("install") + .arg("node") + .arg("--") + .arg("--no-bundled-npm") + .assert() + .success(); - create_proto_command(temp.path()) + create_proto_command(sandbox.path()) .arg("install") .arg("yarn") .assert() .success(); - create_shim_command(temp.path(), "yarn") + // TODO: revisit when we change shims + #[cfg(not(windows))] + create_shim_command(sandbox.path(), "yarn") .arg("--version") .assert() .success(); @@ -242,15 +259,15 @@ mod plugins { #[test] fn supports_python() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); - create_proto_command(temp.path()) + create_proto_command(sandbox.path()) .arg("install") .arg("python") .assert() .success(); - create_shim_command(temp.path(), "python") + create_shim_command(sandbox.path(), "python") .arg("--version") .assert() .success(); @@ -258,9 +275,9 @@ mod plugins { #[test] fn supports_rust() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); - create_proto_command(temp.path()) + create_proto_command(sandbox.path()) .arg("install") .arg("rust") .assert() @@ -269,9 +286,9 @@ mod plugins { #[test] fn supports_toml_schema() { - let temp = create_empty_sandbox_with_tools(); + let sandbox = create_empty_sandbox_with_tools(); - create_proto_command(temp.path()) + create_proto_command(sandbox.path()) .arg("install") .arg("moon-test") .assert() diff --git a/crates/cli/tests/run_test.rs b/crates/cli/tests/run_test.rs index 72a7e5ebf..bd5b30b58 100644 --- a/crates/cli/tests/run_test.rs +++ b/crates/cli/tests/run_test.rs @@ -10,9 +10,9 @@ mod run { #[test] fn errors_if_not_installed() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); let assert = cmd.arg("run").arg("node").arg("19.0.0").assert(); assert.stderr(predicate::str::contains( @@ -22,9 +22,9 @@ mod run { #[test] fn errors_if_no_version_detected() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); let assert = cmd.arg("run").arg("node").assert(); assert.stderr(predicate::str::contains( @@ -34,16 +34,16 @@ mod run { #[test] fn runs_a_tool() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("install") .arg("node") .arg("19.0.0") .assert() .success(); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); let assert = cmd .arg("run") .arg("node") @@ -57,9 +57,9 @@ mod run { #[test] fn runs_a_tool_using_version_detection() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("install") .arg("node") .arg("19.0.0") @@ -67,7 +67,7 @@ mod run { .success(); // Arg - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); let assert = cmd .arg("run") .arg("node") @@ -79,7 +79,7 @@ mod run { assert.stdout(predicate::str::contains("19.0.0")); // Env var - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); let assert = cmd .env("PROTO_NODE_VERSION", "19.0.0") .arg("run") @@ -91,9 +91,9 @@ mod run { assert.stdout(predicate::str::contains("19.0.0")); // Local version - temp.create_file(".prototools", "node = \"19.0.0\""); + sandbox.create_file(".prototools", "node = \"19.0.0\""); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); let assert = cmd .arg("run") .arg("node") @@ -103,15 +103,12 @@ mod run { assert.stdout(predicate::str::contains("19.0.0")); - fs::remove_file(temp.path().join(".prototools")).unwrap(); + fs::remove_file(sandbox.path().join(".prototools")).unwrap(); // Global version - temp.create_file( - "tools/node/manifest.json", - r#"{ "default_version": "19.0.0", "installed_versions": ["19.0.0"] }"#, - ); + sandbox.create_file(".proto/.prototools", "node = \"19.0.0\""); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); let assert = cmd .arg("run") .arg("node") @@ -120,24 +117,22 @@ mod run { .assert(); assert.stdout(predicate::str::contains("19.0.0")); - - fs::remove_file(temp.path().join("tools/node/manifest.json")).unwrap(); } // This test fails in Windows for some reason, but works fine with `cargo run`... #[cfg(not(windows))] #[test] fn runs_a_tool_alt_bin() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("install") .arg("node") .arg("19.0.0") .assert() .success(); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); let assert = cmd .arg("run") .arg("node") @@ -153,17 +148,17 @@ mod run { #[test] fn updates_last_used_at() { - let temp = create_empty_sandbox(); - let manifest_file = temp.path().join("tools/node/manifest.json"); + let sandbox = create_empty_sandbox(); + let manifest_file = sandbox.path().join(".proto/tools/node/manifest.json"); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("install") .arg("node") .arg("19.0.0") .assert() .success(); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("run") .arg("node") .arg("19.0.0") @@ -179,7 +174,7 @@ mod run { assert!(last_used_at.is_some()); // Run again and make sure timestamps update - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("run") .arg("node") .arg("19.0.0") @@ -197,11 +192,11 @@ mod run { #[test] fn auto_installs_if_missing() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); - temp.create_file("config.toml", "auto-install = true"); + sandbox.create_file(".prototools", "[settings]\nauto-install = true"); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); let assert = cmd .arg("run") .arg("node") @@ -215,11 +210,11 @@ mod run { #[test] fn auto_installs_if_missing_with_env_var() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); env::set_var("PROTO_AUTO_INSTALL", "true"); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); let assert = cmd .arg("run") .arg("node") @@ -235,11 +230,11 @@ mod run { #[test] fn doesnt_auto_install_if_false() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); - temp.create_file("config.toml", "auto-install = false"); + sandbox.create_file(".prototools", "[settings]\nauto-install = false"); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); let assert = cmd.arg("run").arg("node").arg("19.0.0").assert(); assert.stderr(predicate::str::contains( @@ -249,11 +244,11 @@ mod run { #[test] fn doesnt_auto_install_subsequently() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); - temp.create_file("config.toml", "auto-install = true"); + sandbox.create_file(".prototools", "[settings]\nauto-install = true"); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); let assert = cmd .arg("run") .arg("node") @@ -264,7 +259,7 @@ mod run { assert.stderr(predicate::str::contains("Node.js has been installed")); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); let assert = cmd .arg("run") .arg("node") @@ -278,9 +273,9 @@ mod run { #[test] fn errors_if_plugin_not_configured() { - let temp = create_empty_sandbox(); + let sandbox = create_empty_sandbox(); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); let assert = cmd.arg("run").arg("plugin-name").arg("1.0.0").assert(); assert.stderr(predicate::str::contains( diff --git a/crates/cli/tests/tool_add_test.rs b/crates/cli/tests/tool_add_test.rs index f3807fc5c..7b7a7fe63 100644 --- a/crates/cli/tests/tool_add_test.rs +++ b/crates/cli/tests/tool_add_test.rs @@ -1,8 +1,7 @@ mod utils; -use proto_core::{Id, PluginLocator, ToolsConfig, UserConfig, TOOLS_CONFIG_NAME, USER_CONFIG_NAME}; +use proto_core::PluginLocator; use starbase_sandbox::predicates::prelude::*; -use std::collections::BTreeMap; use utils::*; mod tool_add { @@ -28,12 +27,13 @@ mod tool_add { #[test] fn updates_local_file() { let sandbox = create_empty_sandbox(); - let config_file = sandbox.path().join(TOOLS_CONFIG_NAME); + let config_file = sandbox.path().join(".prototools"); assert!(!config_file.exists()); let mut cmd = create_proto_command(sandbox.path()); - cmd.arg("tool").arg("add") + cmd.arg("tool") + .arg("add") .arg("id") .arg("source:https://github.com/moonrepo/schema-plugin/releases/latest/download/schema_plugin.wasm") .assert() @@ -41,28 +41,26 @@ mod tool_add { assert!(config_file.exists()); - let manifest = ToolsConfig::load_from(sandbox.path()).unwrap(); + let config = load_config(sandbox.path()); assert_eq!( - manifest.plugins, - BTreeMap::from_iter([( - Id::raw("id"), - PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/schema-plugin/releases/latest/download/schema_plugin.wasm".into() - } - )]) + config.plugins.get("id").unwrap(), + &PluginLocator::SourceUrl { + url: "https://github.com/moonrepo/schema-plugin/releases/latest/download/schema_plugin.wasm".into() + } ); } #[test] fn updates_global_file() { let sandbox = create_empty_sandbox(); - let config_file = sandbox.path().join(USER_CONFIG_NAME); + let config_file = sandbox.path().join(".proto/.prototools"); assert!(!config_file.exists()); let mut cmd = create_proto_command(sandbox.path()); - cmd.arg("tool").arg("add") + cmd.arg("tool") + .arg("add") .arg("id") .arg("source:https://github.com/moonrepo/schema-plugin/releases/latest/download/schema_plugin.wasm") .arg("--global") @@ -71,16 +69,13 @@ mod tool_add { assert!(config_file.exists()); - let manifest = UserConfig::load_from(sandbox.path()).unwrap(); + let config = load_config(sandbox.path().join(".proto")); assert_eq!( - manifest.plugins, - BTreeMap::from_iter([( - Id::raw("id"), - PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/schema-plugin/releases/latest/download/schema_plugin.wasm".into() - } - )]) + config.plugins.get("id").unwrap(), + &PluginLocator::SourceUrl { + url: "https://github.com/moonrepo/schema-plugin/releases/latest/download/schema_plugin.wasm".into() + } ); } } diff --git a/crates/cli/tests/tool_remove_test.rs b/crates/cli/tests/tool_remove_test.rs index f1569750e..dc281bd06 100644 --- a/crates/cli/tests/tool_remove_test.rs +++ b/crates/cli/tests/tool_remove_test.rs @@ -1,11 +1,10 @@ mod utils; -use proto_core::{Id, PluginLocator, ToolsConfig, UserConfig}; +use proto_core::{Id, PluginLocator, ProtoConfig}; use starbase_sandbox::predicates::prelude::*; -use std::collections::BTreeMap; use utils::*; -mod plugin_remove { +mod tool_remove { use super::*; #[test] @@ -24,29 +23,43 @@ mod plugin_remove { fn updates_local_file() { let sandbox = create_empty_sandbox(); - let mut config = ToolsConfig::load_from(sandbox.path()).unwrap(); - config.plugins.insert(Id::raw("id"), PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/schema-plugin/releases/latest/download/schema_plugin.wasm".into() - }); - config.save().unwrap(); + ProtoConfig::update(sandbox.path(), |config| { + config + .plugins + .get_or_insert(Default::default()) + .insert( + Id::raw("id"), + PluginLocator::SourceUrl { + url: "https://github.com/moonrepo/schema-plugin/releases/latest/download/schema_plugin.wasm".into() + }, + ); + }) + .unwrap(); let mut cmd = create_proto_command(sandbox.path()); cmd.arg("tool").arg("remove").arg("id").assert().success(); - let config = ToolsConfig::load_from(sandbox.path()).unwrap(); + let config = load_config(sandbox.path()); - assert_eq!(config.plugins, BTreeMap::new()); + assert!(!config.plugins.contains_key("id")); } #[test] fn updates_global_file() { let sandbox = create_empty_sandbox(); - let mut config = UserConfig::load_from(sandbox.path()).unwrap(); - config.plugins.insert(Id::raw("id"), PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/schema-plugin/releases/latest/download/schema_plugin.wasm".into() - }); - config.save().unwrap(); + ProtoConfig::update(sandbox.path().join(".proto"), |config| { + config + .plugins + .get_or_insert(Default::default()) + .insert( + Id::raw("id"), + PluginLocator::SourceUrl { + url: "https://github.com/moonrepo/schema-plugin/releases/latest/download/schema_plugin.wasm".into() + }, + ); + }) + .unwrap(); let mut cmd = create_proto_command(sandbox.path()); cmd.arg("tool") @@ -56,8 +69,8 @@ mod plugin_remove { .assert() .success(); - let config = UserConfig::load_from(sandbox.path()).unwrap(); + let config = load_config(sandbox.path().join(".proto")); - assert_eq!(config.plugins, BTreeMap::new()); + assert!(!config.plugins.contains_key("id")); } } diff --git a/crates/cli/tests/unalias_test.rs b/crates/cli/tests/unalias_test.rs index cc8d10fe3..ba3ddf8ec 100644 --- a/crates/cli/tests/unalias_test.rs +++ b/crates/cli/tests/unalias_test.rs @@ -1,11 +1,11 @@ mod utils; -use proto_core::{ToolManifest, UnresolvedVersionSpec}; +use proto_core::{Id, PartialProtoToolConfig, ProtoConfig, UnresolvedVersionSpec}; use starbase_sandbox::predicates::prelude::*; use std::collections::BTreeMap; use utils::*; -mod unalias { +mod unalias_local { use super::*; #[test] @@ -21,14 +21,20 @@ mod unalias { #[test] fn removes_existing_alias() { let sandbox = create_empty_sandbox(); - let manifest_file = sandbox.path().join("tools/node/manifest.json"); - let mut manifest = ToolManifest::load(&manifest_file).unwrap(); - manifest.aliases.insert( - "example".into(), - UnresolvedVersionSpec::parse("19.0.0").unwrap(), - ); - manifest.save().unwrap(); + ProtoConfig::update(sandbox.path(), |config| { + config.tools.get_or_insert(Default::default()).insert( + Id::raw("node"), + PartialProtoToolConfig { + aliases: Some(BTreeMap::from_iter([( + "example".into(), + UnresolvedVersionSpec::parse("19.0.0").unwrap(), + )])), + ..Default::default() + }, + ); + }) + .unwrap(); let mut cmd = create_proto_command(sandbox.path()); cmd.arg("unalias") @@ -37,22 +43,28 @@ mod unalias { .assert() .success(); - let manifest = ToolManifest::load(&manifest_file).unwrap(); + let config = load_config(sandbox.path()); - assert!(manifest.aliases.is_empty()); + assert!(config.tools.get("node").unwrap().aliases.is_empty()); } #[test] fn does_nothing_for_unknown_alias() { let sandbox = create_empty_sandbox(); - let manifest_file = sandbox.path().join("tools/node/manifest.json"); - let mut manifest = ToolManifest::load(&manifest_file).unwrap(); - manifest.aliases.insert( - "example".into(), - UnresolvedVersionSpec::parse("19.0.0").unwrap(), - ); - manifest.save().unwrap(); + ProtoConfig::update(sandbox.path(), |config| { + config.tools.get_or_insert(Default::default()).insert( + Id::raw("node"), + PartialProtoToolConfig { + aliases: Some(BTreeMap::from_iter([( + "example".into(), + UnresolvedVersionSpec::parse("19.0.0").unwrap(), + )])), + ..Default::default() + }, + ); + }) + .unwrap(); let mut cmd = create_proto_command(sandbox.path()); cmd.arg("unalias") @@ -61,10 +73,10 @@ mod unalias { .assert() .success(); - let manifest = ToolManifest::load(manifest_file).unwrap(); + let config = load_config(sandbox.path()); assert_eq!( - manifest.aliases, + config.tools.get("node").unwrap().aliases, BTreeMap::from_iter([( "example".into(), UnresolvedVersionSpec::parse("19.0.0").unwrap() @@ -72,3 +84,38 @@ mod unalias { ); } } + +mod unalias_global { + use super::*; + + #[test] + fn removes_existing_alias() { + let sandbox = create_empty_sandbox(); + + ProtoConfig::update(sandbox.path().join(".proto"), |config| { + config.tools.get_or_insert(Default::default()).insert( + Id::raw("node"), + PartialProtoToolConfig { + aliases: Some(BTreeMap::from_iter([( + "example".into(), + UnresolvedVersionSpec::parse("19.0.0").unwrap(), + )])), + ..Default::default() + }, + ); + }) + .unwrap(); + + let mut cmd = create_proto_command(sandbox.path()); + cmd.arg("unalias") + .arg("node") + .arg("example") + .arg("--global") + .assert() + .success(); + + let config = load_config(sandbox.path().join(".proto")); + + assert!(config.tools.get("node").unwrap().aliases.is_empty()); + } +} diff --git a/crates/cli/tests/uninstall_test.rs b/crates/cli/tests/uninstall_test.rs index b12ccba5c..25bcfb64a 100644 --- a/crates/cli/tests/uninstall_test.rs +++ b/crates/cli/tests/uninstall_test.rs @@ -26,8 +26,8 @@ mod uninstall { 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()); + assert!(!temp.path().join(".proto/tools/node/19.0.0").exists()); + assert!(temp.path().join(".proto/tools/node/manifest.json").exists()); } #[test] @@ -40,12 +40,12 @@ mod uninstall { 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()); + assert!(temp.path().join(".proto/tools/node/19.0.0").exists()); + assert!(temp.path().join(".proto/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()); + assert!(!temp.path().join(".proto/tools/node").exists()); } } diff --git a/crates/cli/tests/use_test.rs b/crates/cli/tests/use_test.rs index ed6d3711a..4bf715eaf 100644 --- a/crates/cli/tests/use_test.rs +++ b/crates/cli/tests/use_test.rs @@ -7,16 +7,16 @@ mod install_all { #[test] fn installs_all_tools() { - let temp = create_empty_sandbox(); - let node_path = temp.path().join("tools/node/19.0.0"); - let npm_path = temp.path().join("tools/npm/9.0.0"); - let deno_path = temp.path().join("tools/deno/1.30.0"); + let sandbox = create_empty_sandbox(); + let node_path = sandbox.path().join(".proto/tools/node/19.0.0"); + let npm_path = sandbox.path().join(".proto/tools/npm/9.0.0"); + let deno_path = sandbox.path().join(".proto/tools/deno/1.30.0"); - temp.create_file( + sandbox.create_file( ".prototools", r#"node = "19.0.0" - npm = "9.0.0" - deno = "1.30.0" +npm = "9.0.0" +deno = "1.30.0" "#, ); @@ -24,7 +24,7 @@ mod install_all { assert!(!npm_path.exists()); assert!(!deno_path.exists()); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("use").assert().success(); assert!(node_path.exists()); @@ -34,14 +34,14 @@ mod install_all { #[test] fn installs_tool_via_detection() { - let temp = create_empty_sandbox(); - let node_path = temp.path().join("tools/node/19.0.0"); + let sandbox = create_empty_sandbox(); + let node_path = sandbox.path().join(".proto/tools/node/19.0.0"); - temp.create_file(".nvmrc", "19.0.0"); + sandbox.create_file(".nvmrc", "19.0.0"); assert!(!node_path.exists()); - let mut cmd = create_proto_command(temp.path()); + let mut cmd = create_proto_command(sandbox.path()); cmd.arg("use").assert().success(); assert!(node_path.exists()); diff --git a/crates/cli/tests/utils.rs b/crates/cli/tests/utils.rs index f9d20e322..903e645d6 100644 --- a/crates/cli/tests/utils.rs +++ b/crates/cli/tests/utils.rs @@ -1,9 +1,16 @@ #![allow(dead_code)] +use proto_core::{ProtoConfig, ProtoConfigManager}; use starbase_sandbox::{assert_cmd, create_command_with_name}; pub use starbase_sandbox::{create_empty_sandbox, output_to_string, Sandbox}; use std::path::Path; +pub fn load_config>(dir: T) -> ProtoConfig { + let manager = ProtoConfigManager::load(dir, None).unwrap(); + let config = manager.get_merged_config().unwrap(); + config.to_owned() +} + pub fn create_empty_sandbox_with_tools() -> Sandbox { let temp = create_empty_sandbox(); @@ -25,7 +32,7 @@ pub fn create_proto_command>(path: T) -> assert_cmd::Command { let mut cmd = create_command_with_name(path, "proto"); cmd.timeout(std::time::Duration::from_secs(240)); - cmd.env("PROTO_HOME", path); + cmd.env("PROTO_HOME", path.join(".proto")); cmd.env("PROTO_LOG", "trace"); cmd.env("PROTO_WASM_LOG", "trace"); cmd.env("RUST_BACKTRACE", "1"); @@ -37,13 +44,14 @@ pub fn create_proto_command>(path: T) -> assert_cmd::Command { pub fn create_shim_command>(path: T, name: &str) -> assert_cmd::Command { let path = path.as_ref(); - let mut cmd = assert_cmd::Command::new(path.join("shims").join(if cfg!(windows) { + let mut cmd = assert_cmd::Command::new(path.join(".proto/shims").join(if cfg!(windows) { format!("{name}.cmd") } else { name.to_owned() })); cmd.timeout(std::time::Duration::from_secs(240)); - cmd.env("PROTO_HOME", path); + cmd.env("PROTO_HOME", path.join(".proto")); + cmd.env("PROTO_NODE_VERSION", "latest"); // For package managers cmd.env(format!("PROTO_{}_VERSION", name.to_uppercase()), "latest"); cmd } diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index f99c7dea0..58b09fd58 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proto_core" -version = "0.23.7" +version = "0.24.0" edition = "2021" license = "MIT" description = "Core proto APIs." @@ -8,10 +8,10 @@ homepage = "https://moonrepo.dev/proto" repository = "https://github.com/moonrepo/proto" [dependencies] -proto_pdk_api = { version = "0.10.4", path = "../pdk-api" } -system_env = { version = "0.1.6", path = "../system-env" } -version_spec = { version = "0.1.5", path = "../version-spec" } -warpgate = { version = "0.5.15", path = "../warpgate" } +proto_pdk_api = { version = "0.11.1", path = "../pdk-api" } +system_env = { version = "0.1.7", path = "../system-env" } +version_spec = { version = "0.1.6", path = "../version-spec" } +warpgate = { version = "0.6.0", path = "../warpgate" } cached = { workspace = true } extism = { workspace = true } human-sort = { workspace = true } @@ -20,6 +20,7 @@ minisign-verify = "0.2.1" once_cell = { workspace = true } regex = { workspace = true } reqwest = { workspace = true } +schematic = { workspace = true, features = ["config", "toml"] } semver = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index fe78aaed1..069aca845 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -3,11 +3,11 @@ mod events; mod helpers; mod host_funcs; mod proto; +mod proto_config; mod shimmer; mod tool; mod tool_loader; mod tool_manifest; -mod tools_config; mod user_config; mod version_detector; mod version_resolver; @@ -17,12 +17,12 @@ pub use events::*; pub use extism::{manifest::Wasm, Manifest as PluginManifest}; pub use helpers::*; pub use proto::*; +pub use proto_config::*; pub use semver::{Version, VersionReq}; pub use shimmer::*; pub use tool::*; pub use tool_loader::*; pub use tool_manifest::*; -pub use tools_config::*; pub use user_config::*; pub use version_detector::*; pub use version_resolver::*; diff --git a/crates/core/src/proto.rs b/crates/core/src/proto.rs index 6b10f5701..32b50e78b 100644 --- a/crates/core/src/proto.rs +++ b/crates/core/src/proto.rs @@ -1,5 +1,6 @@ use crate::helpers::{get_home_dir, get_proto_home, is_offline}; -use crate::user_config::UserConfig; +use crate::proto_config::{ProtoConfig, ProtoConfigManager}; +use crate::{ProtoConfigFile, PROTO_CONFIG_NAME}; use once_cell::sync::OnceCell; use std::collections::BTreeMap; use std::env; @@ -18,8 +19,10 @@ pub struct ProtoEnvironment { pub home: PathBuf, // ~ pub root: PathBuf, // ~/.proto - client: Arc>, - loader: Arc>, + config_manager: Arc>, + http_client: Arc>, + plugin_loader: Arc>, + test_mode: bool, } impl ProtoEnvironment { @@ -31,6 +34,7 @@ impl ProtoEnvironment { let mut env = Self::from(sandbox.join(".proto")).unwrap(); env.cwd = sandbox.to_path_buf(); env.home = sandbox.join(".home"); + env.test_mode = true; env } @@ -46,22 +50,30 @@ impl ProtoEnvironment { tools_dir: root.join("tools"), home: get_home_dir()?, root: root.to_owned(), - client: Arc::new(OnceCell::new()), - loader: Arc::new(OnceCell::new()), + config_manager: Arc::new(OnceCell::new()), + http_client: Arc::new(OnceCell::new()), + plugin_loader: Arc::new(OnceCell::new()), + test_mode: false, }) } + pub fn get_config_dir(&self, global: bool) -> &Path { + if global { + &self.root + } else { + &self.cwd + } + } + pub fn get_http_client(&self) -> miette::Result<&reqwest::Client> { - self.client.get_or_try_init(|| { - let user_config = UserConfig::load()?; - let client = create_http_client_with_options(user_config.http)?; + let config = self.load_config()?; - Ok(client) - }) + self.http_client + .get_or_try_init(|| create_http_client_with_options(&config.settings.http)) } pub fn get_plugin_loader(&self) -> &PluginLoader { - self.loader.get_or_init(|| { + self.plugin_loader.get_or_init(|| { let mut loader = PluginLoader::new(&self.plugins_dir, &self.temp_dir); loader.set_offline_checker(is_offline); loader.set_seed(env!("CARGO_PKG_VERSION")); @@ -77,8 +89,33 @@ impl ProtoEnvironment { ]) } - pub fn load_user_config(&self) -> miette::Result { - UserConfig::load_from(&self.root) + pub fn load_config(&self) -> miette::Result<&ProtoConfig> { + self.load_config_manager()?.get_merged_config() + } + + pub fn load_config_manager(&self) -> miette::Result<&ProtoConfigManager> { + self.config_manager.get_or_try_init(|| { + // Don't traverse passed the home directory, + // but only if working directory is within it! + let end_dir = if self.cwd.starts_with(&self.home) { + Some(self.home.as_path()) + } else { + None + }; + + let mut manager = ProtoConfigManager::load(&self.cwd, end_dir)?; + + // Always load the proto home/root config last + let path = self.root.join(PROTO_CONFIG_NAME); + + manager.files.push(ProtoConfigFile { + exists: path.exists(), + path, + config: ProtoConfig::load_from(&self.root, true)?, + }); + + Ok(manager) + }) } } diff --git a/crates/core/src/proto_config.rs b/crates/core/src/proto_config.rs new file mode 100644 index 000000000..2402570af --- /dev/null +++ b/crates/core/src/proto_config.rs @@ -0,0 +1,373 @@ +use miette::IntoDiagnostic; +use once_cell::sync::OnceCell; +use schematic::{ + derive_enum, env, merge, Config, ConfigEnum, ConfigError, ConfigLoader, Format, PartialConfig, + ValidateError, ValidateErrorType, ValidatorError, +}; +use serde::Serialize; +use starbase_utils::json::JsonValue; +use starbase_utils::toml::TomlValue; +use starbase_utils::{fs, toml}; +use std::collections::BTreeMap; +use std::path::{Path, PathBuf}; +use std::sync::Arc; +use tracing::{debug, trace}; +use version_spec::*; +use warpgate::{HttpOptions, Id, PluginLocator}; + +pub const PROTO_CONFIG_NAME: &str = ".prototools"; +pub const SCHEMA_PLUGIN_KEY: &str = "internal-schema"; + +derive_enum!( + #[derive(ConfigEnum, Default)] + pub enum DetectStrategy { + #[default] + FirstAvailable, + PreferPrototools, + } +); + +derive_enum!( + #[derive(ConfigEnum)] + pub enum PinType { + Global, + Local, + } +); + +#[derive(Clone, Config, Debug, Serialize)] +#[config(allow_unknown_fields, rename_all = "kebab-case")] +pub struct ProtoToolConfig { + #[setting(merge = merge::merge_btreemap)] + pub aliases: BTreeMap, + + // Custom configuration to pass to plugins + #[setting(flatten, merge = merge::merge_btreemap)] + pub config: BTreeMap, +} + +#[derive(Clone, Config, Debug, Serialize)] +#[config(rename_all = "kebab-case")] +pub struct ProtoSettingsConfig { + #[setting(env = "PROTO_AUTO_CLEAN", parse_env = env::parse_bool)] + pub auto_clean: bool, + + #[setting(env = "PROTO_AUTO_INSTALL", parse_env = env::parse_bool)] + pub auto_install: bool, + + #[setting(env = "PROTO_DETECT_STRATEGY")] + pub detect_strategy: DetectStrategy, + + #[setting(env = "PROTO_PIN_LATEST")] + pub pin_latest: Option, + + pub http: HttpOptions, +} + +fn merge_tools( + mut prev: BTreeMap, + next: BTreeMap, + context: &(), +) -> Result>, ConfigError> { + for (key, value) in next { + prev.entry(key).or_default().merge(context, value)?; + } + + Ok(Some(prev)) +} + +#[derive(Clone, Config, Debug, Serialize)] +#[config(allow_unknown_fields, rename_all = "kebab-case")] +pub struct ProtoConfig { + #[setting(nested, merge = merge_tools)] + pub tools: BTreeMap, + + #[setting(merge = merge::merge_btreemap)] + pub plugins: BTreeMap, + + #[setting(nested)] + pub settings: ProtoSettingsConfig, + + #[setting(flatten, merge = merge::merge_btreemap)] + pub versions: BTreeMap, + + #[setting(flatten, merge = merge::merge_btreemap, skip_serializing)] + pub unknown: BTreeMap, +} + +impl ProtoConfig { + pub fn builtin_plugins() -> BTreeMap { + let mut config = ProtoConfig::default(); + config.inherit_builtin_plugins(); + config.plugins + } + + pub fn inherit_builtin_plugins(&mut self) { + if !self.plugins.contains_key("bun") { + self.plugins.insert( + Id::raw("bun"), + PluginLocator::SourceUrl { + url: "https://github.com/moonrepo/bun-plugin/releases/download/v0.6.0/bun_plugin.wasm".into() + } + ); + } + + if !self.plugins.contains_key("deno") { + self.plugins.insert( + Id::raw("deno"), + PluginLocator::SourceUrl { + url: "https://github.com/moonrepo/deno-plugin/releases/download/v0.6.0/deno_plugin.wasm".into() + } + ); + } + + if !self.plugins.contains_key("go") { + self.plugins.insert( + Id::raw("go"), + PluginLocator::SourceUrl { + url: "https://github.com/moonrepo/go-plugin/releases/download/v0.6.0/go_plugin.wasm".into() + } + ); + } + + if !self.plugins.contains_key("node") { + self.plugins.insert( + Id::raw("node"), + PluginLocator::SourceUrl { + url: "https://github.com/moonrepo/node-plugin/releases/download/v0.6.0/node_plugin.wasm".into() + } + ); + } + + for depman in ["npm", "pnpm", "yarn"] { + if !self.plugins.contains_key(depman) { + self.plugins.insert( + Id::raw(depman), + PluginLocator::SourceUrl { + url: "https://github.com/moonrepo/node-plugin/releases/download/v0.6.0/node_depman_plugin.wasm".into() + } + ); + } + } + + if !self.plugins.contains_key("python") { + self.plugins.insert( + Id::raw("python"), + PluginLocator::SourceUrl { + url: "https://github.com/moonrepo/python-plugin/releases/download/v0.4.0/python_plugin.wasm".into() + } + ); + } + + if !self.plugins.contains_key("rust") { + self.plugins.insert( + Id::raw("rust"), + PluginLocator::SourceUrl { + url: "https://github.com/moonrepo/rust-plugin/releases/download/v0.5.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.6.0/schema_plugin.wasm".into() + } + ); + } + } + + #[tracing::instrument(skip_all)] + pub fn load_from>( + dir: P, + with_lock: bool, + ) -> miette::Result { + let dir = dir.as_ref(); + let path = dir.join(PROTO_CONFIG_NAME); + + if !path.exists() { + return Ok(PartialProtoConfig::default()); + } + + debug!(file = ?path, "Loading {}", PROTO_CONFIG_NAME); + + let config_path = path.to_string_lossy(); + let config_content = if with_lock { + fs::read_file_with_lock(&path)? + } else { + fs::read_file(&path)? + }; + + let mut config = ConfigLoader::::new() + .code(config_content, Format::Toml)? + .load_partial(&())?; + + config + .validate(&()) + .map_err(|error| ConfigError::Validator { + config: config_path.to_string(), + error, + })?; + + // Because of serde flatten, unknown and invalid fields + // do not trigger validation, so we need to manually handle it + if let Some(fields) = &config.unknown { + let mut error = ValidatorError { + path: schematic::Path::new(vec![]), + errors: vec![], + }; + + for (field, value) in fields { + // Versions show up in both flattened maps... + if config + .versions + .as_ref() + .is_some_and(|versions| versions.contains_key(field)) + { + continue; + } + + let message = if value.is_array() || value.is_table() { + format!("unknown field `{field}`") + } else { + match Id::new(field) { + Ok(_) => format!("invalid version value `{value}`"), + Err(e) => e.to_string(), + } + }; + + error.errors.push(ValidateErrorType::setting( + error.path.join_key(field), + ValidateError::new(message), + )); + } + + if !error.errors.is_empty() { + return Err(ConfigError::Validator { + config: config_path.to_string(), + error, + } + .into()); + } + } + + // Update file paths to be absolute + let make_absolute = |file: &mut PathBuf| { + if file.is_absolute() { + file.to_owned() + } else { + dir.join(file) + } + }; + + if let Some(plugins) = &mut config.plugins { + for locator in plugins.values_mut() { + if let PluginLocator::SourceFile { + path: ref mut source_path, + .. + } = locator + { + *source_path = make_absolute(source_path); + } + } + } + + if let Some(settings) = &mut config.settings { + if let Some(http) = &mut settings.http { + if let Some(root_cert) = &mut http.root_cert { + *root_cert = make_absolute(root_cert); + } + } + } + + Ok(config) + } + + #[tracing::instrument(skip_all)] + pub fn save_to>(dir: P, config: PartialProtoConfig) -> miette::Result { + let path = dir.as_ref().join(PROTO_CONFIG_NAME); + + fs::write_file_with_lock(&path, toml::to_string_pretty(&config).into_diagnostic()?)?; + + Ok(path) + } + + pub fn update, F: FnOnce(&mut PartialProtoConfig)>( + dir: P, + op: F, + ) -> miette::Result { + let dir = dir.as_ref(); + let mut config = Self::load_from(dir, true)?; + + op(&mut config); + + Self::save_to(dir, config) + } +} + +#[derive(Debug)] +pub struct ProtoConfigFile { + pub exists: bool, + pub path: PathBuf, + pub config: PartialProtoConfig, +} + +#[derive(Debug)] +pub struct ProtoConfigManager { + // Paths are sorted from current working directory, + // up until the root or user directory, whichever is first. + // The special `~/.proto/.prototools` config is always + // loaded last, and is the last entry in the list. + // For directories without a config, we still insert + // an empty entry. This helps with traversal logic. + pub files: Vec, + + merged_config: Arc>, +} + +impl ProtoConfigManager { + pub fn load(start_dir: impl AsRef, end_dir: Option<&Path>) -> miette::Result { + trace!("Traversing upwards and loading {} files", PROTO_CONFIG_NAME); + + let mut current_dir = Some(start_dir.as_ref()); + let mut files = vec![]; + + while let Some(dir) = current_dir { + let path = dir.join(PROTO_CONFIG_NAME); + + files.push(ProtoConfigFile { + exists: path.exists(), + path, + config: ProtoConfig::load_from(dir, false)?, + }); + + if end_dir.is_some_and(|end| end == dir) { + break; + } + + current_dir = dir.parent(); + } + + Ok(Self { + files, + merged_config: Arc::new(OnceCell::new()), + }) + } + + pub fn get_merged_config(&self) -> miette::Result<&ProtoConfig> { + self.merged_config.get_or_try_init(|| { + let mut partial = PartialProtoConfig::default(); + let context = &(); + + for file in self.files.iter().rev() { + partial.merge(context, file.config.to_owned())?; + } + + let mut config = ProtoConfig::from_partial(partial.finalize(context)?); + config.inherit_builtin_plugins(); + + Ok(config) + }) + } +} diff --git a/crates/core/src/tool.rs b/crates/core/src/tool.rs index 88404dcd7..9433c9274 100644 --- a/crates/core/src/tool.rs +++ b/crates/core/src/tool.rs @@ -6,8 +6,9 @@ use crate::helpers::{ }; use crate::host_funcs::{create_host_functions, HostData}; use crate::proto::ProtoEnvironment; +use crate::proto_config::ProtoConfig; use crate::shimmer::{get_shim_file_names, ShimContext, SHIM_VERSION}; -use crate::tool_manifest::ToolManifest; +use crate::tool_manifest::{ToolManifest, ToolManifestVersion}; use crate::version_resolver::VersionResolver; use extism::{manifest::Wasm, Manifest as PluginManifest}; use miette::IntoDiagnostic; @@ -25,7 +26,7 @@ use std::path::{Path, PathBuf}; use std::process::Command; use std::sync::Arc; use std::time::{Duration, SystemTime}; -use tracing::{debug, trace, warn}; +use tracing::{debug, info, trace, warn}; use warpgate::{download_from_url_to_file, Id, PluginContainer, PluginLocator, VirtualPath}; #[derive(Debug, Default, Serialize)] @@ -321,11 +322,6 @@ impl Tool { let mut modified = false; - if let Some(default) = sync_changes.default_version { - modified = true; - self.manifest.default_version = Some(default); - } - if let Some(versions) = sync_changes.versions { modified = true; @@ -430,7 +426,14 @@ impl Tool { // Cache the results and create a resolver let mut resolver = VersionResolver::from_output(versions); - resolver.with_manifest(&self.manifest)?; + + resolver.with_manifest(&self.manifest); + + let config = self.proto.load_config()?; + + if let Some(tool_config) = config.tools.get(&self.id) { + resolver.with_config(tool_config); + } Ok(resolver) } @@ -1529,23 +1532,40 @@ impl Tool { ) -> miette::Result { self.resolve_version(initial_version, false).await?; - if self.install(build_from_source).await? { - self.create_executables(true, false).await?; - self.cleanup().await?; - - // Add version to manifest - self.manifest.insert_version( - self.get_resolved_version(), - self.metadata.default_version.clone(), - )?; + if !self.install(build_from_source).await? { + return Ok(false); + } - // Allow plugins to override manifest - self.sync_manifest()?; + self.create_executables(true, false).await?; + self.cleanup().await?; - return Ok(true); - } + let version = self.get_resolved_version(); + let default_version = self + .metadata + .default_version + .clone() + .unwrap_or_else(|| version.to_unresolved_spec()); + + // Add version to manifest + self.manifest.installed_versions.insert(version.clone()); + self.manifest + .versions + .insert(version.clone(), ToolManifestVersion::default()); + self.manifest.save()?; + + // Pin the global version + ProtoConfig::update(self.proto.get_config_dir(true), |config| { + config + .versions + .get_or_insert(Default::default()) + .entry(self.id.clone()) + .or_insert(default_version); + })?; + + // Allow plugins to override manifest + self.sync_manifest()?; - Ok(false) + Ok(true) } /// Teardown the tool by uninstalling the current version, removing the version @@ -1557,12 +1577,29 @@ impl Tool { return Ok(false); } - // Only remove if uninstall was successful - self.manifest.remove_version(self.get_resolved_version())?; + let version = self.get_resolved_version(); + let mut removed_default_version = false; + + // Remove version from manifest + self.manifest.installed_versions.remove(&version); + self.manifest.versions.remove(&version); + self.manifest.save()?; + + // Unpin global version if a match + ProtoConfig::update(self.proto.get_config_dir(true), |config| { + if let Some(versions) = &mut config.versions { + if versions.get(&self.id).is_some_and(|v| v == &version) { + info!("Unpinning global version"); + + versions.remove(&self.id); + removed_default_version = true; + } + } + })?; // If no more default version, delete the symlink, // otherwise the OS will throw errors for missing sources - if self.manifest.default_version.is_none() || self.manifest.installed_versions.is_empty() { + if removed_default_version || self.manifest.installed_versions.is_empty() { for bin in self.get_bin_locations()? { remove_bin_file(bin.path)?; } diff --git a/crates/core/src/tool_loader.rs b/crates/core/src/tool_loader.rs index df234d38e..f0d5e7315 100644 --- a/crates/core/src/tool_loader.rs +++ b/crates/core/src/tool_loader.rs @@ -1,21 +1,18 @@ use crate::error::ProtoError; use crate::proto::ProtoEnvironment; +use crate::proto_config::{ProtoConfig, SCHEMA_PLUGIN_KEY}; use crate::tool::Tool; -use crate::tools_config::{ToolsConfig, SCHEMA_PLUGIN_KEY}; -use crate::user_config::UserConfig; use extism::{manifest::Wasm, Manifest}; use miette::IntoDiagnostic; -use proto_pdk_api::{HostArch, HostEnvironment, HostOS, UserConfigSettings}; +use proto_pdk_api::{HostArch, HostEnvironment, HostOS}; use starbase_utils::{json, toml}; use std::path::PathBuf; -use std::{env, path::Path}; use tracing::{debug, trace}; -use warpgate::{create_http_client_with_options, to_virtual_path, Id, PluginLocator}; +use warpgate::{to_virtual_path, Id, PluginLocator}; pub fn inject_default_manifest_config( id: &Id, proto: &ProtoEnvironment, - user_config: &UserConfig, manifest: &mut Manifest, ) -> miette::Result<()> { trace!(id = id.as_str(), "Storing tool identifier"); @@ -24,18 +21,19 @@ pub fn inject_default_manifest_config( .config .insert("proto_tool_id".to_string(), id.to_string()); - let value = json::to_string(&UserConfigSettings { - auto_clean: user_config.auto_clean, - auto_install: user_config.auto_install, - node_intercept_globals: user_config.node_intercept_globals, - }) - .into_diagnostic()?; + let config = proto.load_config()?; - trace!(config = %value, "Storing user configuration"); + if let Some(tool_config) = config.tools.get(id) { + if !tool_config.config.is_empty() { + let value = json::to_string(&tool_config.config).into_diagnostic()?; - manifest - .config - .insert("proto_user_config".to_string(), value); + trace!(config = %value, "Storing tool configuration"); + + manifest + .config + .insert("proto_tool_config".to_string(), value); + } + } let paths_map = manifest.allowed_paths.as_ref().unwrap(); @@ -56,53 +54,34 @@ pub fn inject_default_manifest_config( Ok(()) } -pub fn locate_tool( - id: &Id, - proto: &ProtoEnvironment, - user_config: &UserConfig, - current_dir_only: bool, -) -> miette::Result { +pub fn locate_tool(id: &Id, proto: &ProtoEnvironment) -> miette::Result { let mut locator = None; + let configs = proto.load_config_manager()?; - debug!( - tool = id.as_str(), - "Traversing upwards to find a configured plugin" - ); + debug!(tool = id.as_str(), "Finding 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); + // Check config files for plugins + for file in &configs.files { + if let Some(plugins) = &file.config.plugins { + if let Some(maybe_locator) = plugins.get(id) { + debug!(file = ?file.path, plugin = maybe_locator.to_string(), "Found a plugin"); - while let Some(dir) = current_dir { - let tools_config = ToolsConfig::load_from(dir)?; - - if let Some(maybe_locator) = tools_config.plugins.get(id) { locator = Some(maybe_locator.to_owned()); break; } - - // Don't traverse passed the home directory, - // or only want to check the current directory - if dir == proto.home || current_dir_only { - break; - } - - current_dir = dir.parent(); } } - // Then check the user's config + // And finally the built-in plugins 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(); + let builtin_plugins = ProtoConfig::builtin_plugins(); if let Some(maybe_locator) = builtin_plugins.get(id) { + debug!( + plugin = maybe_locator.to_string(), + "Using a built-in plugin" + ); + locator = Some(maybe_locator.to_owned()); } } @@ -114,19 +93,16 @@ pub fn locate_tool( Ok(locator) } -pub async fn load_schema_plugin( +pub async fn load_schema_plugin_with_proto( proto: impl AsRef, - user_config: &UserConfig, ) -> miette::Result { let proto = proto.as_ref(); - let http_client = create_http_client_with_options(user_config.http.clone())?; - let plugin_loader = proto.get_plugin_loader(); - let schema_id = Id::raw(SCHEMA_PLUGIN_KEY); - let schema_locator = locate_tool(&schema_id, proto, user_config, true)?; + let schema_locator = locate_tool(&schema_id, proto)?; - plugin_loader - .load_plugin_with_client(schema_id, schema_locator, &http_client) + proto + .get_plugin_loader() + .load_plugin_with_client(schema_id, schema_locator, proto.get_http_client()?) .await } @@ -134,16 +110,14 @@ pub async fn load_tool_from_locator( id: impl AsRef, proto: impl AsRef, locator: impl AsRef, - user_config: &UserConfig, ) -> miette::Result { let id = id.as_ref(); let proto = proto.as_ref(); let locator = locator.as_ref(); - let http_client = create_http_client_with_options(user_config.http.clone())?; - let plugin_loader = proto.get_plugin_loader(); - let plugin_path = plugin_loader - .load_plugin_with_client(&id, locator, &http_client) + let plugin_path = proto + .get_plugin_loader() + .load_plugin_with_client(id, locator, proto.get_http_client()?) .await?; // If a TOML plugin, we need to load the WASM plugin for it, @@ -157,7 +131,7 @@ pub async fn load_tool_from_locator( let mut manifest = Tool::create_plugin_manifest( proto, - Wasm::file(load_schema_plugin(proto, user_config).await?), + Wasm::file(load_schema_plugin_with_proto(proto).await?), )?; // Convert TOML to JSON @@ -176,7 +150,7 @@ pub async fn load_tool_from_locator( Tool::create_plugin_manifest(proto, Wasm::file(plugin_path))? }; - inject_default_manifest_config(id, proto, user_config, &mut manifest)?; + inject_default_manifest_config(id, proto, &mut manifest)?; let mut tool = Tool::load_from_manifest(id, proto, manifest)?; tool.locator = Some(locator.to_owned()); @@ -184,10 +158,6 @@ pub async fn load_tool_from_locator( Ok(tool) } -pub async fn load_tool(id: &Id) -> miette::Result { - let proto = ProtoEnvironment::new()?; - let user_config = proto.load_user_config()?; - let locator = locate_tool(id, &proto, &user_config, false)?; - - load_tool_from_locator(id, proto, locator, &user_config).await +pub async fn load_tool_with_proto(id: &Id, proto: &ProtoEnvironment) -> miette::Result { + load_tool_from_locator(id, proto, locate_tool(id, proto)?).await } diff --git a/crates/core/src/tool_manifest.rs b/crates/core/src/tool_manifest.rs index 9f9c75fff..b946de50f 100644 --- a/crates/core/src/tool_manifest.rs +++ b/crates/core/src/tool_manifest.rs @@ -1,12 +1,13 @@ use crate::helpers::{read_json_file_with_lock, write_json_file_with_lock}; use serde::{Deserialize, Serialize}; +use starbase_styles::color; use std::{ collections::{BTreeMap, HashSet}, env, path::{Path, PathBuf}, time::SystemTime, }; -use tracing::{debug, info}; +use tracing::{debug, warn}; use version_spec::*; fn now() -> u128 { @@ -40,7 +41,9 @@ impl Default for ToolManifestVersion { #[serde(default)] pub struct ToolManifest { // Partial versions allowed + #[deprecated] pub aliases: BTreeMap, + #[deprecated] pub default_version: Option, // Full versions only @@ -71,6 +74,22 @@ impl ToolManifest { manifest.path = path.to_owned(); + #[allow(deprecated)] + if !manifest.aliases.is_empty() { + warn!( + "Found legacy aliases in tool manifest, please run {} to migrate them", + color::shell("proto migrate v0.24") + ); + } + + #[allow(deprecated)] + if manifest.default_version.is_some() { + warn!( + "Found legacy global version in tool manifest, please run {} to migrate it", + color::shell("proto migrate v0.24") + ); + } + Ok(manifest) } @@ -83,54 +102,8 @@ impl ToolManifest { Ok(()) } - pub fn insert_version( - &mut self, - version: VersionSpec, - default_version: Option, - ) -> miette::Result<()> { - if self.default_version.is_none() { - self.default_version = - Some(default_version.unwrap_or_else(|| version.to_unresolved_spec())); - } - - self.installed_versions.insert(version.clone()); - - self.versions - .insert(version, ToolManifestVersion::default()); - - self.save()?; - - Ok(()) - } - - pub fn remove_version(&mut self, version: VersionSpec) -> miette::Result<()> { - self.installed_versions.remove(&version); - - // Remove default version if nothing available - if (self.installed_versions.is_empty() && self.default_version.is_some()) - || self.default_version.as_ref().is_some_and(|v| v == &version) - { - info!("Unpinning default global version"); - - self.default_version = None; - } - - self.versions.remove(&version); - - self.save()?; - - Ok(()) - } - pub fn track_used_at(&mut self, version: VersionSpec) { - self.versions - .entry(version) - .and_modify(|v| { - v.last_used_at = Some(now()); - }) - .or_insert(ToolManifestVersion { - last_used_at: Some(now()), - ..ToolManifestVersion::default() - }); + let entry = self.versions.entry(version).or_default(); + entry.last_used_at = Some(now()); } } diff --git a/crates/core/src/tools_config.rs b/crates/core/src/tools_config.rs deleted file mode 100644 index 4c0c7f4ce..000000000 --- a/crates/core/src/tools_config.rs +++ /dev/null @@ -1,204 +0,0 @@ -use miette::IntoDiagnostic; -use serde::{Deserialize, Serialize}; -use starbase_utils::{fs, toml}; -use std::collections::BTreeMap; -use std::env; -use std::path::{Path, PathBuf}; -use tracing::{debug, trace}; -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() -} - -#[derive(Debug, Default, Deserialize, Serialize)] -#[serde(default, rename_all = "kebab-case")] -pub struct ToolsConfig { - #[serde(flatten, skip_serializing_if = "is_empty")] - pub tools: BTreeMap, - - #[serde(skip_serializing_if = "is_empty")] - pub plugins: BTreeMap, - - #[serde(skip)] - pub path: PathBuf, -} - -impl ToolsConfig { - pub fn builtin_plugins() -> BTreeMap { - let mut config = ToolsConfig::default(); - config.inherit_builtin_plugins(); - config.plugins - } - - #[tracing::instrument(skip_all)] - pub fn load() -> miette::Result { - let working_dir = env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); - - Self::load_from(working_dir) - } - - pub fn load_from>(dir: P) -> miette::Result { - let path = dir.as_ref().join(TOOLS_CONFIG_NAME); - - let mut config: ToolsConfig = if path.exists() { - debug!(file = ?path, "Loading {}", TOOLS_CONFIG_NAME); - - toml::from_str(&fs::read_file(&path)?).into_diagnostic()? - } else { - ToolsConfig::default() - }; - - config.path = path.clone(); - - // Update plugin file paths to be absolute - for locator in config.plugins.values_mut() { - if let PluginLocator::SourceFile { - path: ref mut source_path, - .. - } = locator - { - *source_path = path.parent().unwrap().join(&source_path); - } - } - - Ok(config) - } - - pub fn load_closest() -> miette::Result { - let working_dir = env::current_dir().expect("Unknown current working directory!"); - - Self::load_upwards_from(working_dir, true) - } - - pub fn load_upwards() -> miette::Result { - let working_dir = env::current_dir().expect("Unknown current working directory!"); - - Self::load_upwards_from(working_dir, false) - } - - pub fn load_upwards_from

(starting_dir: P, stop_at_first: bool) -> miette::Result - where - P: AsRef, - { - trace!("Traversing upwards and loading .prototools files"); - - let mut current_dir = Some(starting_dir.as_ref()); - let mut config = ToolsConfig::default(); - - while let Some(dir) = current_dir { - if dir.join(TOOLS_CONFIG_NAME).exists() { - let mut parent_config = Self::load_from(dir)?; - parent_config.merge(config); - - config = parent_config; - - if stop_at_first { - break; - } - } - - match dir.parent() { - Some(parent) => { - current_dir = Some(parent); - } - None => { - break; - } - }; - } - - Ok(config) - } - - pub fn save(&self) -> miette::Result<()> { - fs::write_file(&self.path, toml::to_string_pretty(self).into_diagnostic()?)?; - - Ok(()) - } - - pub fn inherit_builtin_plugins(&mut self) { - if !self.plugins.contains_key("bun") { - self.plugins.insert( - Id::raw("bun"), - PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/bun-plugin/releases/download/v0.5.0/bun_plugin.wasm".into() - } - ); - } - - if !self.plugins.contains_key("deno") { - self.plugins.insert( - Id::raw("deno"), - PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/deno-plugin/releases/download/v0.5.0/deno_plugin.wasm".into() - } - ); - } - - if !self.plugins.contains_key("go") { - self.plugins.insert( - Id::raw("go"), - PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/go-plugin/releases/download/v0.5.0/go_plugin.wasm".into() - } - ); - } - - if !self.plugins.contains_key("node") { - self.plugins.insert( - Id::raw("node"), - PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/node-plugin/releases/download/v0.5.3/node_plugin.wasm".into() - } - ); - } - - for depman in ["npm", "pnpm", "yarn"] { - if !self.plugins.contains_key(depman) { - self.plugins.insert( - Id::raw(depman), - PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/node-plugin/releases/download/v0.5.3/node_depman_plugin.wasm".into() - } - ); - } - } - - if !self.plugins.contains_key("python") { - self.plugins.insert( - Id::raw("python"), - PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/python-plugin/releases/download/v0.3.0/python_plugin.wasm".into() - } - ); - } - - if !self.plugins.contains_key("rust") { - self.plugins.insert( - Id::raw("rust"), - PluginLocator::SourceUrl { - url: "https://github.com/moonrepo/rust-plugin/releases/download/v0.4.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/schema_plugin.wasm".into() - } - ); - } - } - - pub fn merge(&mut self, other: ToolsConfig) { - self.tools.extend(other.tools); - self.plugins.extend(other.plugins); - } -} diff --git a/crates/core/src/user_config.rs b/crates/core/src/user_config.rs index 77717be59..4e5f7145c 100644 --- a/crates/core/src/user_config.rs +++ b/crates/core/src/user_config.rs @@ -1,39 +1,26 @@ -use crate::helpers::get_proto_home; +#![allow(deprecated)] + +use crate::proto_config::{DetectStrategy, PinType}; use miette::IntoDiagnostic; use serde::{Deserialize, Serialize}; use starbase_utils::{fs, toml}; use std::collections::BTreeMap; -use std::env; use std::path::{Path, PathBuf}; use tracing::debug; use warpgate::{HttpOptions, Id, PluginLocator}; pub const USER_CONFIG_NAME: &str = "config.toml"; +#[deprecated] #[derive(Debug, Default, Deserialize, PartialEq, Serialize)] -#[serde(rename_all = "kebab-case")] -pub enum DetectStrategy { - #[default] - FirstAvailable, - PreferPrototools, -} - -#[derive(Debug, Deserialize, PartialEq, Serialize)] -#[serde(rename_all = "kebab-case")] -pub enum PinType { - Global, - Local, -} - -#[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(default, rename_all = "kebab-case")] pub struct UserConfig { - pub auto_clean: bool, - pub auto_install: bool, - pub detect_strategy: DetectStrategy, - pub node_intercept_globals: bool, + pub auto_clean: Option, + pub auto_install: Option, + pub detect_strategy: Option, + pub node_intercept_globals: Option, pub pin_latest: Option, - pub http: HttpOptions, + pub http: Option, pub plugins: BTreeMap, #[serde(skip)] @@ -41,6 +28,7 @@ pub struct UserConfig { } impl UserConfig { + #[tracing::instrument(skip_all)] pub fn load_from>(dir: P) -> miette::Result { let dir = dir.as_ref(); let path = dir.join(USER_CONFIG_NAME); @@ -54,68 +42,11 @@ impl UserConfig { debug!(file = ?path, "Loading {}", USER_CONFIG_NAME); - let contents = fs::read_file(&path)?; + let contents = fs::read_file_with_lock(&path)?; let mut config: UserConfig = toml::from_str(&contents).into_diagnostic()?; - let make_absolute = |file: &mut PathBuf| { - if file.is_absolute() { - file.to_owned() - } else { - dir.join(file) - } - }; - - // Update plugin file paths to be absolute - for locator in config.plugins.values_mut() { - if let PluginLocator::SourceFile { - path: ref mut source_path, - .. - } = locator - { - *source_path = make_absolute(source_path); - } - } - - if let Some(root_cert) = &mut config.http.root_cert { - *root_cert = make_absolute(root_cert); - } - config.path = path; Ok(config) } - - #[tracing::instrument(skip_all)] - pub fn load() -> miette::Result { - Self::load_from(get_proto_home()?) - } - - pub fn save(&self) -> miette::Result<()> { - fs::write_file(&self.path, toml::to_string_pretty(self).into_diagnostic()?)?; - - Ok(()) - } -} - -impl Default for UserConfig { - fn default() -> Self { - Self { - auto_clean: from_var("PROTO_AUTO_CLEAN", false), - auto_install: from_var("PROTO_AUTO_INSTALL", false), - detect_strategy: DetectStrategy::default(), - http: HttpOptions::default(), - node_intercept_globals: from_var("PROTO_NODE_INTERCEPT_GLOBALS", true), - pin_latest: None, - plugins: BTreeMap::default(), - path: PathBuf::new(), - } - } -} - -fn from_var(name: &str, fallback: bool) -> bool { - if let Ok(value) = env::var(name) { - return value == "1" || value == "true" || value == "on"; - } - - fallback } diff --git a/crates/core/src/version_detector.rs b/crates/core/src/version_detector.rs index 97d41959e..84f0ca817 100644 --- a/crates/core/src/version_detector.rs +++ b/crates/core/src/version_detector.rs @@ -1,53 +1,40 @@ use crate::error::ProtoError; +use crate::proto_config::*; use crate::tool::Tool; -use crate::tools_config::ToolsConfig; -use crate::user_config::DetectStrategy; -use std::{env, path::Path}; +use std::env; use tracing::{debug, trace}; use version_spec::*; pub async fn detect_version_first_available( tool: &Tool, - start_dir: &Path, - end_dir: &Path, + config_manager: &ProtoConfigManager, ) -> miette::Result> { - let mut current_dir: Option<&Path> = Some(start_dir); - - while let Some(dir) = current_dir { - trace!( - tool = tool.id.as_str(), - dir = ?dir, - "Checking directory", - ); - - let config = ToolsConfig::load_from(dir)?; - - if let Some(version) = config.tools.get(&tool.id) { - debug!( - tool = tool.id.as_str(), - version = version.to_string(), - file = ?config.path, - "Detected version from .prototools file", - ); - - return Ok(Some(version.to_owned())); + for file in &config_manager.files { + if let Some(versions) = &file.config.versions { + if let Some(version) = versions.get(tool.id.as_str()) { + debug!( + tool = tool.id.as_str(), + version = version.to_string(), + file = ?file.path, + "Detected version from {} file", PROTO_CONFIG_NAME + ); + + return Ok(Some(version.to_owned())); + } } + let dir = file.path.parent().unwrap(); + if let Some(version) = tool.detect_version_from(dir).await? { debug!( tool = tool.id.as_str(), version = version.to_string(), + dir = ?dir, "Detected version from tool's ecosystem" ); return Ok(Some(version)); } - - if dir == end_dir { - break; - } - - current_dir = dir.parent(); } Ok(None) @@ -55,58 +42,38 @@ pub async fn detect_version_first_available( pub async fn detect_version_prefer_prototools( tool: &Tool, - start_dir: &Path, - end_dir: &Path, + config_manager: &ProtoConfigManager, ) -> miette::Result> { - let mut config_version = None; - let mut config_path = None; - let mut ecosystem_version = None; - let mut current_dir: Option<&Path> = Some(start_dir); - - while let Some(dir) = current_dir { - trace!( - tool = tool.id.as_str(), - dir = ?dir, - "Checking directory", - ); - - if config_version.is_none() { - let mut config = ToolsConfig::load_from(dir)?; - - config_version = config.tools.remove(&tool.id); - config_path = Some(config.path); - } - - if ecosystem_version.is_none() { - ecosystem_version = tool.detect_version_from(dir).await?; - } - - if dir == end_dir { - break; + // Check config files first + for file in &config_manager.files { + if let Some(versions) = &file.config.versions { + if let Some(version) = versions.get(tool.id.as_str()) { + debug!( + tool = tool.id.as_str(), + version = version.to_string(), + file = ?file.path, + "Detected version from {} file", PROTO_CONFIG_NAME + ); + + return Ok(Some(version.to_owned())); + } } - - current_dir = dir.parent(); } - if let Some(version) = config_version { - debug!( - tool = tool.id.as_str(), - version = version.to_string(), - file = ?config_path.unwrap(), - "Detected version from .prototools file", - ); - - return Ok(Some(version.to_owned())); - } + // Then check the ecosystem + for file in &config_manager.files { + let dir = file.path.parent().unwrap(); - if let Some(version) = ecosystem_version { - debug!( - tool = tool.id.as_str(), - version = version.to_string(), - "Detected version from tool's ecosystem" - ); + if let Some(version) = tool.detect_version_from(dir).await? { + debug!( + tool = tool.id.as_str(), + version = version.to_string(), + dir = ?dir, + "Detected version from tool's ecosystem" + ); - return Ok(Some(version)); + return Ok(Some(version)); + } } Ok(None) @@ -126,7 +93,7 @@ pub async fn detect_version( return Ok(candidate); } - // Env var takes highest priorit + // Env var takes highest priority let env_var = format!("{}_VERSION", tool.get_env_var_prefix()); if let Ok(session_version) = env::var(&env_var) { @@ -143,45 +110,29 @@ pub async fn detect_version( error, })?, ); - } else { - trace!( - tool = tool.id.as_str(), - "Attempting to find local version from config files" - ); - } - - // Traverse upwards and attempt to detect a local version - if let Ok(working_dir) = env::current_dir() { - let user_config = tool.proto.load_user_config()?; - let detected_version = match user_config.detect_strategy { - DetectStrategy::FirstAvailable => { - detect_version_first_available(tool, &working_dir, &tool.proto.home).await? - } - DetectStrategy::PreferPrototools => { - detect_version_prefer_prototools(tool, &working_dir, &tool.proto.home).await? - } - }; - - if let Some(version) = detected_version { - return Ok(version); - } } - // If still no version, load the global version + // Traverse upwards and attempt to detect a version trace!( tool = tool.id.as_str(), - "Attempting to use global version from manifest", + "Attempting to find version from {} files", + PROTO_CONFIG_NAME ); - if let Some(global_version) = &tool.manifest.default_version { - debug!( - tool = tool.id.as_str(), - version = global_version.to_string(), - file = ?tool.manifest.path, - "Detected global version from manifest", - ); + let config_manager = tool.proto.load_config_manager()?; + let config = tool.proto.load_config()?; + + let detected_version = match config.settings.detect_strategy { + DetectStrategy::FirstAvailable => { + detect_version_first_available(tool, config_manager).await? + } + DetectStrategy::PreferPrototools => { + detect_version_prefer_prototools(tool, config_manager).await? + } + }; - return Ok(global_version.to_owned()); + if let Some(version) = detected_version { + return Ok(version); } // We didn't find anything! diff --git a/crates/core/src/version_resolver.rs b/crates/core/src/version_resolver.rs index d1bb19c37..32e5c9463 100644 --- a/crates/core/src/version_resolver.rs +++ b/crates/core/src/version_resolver.rs @@ -1,17 +1,18 @@ use crate::error::ProtoError; +use crate::proto_config::ProtoToolConfig; use crate::tool_manifest::ToolManifest; -use crate::VersionSpec; use proto_pdk_api::LoadVersionsOutput; use semver::{Version, VersionReq}; use std::collections::{BTreeMap, HashSet}; use version_spec::*; -#[derive(Debug, Default)] +#[derive(Default)] pub struct VersionResolver<'tool> { pub aliases: BTreeMap, pub versions: Vec, manifest: Option<&'tool ToolManifest>, + config: Option<&'tool ProtoToolConfig>, } impl<'tool> VersionResolver<'tool> { @@ -31,33 +32,35 @@ impl<'tool> VersionResolver<'tool> { .insert("latest".into(), UnresolvedVersionSpec::Version(latest)); } - // if let Some(canary) = output.canary { - // resolver - // .aliases - // .insert("canary".into(), UnresolvedVersionSpec::Version(canary)); - // } - // Sort from newest to oldest resolver.versions.sort_by(|a, d| d.cmp(a)); resolver } - pub fn with_manifest(&mut self, manifest: &'tool ToolManifest) -> miette::Result<()> { + pub fn with_manifest(&mut self, manifest: &'tool ToolManifest) { self.manifest = Some(manifest); + } - Ok(()) + pub fn with_config(&mut self, config: &'tool ProtoToolConfig) { + self.config = Some(config); } pub fn resolve(&self, candidate: &UnresolvedVersionSpec) -> miette::Result { - resolve_version(candidate, &self.versions, &self.aliases, self.manifest) + resolve_version( + candidate, + &self.versions, + &self.aliases, + self.manifest, + self.config, + ) } pub fn resolve_without_manifest( &self, candidate: &UnresolvedVersionSpec, ) -> miette::Result { - resolve_version(candidate, &self.versions, &self.aliases, None) + resolve_version(candidate, &self.versions, &self.aliases, None, None) } } @@ -91,6 +94,7 @@ pub fn resolve_version( versions: &[Version], aliases: &BTreeMap, manifest: Option<&ToolManifest>, + config: Option<&ProtoToolConfig>, ) -> miette::Result { let remote_versions = versions.iter().collect::>(); let installed_versions = if let Some(manifest) = manifest { @@ -106,8 +110,8 @@ pub fn resolve_version( UnresolvedVersionSpec::Alias(alias) => { let mut alias_value = None; - if let Some(manifest) = manifest { - alias_value = manifest.aliases.get(alias); + if let Some(config) = config { + alias_value = config.aliases.get(alias); } if alias_value.is_none() { @@ -115,7 +119,7 @@ pub fn resolve_version( } if let Some(value) = alias_value { - return resolve_version(value, versions, aliases, manifest); + return resolve_version(value, versions, aliases, manifest, config); } } UnresolvedVersionSpec::Req(req) => { diff --git a/crates/core/tests/proto_config_test.rs b/crates/core/tests/proto_config_test.rs new file mode 100644 index 000000000..67ed65017 --- /dev/null +++ b/crates/core/tests/proto_config_test.rs @@ -0,0 +1,483 @@ +use proto_core::{ + DetectStrategy, PartialProtoSettingsConfig, PinType, ProtoConfig, ProtoConfigManager, +}; +use schematic::ConfigError; +use starbase_sandbox::create_empty_sandbox; +use starbase_utils::json::JsonValue; +use std::collections::BTreeMap; +use std::env; +use version_spec::UnresolvedVersionSpec; +use warpgate::{GitHubLocator, HttpOptions, Id, PluginLocator}; + +fn handle_error(report: miette::Report) { + panic!( + "{}", + report + .downcast_ref::() + .unwrap() + .to_full_string() + ); +} + +mod proto_config { + use super::*; + + #[test] + #[should_panic(expected = "invalid version value `123`")] + fn errors_for_non_version_string() { + let sandbox = create_empty_sandbox(); + sandbox.create_file(".prototools", "node = 123"); + + handle_error(ProtoConfig::load_from(sandbox.path(), false).unwrap_err()); + } + + #[test] + #[should_panic(expected = "unknown field `other`")] + fn errors_for_non_plugins_table() { + let sandbox = create_empty_sandbox(); + sandbox.create_file(".prototools", "[other]\nkey = 123"); + + handle_error(ProtoConfig::load_from(sandbox.path(), false).unwrap_err()); + } + + #[test] + #[should_panic(expected = "must be a valid kebab-case string.")] + fn errors_for_non_kebab_id() { + let sandbox = create_empty_sandbox(); + sandbox.create_file(".prototools", "fooBar = \"1.2.3\""); + + handle_error(ProtoConfig::load_from(sandbox.path(), false).unwrap_err()); + } + + #[test] + fn can_set_settings() { + let sandbox = create_empty_sandbox(); + sandbox.create_file( + ".prototools", + r#" +[settings] +auto-clean = true +auto-install = true +pin-latest = "global" +"#, + ); + + let config = ProtoConfig::load_from(sandbox.path(), false).unwrap(); + + assert_eq!( + config.settings.unwrap(), + PartialProtoSettingsConfig { + auto_clean: Some(true), + auto_install: Some(true), + pin_latest: Some(PinType::Global), + ..Default::default() + } + ); + } + + #[test] + fn can_set_settings_from_env_vars() { + let sandbox = create_empty_sandbox(); + sandbox.create_file(".prototools", ""); + + env::set_var("PROTO_AUTO_CLEAN", "1"); + env::set_var("PROTO_AUTO_INSTALL", "true"); + env::set_var("PROTO_DETECT_STRATEGY", "prefer-prototools"); + env::set_var("PROTO_PIN_LATEST", "local"); + + // Need to use the manager since it runs the finalize process + let manager = ProtoConfigManager::load(sandbox.path(), None).unwrap(); + let config = manager.get_merged_config().unwrap(); + + assert!(config.settings.auto_clean); + assert!(config.settings.auto_install); + assert_eq!( + config.settings.detect_strategy, + DetectStrategy::PreferPrototools + ); + assert_eq!(config.settings.pin_latest, Some(PinType::Local)); + + env::remove_var("PROTO_AUTO_CLEAN"); + env::remove_var("PROTO_AUTO_INSTALL"); + env::remove_var("PROTO_DETECT_STRATEGY"); + env::remove_var("PROTO_PIN_LATEST"); + } + + #[test] + fn can_set_plugins() { + let sandbox = create_empty_sandbox(); + sandbox.create_file( + ".prototools", + r#" +[plugins] +foo = "github:moonrepo/foo" +bar = "source:https://moonrepo.dev/path/file.wasm" +"#, + ); + + let config = ProtoConfig::load_from(sandbox.path(), false).unwrap(); + + assert_eq!( + config.plugins.unwrap(), + BTreeMap::from_iter([ + ( + Id::raw("bar"), + PluginLocator::SourceUrl { + url: "https://moonrepo.dev/path/file.wasm".into() + } + ), + ( + Id::raw("foo"), + PluginLocator::GitHub(GitHubLocator { + file_prefix: "foo_plugin".into(), + repo_slug: "moonrepo/foo".into(), + tag: None, + }) + ), + ]) + ); + } + + #[test] + fn updates_plugin_files_to_absolute() { + let sandbox = create_empty_sandbox(); + sandbox.create_file( + ".prototools", + r#" +[plugins] +foo = "source:../file.wasm" +"#, + ); + + let config = ProtoConfig::load_from(sandbox.path(), false).unwrap(); + + assert_eq!( + config.plugins.unwrap(), + BTreeMap::from_iter([( + Id::raw("foo"), + PluginLocator::SourceFile { + file: "../file.wasm".into(), + path: sandbox.path().join("../file.wasm") + } + )]) + ); + } + + #[test] + fn updates_root_cert_to_absolute() { + let sandbox = create_empty_sandbox(); + sandbox.create_file( + ".prototools", + r#" +[settings.http] +root-cert = "../cert.pem" +"#, + ); + + let config = ProtoConfig::load_from(sandbox.path(), false).unwrap(); + + assert_eq!( + config.settings.unwrap().http.unwrap(), + HttpOptions { + root_cert: Some(sandbox.path().join("../cert.pem")), + ..Default::default() + } + ); + } + + #[test] + fn parses_plugins_table() { + let sandbox = create_empty_sandbox(); + sandbox.create_file( + ".prototools", + r#" + node = "12.0.0" + rust = "stable" + + [plugins] + foo = "source:./test.toml" + kebab-case = "source:./camel.toml" + "#, + ); + + let config = ProtoConfig::load_from(sandbox.path(), false).unwrap(); + + assert_eq!( + config.versions.unwrap(), + BTreeMap::from_iter([ + ( + Id::raw("node"), + UnresolvedVersionSpec::parse("12.0.0").unwrap() + ), + ( + Id::raw("rust"), + UnresolvedVersionSpec::Alias("stable".into()) + ), + ]) + ); + + assert_eq!( + config.plugins.unwrap(), + BTreeMap::from_iter([ + ( + Id::raw("foo"), + PluginLocator::SourceFile { + file: "./test.toml".into(), + path: sandbox.path().join("./test.toml") + } + ), + ( + Id::raw("kebab-case"), + PluginLocator::SourceFile { + file: "./camel.toml".into(), + path: sandbox.path().join("./camel.toml") + } + ) + ]) + ); + } + + #[test] + fn formats_plugins_table() { + let sandbox = create_empty_sandbox(); + let mut config = ProtoConfig::load_from(sandbox.path(), false).unwrap(); + let versions = config.versions.get_or_insert(Default::default()); + + versions.insert( + Id::raw("node"), + UnresolvedVersionSpec::parse("12.0.0").unwrap(), + ); + versions.insert( + Id::raw("rust"), + UnresolvedVersionSpec::Alias("stable".into()), + ); + + let plugins = config.plugins.get_or_insert(Default::default()); + + plugins.insert( + Id::raw("foo"), + PluginLocator::SourceFile { + file: "./test.toml".into(), + path: sandbox.path().join("./test.toml"), + }, + ); + + let path = ProtoConfig::save_to(sandbox.path(), config).unwrap(); + + assert_eq!( + std::fs::read_to_string(path).unwrap(), + r#"node = "12.0.0" +rust = "stable" + +[plugins] +foo = "source:./test.toml" +"#, + ); + } + + mod tool_config { + use super::*; + + #[test] + fn can_set_extra_settings() { + let sandbox = create_empty_sandbox(); + sandbox.create_file( + ".prototools", + r#" +[tools.node] +bundled-npm = "bundled" +intercept-globals = false +"#, + ); + + let config = ProtoConfig::load_from(sandbox.path(), false).unwrap(); + + assert_eq!( + config + .tools + .unwrap() + .get("node") + .unwrap() + .config + .as_ref() + .unwrap(), + &BTreeMap::from_iter([ + ( + "bundled-npm".to_owned(), + JsonValue::String("bundled".into()) + ), + ("intercept-globals".to_owned(), JsonValue::Bool(false)), + ]) + ); + } + + #[test] + fn merges_plugin_settings() { + let sandbox = create_empty_sandbox(); + sandbox.create_file( + "a/b/.prototools", + r#" +[tools.node] +value = "b" +"#, + ); + sandbox.create_file( + "a/.prototools", + r#" +[tools.node] +depth = 1 +"#, + ); + sandbox.create_file( + ".prototools", + r#" +[tools.node] +value = "root" +"#, + ); + + let config = ProtoConfigManager::load(sandbox.path().join("a/b"), None) + .unwrap() + .get_merged_config() + .unwrap() + .to_owned(); + + assert_eq!( + config.tools.get("node").unwrap().config, + BTreeMap::from_iter([ + ("value".to_owned(), JsonValue::String("b".into())), + ("depth".to_owned(), JsonValue::from(1)), + ]) + ); + } + + #[test] + fn merges_aliases() { + let sandbox = create_empty_sandbox(); + sandbox.create_file( + "a/b/.prototools", + r#" +[tools.node.aliases] +value = "1.2.3" +"#, + ); + sandbox.create_file( + "a/.prototools", + r#" +[tools.node.aliases] +stable = "1.0.0" +"#, + ); + sandbox.create_file( + ".prototools", + r#" +[tools.node.aliases] +value = "4.5.6" +"#, + ); + + let config = ProtoConfigManager::load(sandbox.path().join("a/b"), None) + .unwrap() + .get_merged_config() + .unwrap() + .to_owned(); + + assert_eq!( + config.tools.get("node").unwrap().aliases, + BTreeMap::from_iter([ + ( + "stable".to_owned(), + UnresolvedVersionSpec::parse("1.0.0").unwrap() + ), + ( + "value".to_owned(), + UnresolvedVersionSpec::parse("1.2.3").unwrap() + ), + ]) + ); + } + } +} + +mod proto_config_manager { + use super::*; + + #[test] + fn merges_traversing_upwards() { + use starbase_sandbox::pretty_assertions::assert_eq; + let sandbox = create_empty_sandbox(); + + sandbox.create_file( + "one/two/three/.prototools", + r#" +node = "1.2.3" + +[plugins] +node = "source:./node.toml" +"#, + ); + + sandbox.create_file( + "one/two/.prototools", + r#" +[plugins] +bun = "source:../bun.wasm" +"#, + ); + + sandbox.create_file( + "one/.prototools", + r#" +bun = "4.5.6" + +[plugins] +node = "source:../node.toml" +"#, + ); + + sandbox.create_file( + ".prototools", + r#" +node = "7.8.9" +deno = "7.8.9" +"#, + ); + + let manager = ProtoConfigManager::load(sandbox.path().join("one/two/three"), None).unwrap(); + let config = manager.get_merged_config().unwrap(); + + assert_eq!( + config.versions, + BTreeMap::from_iter([ + ( + Id::raw("node"), + UnresolvedVersionSpec::parse("1.2.3").unwrap() + ), + ( + Id::raw("bun"), + UnresolvedVersionSpec::parse("4.5.6").unwrap() + ), + ( + Id::raw("deno"), + UnresolvedVersionSpec::parse("7.8.9").unwrap() + ), + ]) + ); + + assert_eq!( + config.plugins.get("node").unwrap(), + &PluginLocator::SourceFile { + file: "./node.toml".into(), + path: sandbox.path().join("one/two/three/./node.toml") + } + ); + + assert_eq!( + config.plugins.get("bun").unwrap(), + &PluginLocator::SourceFile { + file: "../bun.wasm".into(), + path: sandbox.path().join("one/two/../bun.wasm") + } + ); + } +} diff --git a/crates/core/tests/tools_config_test.rs b/crates/core/tests/tools_config_test.rs deleted file mode 100644 index 3ea89cc50..000000000 --- a/crates/core/tests/tools_config_test.rs +++ /dev/null @@ -1,206 +0,0 @@ -use proto_core::{ToolsConfig, UnresolvedVersionSpec}; -use starbase_sandbox::create_empty_sandbox; -use std::collections::BTreeMap; -use std::str::FromStr; -use warpgate::{Id, PluginLocator}; - -mod tools_config { - use super::*; - - #[test] - #[should_panic(expected = "invalid type: integer `123`, expected a string")] - fn errors_for_non_version_string() { - let sandbox = create_empty_sandbox(); - sandbox.create_file(".prototools", "node = 123"); - - ToolsConfig::load_from(sandbox.path()).unwrap(); - } - - #[test] - #[should_panic(expected = "invalid type: map, expected a string")] - fn errors_for_non_plugins_table() { - let sandbox = create_empty_sandbox(); - sandbox.create_file(".prototools", "[other]\nkey = 123"); - - ToolsConfig::load_from(sandbox.path()).unwrap(); - } - - #[test] - #[should_panic( - expected = "Invalid plugin identifier fooBar, must be a valid kebab-case string." - )] - fn errors_for_non_kebab_id() { - let sandbox = create_empty_sandbox(); - sandbox.create_file(".prototools", "fooBar = \"1.2.3\""); - - ToolsConfig::load_from(sandbox.path()).unwrap(); - } - - #[test] - fn parses_plugins_table() { - let sandbox = create_empty_sandbox(); - sandbox.create_file( - ".prototools", - r#" -node = "12.0.0" -rust = "stable" - -[plugins] -foo = "source:./test.toml" -kebab-case = "source:./camel.toml" -"#, - ); - - let config = ToolsConfig::load_from(sandbox.path()).unwrap(); - - assert_eq!( - config.tools, - BTreeMap::from_iter([ - ( - Id::raw("node"), - UnresolvedVersionSpec::from_str("12.0.0").unwrap() - ), - ( - Id::raw("rust"), - UnresolvedVersionSpec::Alias("stable".into()) - ), - ]) - ); - - assert_eq!( - config.plugins, - BTreeMap::from_iter([ - ( - Id::raw("foo"), - PluginLocator::SourceFile { - file: "./test.toml".into(), - path: sandbox.path().join("./test.toml") - } - ), - ( - Id::raw("kebab-case"), - PluginLocator::SourceFile { - file: "./camel.toml".into(), - path: sandbox.path().join("./camel.toml") - } - ) - ]) - ); - } - - #[test] - fn formats_plugins_table() { - let sandbox = create_empty_sandbox(); - let mut config = ToolsConfig::load_from(sandbox.path()).unwrap(); - - config.tools.insert( - Id::raw("node"), - UnresolvedVersionSpec::from_str("12.0.0").unwrap(), - ); - config.tools.insert( - Id::raw("rust"), - UnresolvedVersionSpec::Alias("stable".into()), - ); - - config.plugins.insert( - Id::raw("foo"), - PluginLocator::SourceFile { - file: "./test.toml".into(), - path: sandbox.path().join("./test.toml"), - }, - ); - config.save().unwrap(); - - assert_eq!( - std::fs::read_to_string(config.path).unwrap(), - r#"node = "12.0.0" -rust = "stable" - -[plugins] -foo = "source:./test.toml" -"#, - ); - } - - #[test] - fn merges_traversing_upwards() { - let sandbox = create_empty_sandbox(); - - sandbox.create_file( - "one/two/three/.prototools", - r#" - node = "1.2.3" - - [plugins] - node = "source:./node.toml" - "#, - ); - - sandbox.create_file( - "one/two/.prototools", - r#" - [plugins] - bun = "source:../bun.wasm" - "#, - ); - - sandbox.create_file( - "one/.prototools", - r#" - bun = "4.5.6" - - [plugins] - node = "source:../node.toml" - "#, - ); - - sandbox.create_file( - ".prototools", - r#" - node = "7.8.9" - deno = "7.8.9" - "#, - ); - - let config = - ToolsConfig::load_upwards_from(sandbox.path().join("one/two/three"), false).unwrap(); - - assert_eq!( - config.tools, - BTreeMap::from_iter([ - ( - Id::raw("node"), - UnresolvedVersionSpec::parse("1.2.3").unwrap() - ), - ( - Id::raw("bun"), - UnresolvedVersionSpec::parse("4.5.6").unwrap() - ), - ( - Id::raw("deno"), - UnresolvedVersionSpec::parse("7.8.9").unwrap() - ), - ]) - ); - - assert_eq!( - config.plugins, - BTreeMap::from_iter([ - ( - Id::raw("node"), - PluginLocator::SourceFile { - file: "./node.toml".into(), - path: sandbox.path().join("one/two/three/./node.toml") - } - ), - ( - Id::raw("bun"), - PluginLocator::SourceFile { - file: "../bun.wasm".into(), - path: sandbox.path().join("one/two/../bun.wasm") - } - ) - ]) - ); - } -} diff --git a/crates/core/tests/user_config_test.rs b/crates/core/tests/user_config_test.rs deleted file mode 100644 index 49bea7aa5..000000000 --- a/crates/core/tests/user_config_test.rs +++ /dev/null @@ -1,149 +0,0 @@ -use proto_core::{DetectStrategy, PinType, UserConfig, USER_CONFIG_NAME}; -use starbase_sandbox::create_empty_sandbox; -use std::collections::BTreeMap; -use std::env; -use warpgate::{GitHubLocator, HttpOptions, Id, PluginLocator}; - -mod user_config { - use super::*; - - #[test] - fn loads_defaults_if_missing() { - let sandbox = create_empty_sandbox(); - let config = UserConfig::load_from(sandbox.path()).unwrap(); - - assert_eq!( - config, - UserConfig { - auto_clean: false, - auto_install: false, - detect_strategy: DetectStrategy::default(), - node_intercept_globals: true, - http: HttpOptions::default(), - pin_latest: None, - plugins: BTreeMap::default(), - path: sandbox.path().join(USER_CONFIG_NAME), - } - ); - } - - #[test] - fn can_set_values() { - let sandbox = create_empty_sandbox(); - sandbox.create_file( - "config.toml", - r#" -auto-clean = true -auto-install = true -node-intercept-globals = false -pin-latest = "global" -"#, - ); - - let config = UserConfig::load_from(sandbox.path()).unwrap(); - - assert_eq!( - config, - UserConfig { - auto_clean: true, - auto_install: true, - detect_strategy: DetectStrategy::default(), - node_intercept_globals: false, - http: HttpOptions::default(), - pin_latest: Some(PinType::Global), - plugins: BTreeMap::default(), - path: sandbox.path().join(USER_CONFIG_NAME), - } - ); - } - - #[test] - fn can_set_booleans_from_env_vars() { - let sandbox = create_empty_sandbox(); - sandbox.create_file("config.toml", ""); - - env::set_var("PROTO_AUTO_CLEAN", "1"); - env::set_var("PROTO_AUTO_INSTALL", "true"); - env::set_var("PROTO_NODE_INTERCEPT_GLOBALS", "off"); - - let config = UserConfig::load_from(sandbox.path()).unwrap(); - - assert_eq!( - config, - UserConfig { - auto_clean: true, - auto_install: true, - detect_strategy: DetectStrategy::default(), - node_intercept_globals: false, - http: HttpOptions::default(), - pin_latest: None, - plugins: BTreeMap::default(), - path: sandbox.path().join(USER_CONFIG_NAME), - } - ); - - env::remove_var("PROTO_AUTO_CLEAN"); - env::remove_var("PROTO_AUTO_INSTALL"); - env::remove_var("PROTO_NODE_INTERCEPT_GLOBALS"); - } - - #[test] - fn can_set_plugins() { - let sandbox = create_empty_sandbox(); - sandbox.create_file( - "config.toml", - r#" -[plugins] -foo = "github:moonrepo/foo" -bar = "source:https://moonrepo.dev/path/file.wasm" -"#, - ); - - let config = UserConfig::load_from(sandbox.path()).unwrap(); - - assert_eq!( - config.plugins, - BTreeMap::from_iter([ - ( - Id::raw("bar"), - PluginLocator::SourceUrl { - url: "https://moonrepo.dev/path/file.wasm".into() - } - ), - ( - Id::raw("foo"), - PluginLocator::GitHub(GitHubLocator { - file_prefix: "foo_plugin".into(), - repo_slug: "moonrepo/foo".into(), - tag: None, - }) - ), - ]) - ); - } - - #[test] - fn updates_plugin_files_to_absolute() { - let sandbox = create_empty_sandbox(); - sandbox.create_file( - "config.toml", - r#" -[plugins] -foo = "source:../file.wasm" -"#, - ); - - let config = UserConfig::load_from(sandbox.path()).unwrap(); - - assert_eq!( - config.plugins, - BTreeMap::from_iter([( - Id::raw("foo"), - PluginLocator::SourceFile { - file: "../file.wasm".into(), - path: sandbox.path().join("../file.wasm") - } - )]) - ); - } -} diff --git a/crates/core/tests/version_detector_test.rs b/crates/core/tests/version_detector_test.rs index 885a8304d..d997ccc80 100644 --- a/crates/core/tests/version_detector_test.rs +++ b/crates/core/tests/version_detector_test.rs @@ -1,6 +1,6 @@ use proto_core::{ detect_version_first_available, detect_version_prefer_prototools, load_tool_from_locator, - ProtoEnvironment, Tool, ToolsConfig, UnresolvedVersionSpec, UserConfig, + ProtoConfig, ProtoConfigManager, ProtoEnvironment, Tool, UnresolvedVersionSpec, }; use starbase_sandbox::create_empty_sandbox; use std::path::Path; @@ -13,8 +13,7 @@ mod version_detector { load_tool_from_locator( Id::raw("node"), ProtoEnvironment::new().unwrap(), - ToolsConfig::builtin_plugins().get("node").unwrap(), - &UserConfig::default(), + ProtoConfig::builtin_plugins().get("node").unwrap(), ) .await .unwrap() @@ -30,23 +29,32 @@ mod version_detector { let tool = create_node(sandbox.path()).await; assert_eq!( - detect_version_first_available(&tool, &sandbox.path().join("a/b/c"), sandbox.path()) - .await - .unwrap(), + detect_version_first_available( + &tool, + &ProtoConfigManager::load(sandbox.path().join("a/b/c"), None).unwrap() + ) + .await + .unwrap(), Some(UnresolvedVersionSpec::parse("~16").unwrap()) ); assert_eq!( - detect_version_first_available(&tool, &sandbox.path().join("a/b"), sandbox.path()) - .await - .unwrap(), + detect_version_first_available( + &tool, + &ProtoConfigManager::load(sandbox.path().join("a/b"), None).unwrap() + ) + .await + .unwrap(), Some(UnresolvedVersionSpec::parse("~18").unwrap()) ); assert_eq!( - detect_version_first_available(&tool, &sandbox.path().join("a"), sandbox.path()) - .await - .unwrap(), + detect_version_first_available( + &tool, + &ProtoConfigManager::load(sandbox.path().join("a"), None).unwrap() + ) + .await + .unwrap(), Some(UnresolvedVersionSpec::parse("~20").unwrap()) ); } @@ -58,9 +66,10 @@ mod version_detector { sandbox.create_file("package.json", r#"{ "engines": { "node": "18" } }"#); let tool = create_node(sandbox.path()).await; + let manager = ProtoConfigManager::load(sandbox.path().join("a/b"), None).unwrap(); assert_eq!( - detect_version_first_available(&tool, &sandbox.path().join("a/b"), sandbox.path()) + detect_version_first_available(&tool, &manager) .await .unwrap(), Some(UnresolvedVersionSpec::parse("~20").unwrap()) @@ -74,9 +83,10 @@ mod version_detector { sandbox.create_file("a/package.json", r#"{ "engines": { "node": "18" } }"#); let tool = create_node(sandbox.path()).await; + let manager = ProtoConfigManager::load(sandbox.path().join("a/b"), None).unwrap(); assert_eq!( - detect_version_first_available(&tool, &sandbox.path().join("a/b"), sandbox.path()) + detect_version_first_available(&tool, &manager) .await .unwrap(), Some(UnresolvedVersionSpec::parse("~18").unwrap()) @@ -92,9 +102,10 @@ mod version_detector { sandbox.create_file("a/b/c/package.json", r#"{ "engines": { "node": "19" } }"#); let tool = create_node(sandbox.path()).await; + let manager = ProtoConfigManager::load(sandbox.path().join("a/b/c"), None).unwrap(); assert_eq!( - detect_version_prefer_prototools(&tool, &sandbox.path().join("a/b/c"), sandbox.path()) + detect_version_prefer_prototools(&tool, &manager) .await .unwrap(), Some(UnresolvedVersionSpec::parse("~18").unwrap()) diff --git a/crates/core/tests/version_resolver_test.rs b/crates/core/tests/version_resolver_test.rs index b68742b98..3dce1ea8c 100644 --- a/crates/core/tests/version_resolver_test.rs +++ b/crates/core/tests/version_resolver_test.rs @@ -1,4 +1,6 @@ -use proto_core::{resolve_version, ToolManifest, UnresolvedVersionSpec, VersionSpec}; +use proto_core::{ + resolve_version, ProtoToolConfig, ToolManifest, UnresolvedVersionSpec, VersionSpec, +}; use semver::Version; use std::collections::BTreeMap; @@ -43,15 +45,6 @@ mod version_resolver { fn create_manifest() -> ToolManifest { let mut manifest = ToolManifest::default(); - manifest.aliases.insert( - "latest-manifest".into(), - UnresolvedVersionSpec::Version(Version::new(8, 0, 0)), - ); - manifest.aliases.insert( - "stable-manifest".into(), - UnresolvedVersionSpec::Alias("stable".into()), - ); - manifest .installed_versions .insert(VersionSpec::parse("3.0.0").unwrap()); @@ -62,6 +55,21 @@ mod version_resolver { manifest } + fn create_tool_config() -> ProtoToolConfig { + let mut config = ProtoToolConfig::default(); + + config.aliases.insert( + "latest-manifest".into(), + UnresolvedVersionSpec::Version(Version::new(8, 0, 0)), + ); + config.aliases.insert( + "stable-manifest".into(), + UnresolvedVersionSpec::Alias("stable".into()), + ); + + config + } + #[test] fn resolves_aliases() { let versions = create_versions(); @@ -73,6 +81,7 @@ mod version_resolver { &versions, &aliases, None, + None, ) .unwrap(), Version::new(10, 0, 0) @@ -84,6 +93,7 @@ mod version_resolver { &versions, &aliases, None, + None, ) .unwrap(), Version::new(10, 0, 0) @@ -91,10 +101,11 @@ mod version_resolver { } #[test] - fn resolves_aliases_from_manifest() { + fn resolves_aliases_from_config() { let versions = create_versions(); let aliases = create_aliases(); let manifest = create_manifest(); + let config = create_tool_config(); assert_eq!( resolve_version( @@ -102,6 +113,7 @@ mod version_resolver { &versions, &aliases, Some(&manifest), + Some(&config), ) .unwrap(), Version::new(8, 0, 0) @@ -113,6 +125,7 @@ mod version_resolver { &versions, &aliases, Some(&manifest), + Some(&config), ) .unwrap(), Version::new(10, 0, 0) @@ -130,6 +143,7 @@ mod version_resolver { &versions, &aliases, None, + None, ) .unwrap(); } @@ -145,6 +159,7 @@ mod version_resolver { &versions, &aliases, None, + None, ) .unwrap(); } @@ -160,6 +175,7 @@ mod version_resolver { &versions, &aliases, None, + None, ) .unwrap(); } @@ -175,6 +191,7 @@ mod version_resolver { &versions, &aliases, None, + None, ) .unwrap(), Version::new(1, 10, 5) @@ -186,6 +203,7 @@ mod version_resolver { &versions, &aliases, None, + None, ) .unwrap(), Version::new(8, 0, 0) @@ -193,10 +211,11 @@ mod version_resolver { } #[test] - fn resolves_versions_from_manifest() { + fn resolves_versions_from_config() { let versions = create_versions(); let aliases = create_aliases(); let manifest = create_manifest(); + let config = create_tool_config(); assert_eq!( resolve_version( @@ -204,6 +223,7 @@ mod version_resolver { &versions, &aliases, Some(&manifest), + Some(&config), ) .unwrap(), Version::new(3, 0, 0) @@ -221,6 +241,7 @@ mod version_resolver { &versions, &aliases, None, + None, ) .unwrap(), Version::new(1, 2, 3) @@ -232,6 +253,7 @@ mod version_resolver { &versions, &aliases, None, + None, ) .unwrap(), Version::new(1, 0, 0) @@ -243,6 +265,7 @@ mod version_resolver { &versions, &aliases, None, + None, ) .unwrap(), Version::new(1, 10, 5) @@ -250,10 +273,11 @@ mod version_resolver { } #[test] - fn resolves_partial_versions_with_manifest() { + fn resolves_partial_versions_with_config() { let versions = create_versions(); let aliases = create_aliases(); let manifest = create_manifest(); + let config = create_tool_config(); assert_eq!( resolve_version( @@ -261,6 +285,7 @@ mod version_resolver { &versions, &aliases, Some(&manifest), + Some(&config), ) .unwrap(), Version::new(3, 3, 3) @@ -272,6 +297,7 @@ mod version_resolver { &versions, &aliases, Some(&manifest), + Some(&config), ) .unwrap(), Version::new(3, 3, 3) @@ -289,6 +315,7 @@ mod version_resolver { &versions, &aliases, None, + None, ) .unwrap(), Version::new(8, 0, 0) @@ -300,6 +327,7 @@ mod version_resolver { &versions, &aliases, None, + None, ) .unwrap(), Version::new(8, 0, 0) @@ -317,6 +345,7 @@ mod version_resolver { &versions, &aliases, None, + None, ) .unwrap(); } @@ -332,6 +361,7 @@ mod version_resolver { &versions, &aliases, None, + None, ) .unwrap(), Version::new(8, 0, 0) @@ -343,6 +373,7 @@ mod version_resolver { &versions, &aliases, None, + None, ) .unwrap(), Version::new(1, 1, 1) @@ -354,6 +385,7 @@ mod version_resolver { &versions, &aliases, None, + None, ) .unwrap(), Version::new(8, 0, 0) @@ -365,6 +397,7 @@ mod version_resolver { &versions, &aliases, None, + None, ) .unwrap(), Version::new(8, 0, 0) @@ -377,6 +410,7 @@ mod version_resolver { &versions, &aliases, None, + None, ) .unwrap(), Version::new(1, 10, 5) @@ -389,6 +423,7 @@ mod version_resolver { &versions, &aliases, None, + None, ) .unwrap(), Version::new(10, 0, 0) @@ -406,6 +441,7 @@ mod version_resolver { &versions, &aliases, None, + None, ) .unwrap(); } @@ -421,6 +457,7 @@ mod version_resolver { &versions, &aliases, None, + None, ) .unwrap(), Version::new(8, 0, 0) @@ -438,6 +475,7 @@ mod version_resolver { &versions, &aliases, None, + None, ) .unwrap(); } @@ -453,6 +491,7 @@ mod version_resolver { &versions, &aliases, None, + None, ) .unwrap(); } diff --git a/crates/pdk-api/Cargo.toml b/crates/pdk-api/Cargo.toml index e99bde855..acd877e5d 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.4" +version = "0.11.1" 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.6", path = "../system-env" } -version_spec = { version = "0.1.5", path = "../version-spec" } -warpgate_api = { version = "0.1.5", path = "../warpgate-api" } +system_env = { version = "0.1.7", path = "../system-env" } +version_spec = { version = "0.1.6", path = "../version-spec" } +warpgate_api = { version = "0.1.6", 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 ebe6afa1d..4475ff124 100644 --- a/crates/pdk-api/src/api.rs +++ b/crates/pdk-api/src/api.rs @@ -474,7 +474,7 @@ impl InstallGlobalOutput { Self { installed: false, - error: Some(result.stderr), + error: Some(result.get_output()), } } } @@ -516,7 +516,7 @@ impl UninstallGlobalOutput { Self { uninstalled: false, - error: Some(result.stderr), + error: Some(result.get_output()), } } } @@ -621,10 +621,6 @@ json_struct!( json_struct!( /// Output returned by the `sync_manifest` function. pub struct SyncManifestOutput { - /// Override the default version with a new alias or version. - #[serde(skip_serializing_if = "Option::is_none")] - pub default_version: Option, - /// List of versions that are currently installed. Will replace /// what is currently in the manifest. #[serde(skip_serializing_if = "Option::is_none")] diff --git a/crates/pdk-api/src/host.rs b/crates/pdk-api/src/host.rs index 67affc139..2583141e8 100644 --- a/crates/pdk-api/src/host.rs +++ b/crates/pdk-api/src/host.rs @@ -13,12 +13,3 @@ json_struct!( pub proto_dir: VirtualPath, } ); - -json_struct!( - /// The current user's proto configuration. - pub struct UserConfigSettings { - pub auto_clean: bool, - pub auto_install: bool, - pub node_intercept_globals: bool, - } -); diff --git a/crates/pdk-api/src/host_funcs.rs b/crates/pdk-api/src/host_funcs.rs index 63aadd94d..0b4b5f9c8 100644 --- a/crates/pdk-api/src/host_funcs.rs +++ b/crates/pdk-api/src/host_funcs.rs @@ -100,3 +100,20 @@ json_struct!( pub stdout: String, } ); + +impl ExecCommandOutput { + pub fn get_output(&self) -> String { + let mut out = String::new(); + out.push_str(self.stdout.trim()); + + if !self.stderr.is_empty() { + if !out.is_empty() { + out.push(' '); + } + + out.push_str(self.stderr.trim()); + } + + out + } +} diff --git a/crates/pdk-test-utils/Cargo.toml b/crates/pdk-test-utils/Cargo.toml index 223f43587..6c1c2506d 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.11.1" +version = "0.12.0" 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.23.7", path = "../core" } -proto_pdk_api = { version = "0.10.4", path = "../pdk-api" } +proto_core = { version = "0.24.0", path = "../core" } +proto_pdk_api = { version = "0.11.1", path = "../pdk-api" } extism = { workspace = true } serde_json = { workspace = true } toml = { version = "0.8.8", optional = true } diff --git a/crates/pdk-test-utils/src/lib.rs b/crates/pdk-test-utils/src/lib.rs index 408976132..067b14a0b 100644 --- a/crates/pdk-test-utils/src/lib.rs +++ b/crates/pdk-test-utils/src/lib.rs @@ -4,8 +4,8 @@ mod wrapper; pub use macros::*; pub use proto_core as core; pub use proto_core::{ - Id, ProtoEnvironment, Tool, ToolManifest, ToolsConfig, UnresolvedVersionSpec, UserConfig, - Version, VersionReq, VersionSpec, Wasm, + Id, ProtoEnvironment, Tool, ToolManifest, UnresolvedVersionSpec, Version, VersionReq, + VersionSpec, Wasm, }; pub use proto_pdk_api::*; pub use wrapper::WasmTestWrapper; @@ -119,12 +119,11 @@ fn internal_create_plugin( ) -> WasmTestWrapper { let id = Id::new(id).unwrap(); let proto = ProtoEnvironment::new_testing(sandbox); - let user_config = UserConfig::default(); let mut manifest = Tool::create_plugin_manifest(&proto, Wasm::file(find_wasm_file(sandbox))).unwrap(); - inject_default_manifest_config(&id, &proto, &user_config, &mut manifest).unwrap(); + inject_default_manifest_config(&id, &proto, &mut manifest).unwrap(); manifest.config.extend(config); diff --git a/crates/pdk/Cargo.toml b/crates/pdk/Cargo.toml index 1332335ea..6a1fa485c 100644 --- a/crates/pdk/Cargo.toml +++ b/crates/pdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proto_pdk" -version = "0.10.3" +version = "0.11.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.10.4", path = "../pdk-api" } +proto_pdk_api = { version = "0.11.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 aa8f1bc29..3ec67b5a9 100644 --- a/crates/pdk/src/helpers.rs +++ b/crates/pdk/src/helpers.rs @@ -3,7 +3,6 @@ use extism_pdk::http::request; use extism_pdk::*; use proto_pdk_api::{ ExecCommandInput, ExecCommandOutput, HostArch, HostEnvironment, HostOS, PluginError, - UserConfigSettings, }; use serde::de::DeserializeOwned; use std::collections::HashMap; @@ -236,11 +235,22 @@ pub fn get_target_triple(env: &HostEnvironment, name: &str) -> Result String { config::get("proto_tool_id").expect("Missing tool ID!") } +/// Get tool configuration for the current WASM plugin that was configured in a `.prototools` file. +pub fn get_tool_config() -> anyhow::Result { + let config: T = if let Some(value) = config::get("proto_tool_config") { + json::from_str(&value)? + } else { + T::default() + }; + + Ok(config) +} + /// Return information about proto and the host environment. pub fn get_proto_environment() -> anyhow::Result { let config = config::get("proto_environment").expect("Missing proto environment!"); @@ -249,10 +259,13 @@ pub fn get_proto_environment() -> anyhow::Result { Ok(config) } -/// Return the loaded proto user configuration (`~/.proto/config.toml`). Does not include plugins! -pub fn get_proto_user_config() -> anyhow::Result { - let config = config::get("proto_user_config").expect("Missing proto user configuration!"); - let config: UserConfigSettings = json::from_str(&config)?; +/// Return the loaded proto user configuration (`~/.proto/.prototools`). Does not include plugins! +#[allow(deprecated)] +#[deprecated] +pub fn get_proto_user_config() -> anyhow::Result { + if let Some(config) = config::get("proto_user_config") { + return Ok(json::from_str(&config)?); + } - Ok(config) + Ok(json::Value::default()) } diff --git a/crates/system-env/Cargo.toml b/crates/system-env/Cargo.toml index 37f7626e7..86897b1ba 100644 --- a/crates/system-env/Cargo.toml +++ b/crates/system-env/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "system_env" -version = "0.1.6" +version = "0.1.7" edition = "2021" license = "MIT" description = "Information about the system environment: operating system, architecture, package manager, etc." diff --git a/crates/system-env/src/helpers.rs b/crates/system-env/src/helpers.rs index 6123113b9..bdf2e4030 100644 --- a/crates/system-env/src/helpers.rs +++ b/crates/system-env/src/helpers.rs @@ -72,7 +72,7 @@ pub fn is_command_on_path>(name: T) -> bool { /// Create a new process [`Command`] and append the provided arguments. If the provided binary /// name is not an absolute path, we'll attempt to find it on `PATH` using [`find_command_on_path`]. /// -/// Furthermore, if the binary path is a Windows script (`.ps1`, `.cmd`, `.bat``), we'll wrap +/// Furthermore, if the binary path is a Windows script (`.ps1`, `.cmd`, `.bat`), we'll wrap /// the binary in a PowerShell command, and pass the original command via `-Command`. pub fn create_process_command, I: IntoIterator, A: AsRef>( bin: T, diff --git a/crates/version-spec/Cargo.toml b/crates/version-spec/Cargo.toml index 25ebef0aa..832d6ec88 100644 --- a/crates/version-spec/Cargo.toml +++ b/crates/version-spec/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "version_spec" -version = "0.1.5" +version = "0.1.6" edition = "2021" license = "MIT" description = "A specification for working with partial, full, or aliased versions." @@ -10,5 +10,10 @@ repository = "https://github.com/moonrepo/proto" [dependencies] human-sort = { workspace = true } regex = { workspace = true } +schematic = { workspace = true, optional = true, features = ["schema"] } semver = { workspace = true } serde = { workspace = true } + +[features] +default = [] +schematic = ["dep:schematic"] diff --git a/crates/version-spec/src/resolved_spec.rs b/crates/version-spec/src/resolved_spec.rs index 56bf3a580..07279ba63 100644 --- a/crates/version-spec/src/resolved_spec.rs +++ b/crates/version-spec/src/resolved_spec.rs @@ -64,6 +64,13 @@ impl VersionSpec { } } +#[cfg(feature = "schematic")] +impl schematic::Schematic for VersionSpec { + fn generate_schema() -> schematic::SchemaType { + schematic::SchemaType::string() + } +} + impl Default for VersionSpec { /// Returns a `latest` alias. fn default() -> Self { diff --git a/crates/version-spec/src/unresolved_spec.rs b/crates/version-spec/src/unresolved_spec.rs index 237f85da6..bf10206e7 100644 --- a/crates/version-spec/src/unresolved_spec.rs +++ b/crates/version-spec/src/unresolved_spec.rs @@ -81,6 +81,13 @@ impl UnresolvedVersionSpec { } } +#[cfg(feature = "schematic")] +impl schematic::Schematic for UnresolvedVersionSpec { + fn generate_schema() -> schematic::SchemaType { + schematic::SchemaType::string() + } +} + impl Default for UnresolvedVersionSpec { /// Returns a `latest` alias. fn default() -> Self { diff --git a/crates/warpgate-api/Cargo.toml b/crates/warpgate-api/Cargo.toml index 48f0b7cfb..aa9148e46 100644 --- a/crates/warpgate-api/Cargo.toml +++ b/crates/warpgate-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "warpgate_api" -version = "0.1.5" +version = "0.1.6" edition = "2021" license = "MIT" description = "APIs for working with Warpgate plugins." @@ -8,4 +8,9 @@ homepage = "https://moonrepo.dev/proto" repository = "https://github.com/moonrepo/proto" [dependencies] +schematic = { workspace = true, optional = true, features = ["schema"] } serde = { workspace = true } + +[features] +default = [] +schematic = ["dep:schematic"] diff --git a/crates/warpgate-api/src/virtual_path.rs b/crates/warpgate-api/src/virtual_path.rs index f66335e4e..ef48250f3 100644 --- a/crates/warpgate-api/src/virtual_path.rs +++ b/crates/warpgate-api/src/virtual_path.rs @@ -40,6 +40,16 @@ impl VirtualPath { } } +#[cfg(feature = "schematic")] +impl schematic::Schematic for VirtualPath { + fn generate_schema() -> schematic::SchemaType { + schematic::SchemaType::String(schematic::schema::StringType { + format: Some("path".into()), + ..Default::default() + }) + } +} + impl Default for VirtualPath { fn default() -> Self { Self::Only(PathBuf::new()) diff --git a/crates/warpgate/Cargo.toml b/crates/warpgate/Cargo.toml index 05d413359..14f84c9cf 100644 --- a/crates/warpgate/Cargo.toml +++ b/crates/warpgate/Cargo.toml @@ -1,19 +1,20 @@ [package] name = "warpgate" -version = "0.5.15" +version = "0.6.0" 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.5", path = "../warpgate-api" } +warpgate_api = { version = "0.1.6", path = "../warpgate-api" } extism = { workspace = true } miette = { workspace = true } once_cell = { workspace = true } once_map = { workspace = true } regex = { workspace = true } reqwest = { workspace = true, features = ["json", "rustls-tls-native-roots"] } +schematic = { workspace = true, optional = true, features = ["schema"] } serde = { workspace = true } serde_json = { workspace = true } sha2 = { workspace = true } @@ -26,3 +27,7 @@ tracing = { workspace = true } [dev-dependencies] starbase_sandbox = { workspace = true } tokio = { workspace = true } + +[features] +default = [] +schematic = ["dep:schematic", "warpgate_api/schematic"] diff --git a/crates/warpgate/src/client.rs b/crates/warpgate/src/client.rs index 82d7a52ec..ac819c710 100644 --- a/crates/warpgate/src/client.rs +++ b/crates/warpgate/src/client.rs @@ -6,6 +6,7 @@ use tracing::{trace, warn}; #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(default, rename_all = "kebab-case")] +#[cfg_attr(feature = "schematic", derive(schematic::Schematic))] pub struct HttpOptions { pub allow_invalid_certs: bool, pub proxies: Vec, @@ -14,12 +15,12 @@ pub struct HttpOptions { /// Create an HTTP/HTTPS client that'll be used for downloading files. pub fn create_http_client() -> miette::Result { - create_http_client_with_options(HttpOptions::default()) + create_http_client_with_options(&HttpOptions::default()) } /// Create an HTTP/HTTPS client with the provided options, that'll be /// used for downloading files. -pub fn create_http_client_with_options(options: HttpOptions) -> miette::Result { +pub fn create_http_client_with_options(options: &HttpOptions) -> miette::Result { let mut client = reqwest::Client::builder() .user_agent(format!("warpgate@{}", env!("CARGO_PKG_VERSION"))) .use_rustls_tls(); @@ -30,19 +31,19 @@ pub fn create_http_client_with_options(options: HttpOptions) -> miette::Result { client = client.add_root_certificate( - reqwest::Certificate::from_der(&fs::read_file_bytes(&root_cert)?) + reqwest::Certificate::from_der(&fs::read_file_bytes(root_cert)?) .into_diagnostic()?, ) } Some("pem") => { client = client.add_root_certificate( - reqwest::Certificate::from_pem(&fs::read_file_bytes(&root_cert)?) + reqwest::Certificate::from_pem(&fs::read_file_bytes(root_cert)?) .into_diagnostic()?, ) } @@ -55,7 +56,7 @@ pub fn create_http_client_with_options(options: HttpOptions) -> miette::Result schematic::SchemaType { + schematic::SchemaType::string() + } +} + impl Display for Id { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) diff --git a/crates/warpgate/src/loader.rs b/crates/warpgate/src/loader.rs index 0204e4c09..d1b70e596 100644 --- a/crates/warpgate/src/loader.rs +++ b/crates/warpgate/src/loader.rs @@ -140,25 +140,26 @@ impl PluginLoader { return Ok(false); } - let mut cached = true; + let metadata = fs::metadata(path)?; - // If latest, cache only lasts for 7 days - if fs::file_name(path).contains("-latest-") { - let metadata = fs::metadata(path)?; - - cached = if let Ok(filetime) = metadata.created().or_else(|_| metadata.modified()) { - filetime > SystemTime::now() - Duration::from_secs(86400 * 7) + let mut cached = if let Ok(filetime) = metadata.created().or_else(|_| metadata.modified()) { + let days = if fs::file_name(path).contains("-latest-") { + 7 } else { - false + 30 }; - if !cached && self.is_offline() { - cached = true; - } + filetime > SystemTime::now() - Duration::from_secs(86400 * days) + } else { + false + }; - if !cached { - fs::remove_file(path)?; - } + if !cached && self.is_offline() { + cached = true; + } + + if !cached { + fs::remove_file(path)?; } if cached { diff --git a/crates/warpgate/src/locator.rs b/crates/warpgate/src/locator.rs index 252001d72..32247ef56 100644 --- a/crates/warpgate/src/locator.rs +++ b/crates/warpgate/src/locator.rs @@ -52,6 +52,13 @@ pub enum PluginLocator { Wapm(WapmLocator), } +#[cfg(feature = "schematic")] +impl schematic::Schematic for PluginLocator { + fn generate_schema() -> schematic::SchemaType { + schematic::SchemaType::string() + } +} + impl Display for PluginLocator { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/crates/warpgate/src/plugin.rs b/crates/warpgate/src/plugin.rs index 30e1aaf07..42319aee2 100644 --- a/crates/warpgate/src/plugin.rs +++ b/crates/warpgate/src/plugin.rs @@ -189,7 +189,7 @@ impl<'plugin> PluginContainer<'plugin> { { WarpgateError::PluginCallFailed { func: func.to_owned(), - error, + error: error.to_string(), } } // When in release mode, errors don't render properly with the diff --git a/plugins/Cargo.lock b/plugins/Cargo.lock index 5adc948f8..0ede9f0ce 100644 --- a/plugins/Cargo.lock +++ b/plugins/Cargo.lock @@ -136,7 +136,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -332,6 +332,15 @@ dependencies = [ "winx 0.35.1", ] +[[package]] +name = "castaway" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" +dependencies = [ + "rustversion", +] + [[package]] name = "cbindgen" version = "0.24.5" @@ -381,6 +390,28 @@ dependencies = [ "windows-targets 0.48.1", ] +[[package]] +name = "chrono-tz" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e23185c0e21df6ed832a12e2bda87c7d1def6842881fb634a8511ced741b0d76" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433e39f13c9a060046954e0592a8d0a4bcb1040125cbf91cb8ee58964cfb350f" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + [[package]] name = "clap" version = "3.2.25" @@ -411,6 +442,19 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aaa6b4b263a5d737e9bf6b7c09b72c41a5480aec4d7219af827f6564e950b6a5" +[[package]] +name = "compact_str" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "ryu", + "static_assertions", +] + [[package]] name = "console" version = "0.15.7" @@ -443,6 +487,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -692,7 +745,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -714,7 +767,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core 0.20.3", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -743,6 +796,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7" +[[package]] +name = "deunicode" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1abaf4d861455be59f64fd2b55606cb151fce304ede7165f410243ce96bde6" + [[package]] name = "diff" version = "0.1.13" @@ -896,9 +955,9 @@ dependencies = [ [[package]] name = "extism" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67e81d3e7d1592412579984e773bd553703a664bbfb1ddded742bf6dfae63847" +checksum = "1b7809353b2a431222219d4699947200c49a043d978fb28b4e199ced449117ef" dependencies = [ "anyhow", "extism-manifest", @@ -941,14 +1000,14 @@ checksum = "d2be216330f7304de051e0faf1578880e9e0dc1ecbd2c0fea5765c63a079d0ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] name = "extism-runtime" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126b884cfa74fc386eedff1095bac8ef6c2ae5efdb656e508f12a41434d0bf0c" +checksum = "6bf6aade8203c23185bcf12871168fc5959b9d62e7a59a4287fe2d5be69a68d5" dependencies = [ "anyhow", "cbindgen", @@ -1040,9 +1099,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -1091,7 +1150,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1142,6 +1201,31 @@ dependencies = [ "serde_json", ] +[[package]] +name = "garde" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f0bde634ca1248cfcd67a29f7b3798997d328406020f5e19b502b0a0d5e8cae" +dependencies = [ + "compact_str", + "garde_derive", + "once_cell", + "regex", + "smallvec", +] + +[[package]] +name = "garde_derive" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feafd106c73dd61d6f10f2fa782cdb6212b3422aee5b953a094818e9575078aa" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.39", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1315,6 +1399,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "140a09c9305e6d5e557e2ed7cbc68e05765a7d4213975b87cb04920689cc6219" +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + [[package]] name = "humantime" version = "2.1.0" @@ -1396,9 +1489,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -1600,6 +1693,12 @@ version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -1748,7 +1847,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1853,9 +1952,9 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "once_map" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64e67adc739ad601550e953442818011f3c21bc7bd2c77236dc167bfd4d4c42" +checksum = "eba3de7c2ff90d756ee0713daaae3a3547fa2424b3b9cca46bce0b4dcd1d7ef2" dependencies = [ "ahash", "hashbrown 0.14.2", @@ -1919,6 +2018,15 @@ dependencies = [ "windows-targets 0.48.1", ] +[[package]] +name = "parse-zoneinfo" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" +dependencies = [ + "regex", +] + [[package]] name = "paste" version = "1.0.13" @@ -1927,9 +2035,92 @@ checksum = "b4b27ab7be369122c218afc2079489cdcb4b517c0a3fc386ff11e1fedfcc2b35" [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "pest_meta" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] [[package]] name = "pin-project-lite" @@ -2007,16 +2198,16 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] [[package]] name = "proto_core" -version = "0.23.4" +version = "0.23.7" dependencies = [ "cached", "extism", @@ -2027,6 +2218,7 @@ dependencies = [ "proto_pdk_api", "regex", "reqwest", + "schematic", "semver", "serde", "serde_json", @@ -2036,8 +2228,8 @@ dependencies = [ "starbase_styles", "starbase_utils", "system_env", + "tera", "thiserror", - "tinytemplate", "tracing", "url", "version_spec", @@ -2425,6 +2617,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "ryu" version = "1.0.14" @@ -2449,6 +2647,43 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "schematic" +version = "0.12.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710a6af816c586a37dc9b8b304370f36d8b90751785bbb245942bed0ae00a69d" +dependencies = [ + "garde", + "miette", + "schematic_macros", + "schematic_types", + "serde", + "serde_path_to_error", + "starbase_styles", + "thiserror", + "toml 0.8.8", + "tracing", +] + +[[package]] +name = "schematic_macros" +version = "0.12.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10e8b482f96eaca0fdc4fe3441c2eda44f5aeeb1a93b653d214ee2ecf4975be4" +dependencies = [ + "convert_case", + "darling 0.20.3", + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "schematic_types" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c38cdd0968bde35f7e093600c90e0306da6af5ffd1b3cb175dbfde1146debcf2" + [[package]] name = "scopeguard" version = "1.1.0" @@ -2499,9 +2734,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.192" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] @@ -2518,13 +2753,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -2538,6 +2773,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_spanned" version = "0.6.4" @@ -2612,6 +2857,12 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -2627,6 +2878,16 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" +[[package]] +name = "slug" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bd94acec9c8da640005f8e135a39fc0372e74535e6b368b7a04b875f784c8c4" +dependencies = [ + "deunicode", + "wasm-bindgen", +] + [[package]] name = "smallvec" version = "1.11.0" @@ -2711,7 +2972,7 @@ dependencies = [ "darling 0.20.3", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -2764,6 +3025,12 @@ dependencies = [ "wax", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" @@ -2793,9 +3060,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.38" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -2841,7 +3108,7 @@ dependencies = [ [[package]] name = "system_env" -version = "0.1.5" +version = "0.1.6" dependencies = [ "serde", "serde_json", @@ -2889,6 +3156,28 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "tera" +version = "1.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "970dff17c11e884a4a09bc76e3a17ef71e01bb13447a11e85226e254fe6d10b8" +dependencies = [ + "chrono", + "chrono-tz", + "globwalk", + "humansize", + "lazy_static", + "percent-encoding", + "pest", + "pest_derive", + "rand", + "regex", + "serde", + "serde_json", + "slug", + "unic-segment", +] + [[package]] name = "termcolor" version = "1.2.0" @@ -2927,7 +3216,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -2951,16 +3240,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "tinyvec" version = "1.6.0" @@ -3003,7 +3282,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3124,7 +3403,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3157,6 +3436,62 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" +dependencies = [ + "unic-ucd-segment", +] + +[[package]] +name = "unic-ucd-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + [[package]] name = "unicase" version = "2.7.0" @@ -3187,6 +3522,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "unicode-width" version = "0.1.10" @@ -3232,9 +3573,9 @@ dependencies = [ [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -3302,7 +3643,7 @@ dependencies = [ [[package]] name = "warpgate" -version = "0.5.14" +version = "0.5.15" dependencies = [ "extism", "miette", @@ -3399,7 +3740,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", "wasm-bindgen-shared", ] @@ -3433,7 +3774,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/plugins/wasm-test/src/lib.rs b/plugins/wasm-test/src/lib.rs index 1ff810175..b631a66f9 100644 --- a/plugins/wasm-test/src/lib.rs +++ b/plugins/wasm-test/src/lib.rs @@ -14,6 +14,17 @@ extern "ExtismHost" { // fn to_virtual_path(path: &str) -> String; } +#[derive(Debug, Default, Deserialize)] +#[serde(default, deny_unknown_fields, rename_all = "kebab-case")] +struct WasmTestConfig { + number: usize, + string: String, + boolean: bool, + unknown: Option, + list: Vec, + map: HashMap, +} + #[plugin_fn] pub fn register_tool(_: ()) -> FnResult> { host_log!(stdout, "Registering tool"); @@ -26,6 +37,10 @@ pub fn register_tool(_: ()) -> FnResult> { // let real = real_path!(PathBuf::from("/proto")); // let _virtual = virtual_path!(&real); + let config = get_tool_config::()?; + + host_log!("Config = {:?}", config); + Ok(Json(ToolMetadataOutput { name: "WASM Test".into(), type_of: PluginType::CLI,