From 9789648c6d9c97d18403091f5888b05448f4a097 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Thu, 18 Jul 2024 12:09:53 -0700 Subject: [PATCH] new: Add `builtin-plugins` setting. (#553) --- CHANGELOG.md | 3 + crates/cli/tests/plugins_test.rs | 21 +++++ crates/core/src/proto_config.rs | 48 ++++++++--- crates/core/src/tool_loader.rs | 4 +- crates/core/tests/proto_config_test.rs | 98 ++++++++++++++++++++++ crates/core/tests/version_detector_test.rs | 5 +- 6 files changed, 165 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90f11ef39..e2d6b373a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ #### 🚀 Updates +- Added a new setting to `.prototools`, `settings.builtin-plugins`, that can be used to disable all built-in plugins, or only allow a few select plugins. + - Supports a boolean or list of plugin names. + - All are enabled by default for backwards compatibility. - Merged `proto use` and `proto install` commands. If no arguments are provided to `proto install`, it will install all configured tools. ## 0.38.4 diff --git a/crates/cli/tests/plugins_test.rs b/crates/cli/tests/plugins_test.rs index 1c4d276b3..3fd5ec318 100644 --- a/crates/cli/tests/plugins_test.rs +++ b/crates/cli/tests/plugins_test.rs @@ -4,6 +4,7 @@ use proto_core::{ load_tool_from_locator, Id, PluginLocator, ProtoEnvironment, Tool, UnresolvedVersionSpec, }; use starbase_sandbox::assert_snapshot; +use starbase_sandbox::predicates::prelude::*; use std::env; use std::fs; use std::future::Future; @@ -331,5 +332,25 @@ mod plugins { // Doesn't create shims } + + #[test] + fn errors_if_disabled() { + let sandbox = create_empty_proto_sandbox(); + sandbox.create_file( + ".prototools", + r#" +[settings] +builtin-plugins = false +"#, + ); + + let assert = sandbox + .run_bin(|cmd| { + cmd.arg("install").arg("go"); + }) + .failure(); + + assert.stderr(predicate::str::contains("Unable to proceed, go")); + } } } diff --git a/crates/core/src/proto_config.rs b/crates/core/src/proto_config.rs index 6d23af587..27437c5bb 100644 --- a/crates/core/src/proto_config.rs +++ b/crates/core/src/proto_config.rs @@ -3,10 +3,11 @@ use indexmap::IndexMap; use once_cell::sync::OnceCell; use rustc_hash::FxHashMap; use schematic::{ - derive_enum, env, merge, Config, ConfigEnum, ConfigError, ConfigLoader, Format, HandlerError, - MergeResult, PartialConfig, ValidateError, ValidateErrorType, ValidatorError, + derive_enum, env, merge, Config, ConfigEnum, ConfigError, ConfigLoader, DefaultValueResult, + Format, HandlerError, MergeResult, PartialConfig, ValidateError, ValidateErrorType, + ValidatorError, }; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use starbase_styles::color; use starbase_utils::json::JsonValue; use starbase_utils::toml::TomlValue; @@ -102,6 +103,17 @@ impl EnvVar { } } +#[derive(Clone, Config, Debug, Deserialize, PartialEq, Serialize)] +#[serde(untagged)] +pub enum BuiltinPlugins { + Enabled(bool), + Allowed(Vec), +} + +fn default_builtin_plugins(_context: &()) -> DefaultValueResult { + Ok(Some(BuiltinPlugins::Enabled(true))) +} + #[derive(Clone, Config, Debug, Serialize)] #[config(allow_unknown_fields)] #[serde(rename_all = "kebab-case")] @@ -129,6 +141,9 @@ pub struct ProtoSettingsConfig { #[setting(env = "PROTO_AUTO_INSTALL", parse_env = env::parse_bool)] pub auto_install: bool, + #[setting(default = default_builtin_plugins)] + pub builtin_plugins: BuiltinPlugins, + #[setting(env = "PROTO_DETECT_STRATEGY")] pub detect_strategy: DetectStrategy, @@ -170,14 +185,25 @@ pub struct ProtoConfig { } impl ProtoConfig { - pub fn builtin_plugins() -> BTreeMap { + pub fn builtin_plugins(&self) -> BTreeMap { let mut config = ProtoConfig::default(); + + // Inherit this setting in case builtins have been disabled + config.settings.builtin_plugins = self.settings.builtin_plugins.clone(); + + // Then inherit all the available builtins config.inherit_builtin_plugins(); + config.plugins } pub fn inherit_builtin_plugins(&mut self) { - if !self.plugins.contains_key("bun") { + let is_allowed = |id: &str| match &self.settings.builtin_plugins { + BuiltinPlugins::Enabled(state) => *state, + BuiltinPlugins::Allowed(list) => list.iter().any(|aid| aid == id), + }; + + if !self.plugins.contains_key("bun") && is_allowed("bun") { self.plugins.insert( Id::raw("bun"), PluginLocator::Url { @@ -186,7 +212,7 @@ impl ProtoConfig { ); } - if !self.plugins.contains_key("deno") { + if !self.plugins.contains_key("deno") && is_allowed("deno") { self.plugins.insert( Id::raw("deno"), PluginLocator::Url { @@ -195,7 +221,7 @@ impl ProtoConfig { ); } - if !self.plugins.contains_key("go") { + if !self.plugins.contains_key("go") && is_allowed("go") { self.plugins.insert( Id::raw("go"), PluginLocator::Url { @@ -204,7 +230,7 @@ impl ProtoConfig { ); } - if !self.plugins.contains_key("node") { + if !self.plugins.contains_key("node") && is_allowed("node") { self.plugins.insert( Id::raw("node"), PluginLocator::Url { @@ -214,7 +240,7 @@ impl ProtoConfig { } for depman in ["npm", "pnpm", "yarn"] { - if !self.plugins.contains_key(depman) { + if !self.plugins.contains_key(depman) && is_allowed(depman) { self.plugins.insert( Id::raw(depman), PluginLocator::Url { @@ -224,7 +250,7 @@ impl ProtoConfig { } } - if !self.plugins.contains_key("python") { + if !self.plugins.contains_key("python") && is_allowed("python") { self.plugins.insert( Id::raw("python"), PluginLocator::Url { @@ -233,7 +259,7 @@ impl ProtoConfig { ); } - if !self.plugins.contains_key("rust") { + if !self.plugins.contains_key("rust") && is_allowed("rust") { self.plugins.insert( Id::raw("rust"), PluginLocator::Url { diff --git a/crates/core/src/tool_loader.rs b/crates/core/src/tool_loader.rs index 6a0fb797a..ba35d4a6d 100644 --- a/crates/core/src/tool_loader.rs +++ b/crates/core/src/tool_loader.rs @@ -1,6 +1,6 @@ use crate::error::ProtoError; use crate::proto::ProtoEnvironment; -use crate::proto_config::{ProtoConfig, SCHEMA_PLUGIN_KEY}; +use crate::proto_config::SCHEMA_PLUGIN_KEY; use crate::tool::Tool; use starbase_utils::{json, toml}; use std::fmt::Debug; @@ -50,7 +50,7 @@ pub fn locate_tool(id: &Id, proto: &ProtoEnvironment) -> miette::Result>(), + ["go", "internal-schema", "node"] + ); + } + + #[test] + fn can_disable() { + let sandbox = create_empty_sandbox(); + sandbox.create_file( + ".prototools", + r#" +[settings] +builtin-plugins = false +"#, + ); + + let config = + ProtoConfig::from_partial(ProtoConfig::load_from(sandbox.path(), false).unwrap()); + + assert_eq!( + config.settings.builtin_plugins, + BuiltinPlugins::Enabled(false) + ); + + assert_eq!(config.builtin_plugins().len(), 1); + } + + #[test] + fn can_disable_with_list() { + let sandbox = create_empty_sandbox(); + sandbox.create_file( + ".prototools", + r#" +[settings] +builtin-plugins = [] +"#, + ); + + let config = + ProtoConfig::from_partial(ProtoConfig::load_from(sandbox.path(), false).unwrap()); + + assert_eq!( + config.settings.builtin_plugins, + BuiltinPlugins::Allowed(vec![]) + ); + + assert_eq!(config.builtin_plugins().len(), 1); + } + } + mod tool_config { use super::*; use rustc_hash::FxHashMap; diff --git a/crates/core/tests/version_detector_test.rs b/crates/core/tests/version_detector_test.rs index ea53d1aaf..ea1605b61 100644 --- a/crates/core/tests/version_detector_test.rs +++ b/crates/core/tests/version_detector_test.rs @@ -14,7 +14,10 @@ mod version_detector { load_tool_from_locator( Id::raw("node"), ProtoEnvironment::new().unwrap(), - ProtoConfig::builtin_plugins().get("node").unwrap(), + ProtoConfig::default() + .builtin_plugins() + .get("node") + .unwrap(), ) .await .unwrap()