diff --git a/docs/configuration.md b/docs/configuration.md index 03d72af533..7a73fe6f30 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -39,11 +39,6 @@ erlang = ['23.3', '24.0'] # supports everything you can do with .tool-versions currently node = ['16', 'prefix:20', 'ref:master', 'path:~/.nodes/14'] -[plugins] -# specify a custom repo url -# note this will only be used if the plugin does not already exist -python = 'https://github.com/asdf-community/asdf-python' - [alias.node.versions] # project-local aliases # use vfox:version-fox/vfox-nodejs when running `mise i node@backend` backend = "vfox:version-fox/vfox-nodejs" @@ -52,6 +47,12 @@ my_custom_node = '20' [tasks.build] run = 'echo "running build tasks"' + +[plugins] +# DEPRECATED: use `alias.` instead +# specify a custom repo url +# note this will only be used if the plugin does not already exist +python = 'https://github.com/asdf-community/asdf-python' ``` `mise.toml` files are hierarchical. The configuration in a file in the current directory will diff --git a/e2e/backend/test_asdf b/e2e/backend/test_asdf new file mode 100644 index 0000000000..35d82888f9 --- /dev/null +++ b/e2e/backend/test_asdf @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +assert "mise x asdf:jdx/mise-tiny -- mise-tiny" "mise-tiny: v3.1.0" +assert "mise x asdf:https://github.com/jdx/mise-tiny -- mise-tiny" "mise-tiny: v3.1.0" diff --git a/e2e/plugins/test_plugin_install b/e2e/plugins/test_plugin_install index a116f1a8eb..2cd762d467 100644 --- a/e2e/plugins/test_plugin_install +++ b/e2e/plugins/test_plugin_install @@ -33,3 +33,6 @@ mise plugin uninstall tiny mise plugin update mise plugin update shfmt mise i + +assert_contains "mise plugin install mise-x 2>&1 || true" "No repository found for plugin mise-x" +assert_contains "mise plugin add node 2>&1 || true" "node is a core plugin and does not need to be installed" diff --git a/e2e/tools/test_tools_alias b/e2e/tools/test_tools_alias index 79c8cafc67..65b7af58fb 100644 --- a/e2e/tools/test_tools_alias +++ b/e2e/tools/test_tools_alias @@ -1,18 +1,25 @@ #!/usr/bin/env bash cat <mise.toml -tools.node = "100.0.0" +tools.node = "1.0.0-node" +tools.erlang = "1.0.0-erlang" +tools.python = "1.0.0-python" tools.mytool = "2" tools.mytool-lts = "lts" -alias.node = "asdf:tiny" -alias.mytool = "asdf:tiny" +[alias] +erlang = 'asdf:https://github.com/jdx/mise-tiny' +python = 'asdf:jdx/mise-tiny' +node = "asdf:tiny" +mytool = "asdf:tiny" [alias.mytool-lts] backend = "asdf:tiny" versions = {lts = "1.0.1"} EOF -assert_contains "mise x node -- rtx-tiny" "rtx-tiny: v100.0.0" +assert "mise x node -- rtx-tiny" "rtx-tiny: v1.0.0-node args:" +assert "mise x python -- mise-tiny" "mise-tiny: v1.0.0-python" +assert "mise x erlang -- mise-tiny" "mise-tiny: v1.0.0-erlang" assert_contains "mise x mytool -- rtx-tiny" "rtx-tiny: v2.1.0" assert_contains "mise x mytool-lts -- rtx-tiny" "rtx-tiny: v1.0.1" diff --git a/src/backend/asdf.rs b/src/backend/asdf.rs index 23c37df550..b0878f215a 100644 --- a/src/backend/asdf.rs +++ b/src/backend/asdf.rs @@ -4,9 +4,6 @@ use std::fs; use std::hash::{Hash, Hasher}; use std::path::{Path, PathBuf}; -use color_eyre::eyre::{eyre, Result, WrapErr}; -use console::style; - use crate::backend::backend_type::BackendType; use crate::backend::external_plugin_cache::ExternalPluginCache; use crate::backend::Backend; @@ -22,7 +19,10 @@ use crate::plugins::Script::{Download, ExecEnv, Install, ParseLegacyFile}; use crate::plugins::{Plugin, PluginType, Script, ScriptManager}; use crate::toolset::{ToolRequest, ToolVersion, Toolset}; use crate::ui::progress_report::SingleReport; -use crate::{env, file}; +use crate::{dirs, env, file}; +use color_eyre::eyre::{eyre, Result, WrapErr}; +use console::style; +use heck::ToKebabCase; /// This represents a plugin installed to ~/.local/share/mise/plugins pub struct AsdfBackend { @@ -42,7 +42,8 @@ pub struct AsdfBackend { impl AsdfBackend { pub fn from_arg(ba: BackendArg) -> Self { let name = ba.tool_name.clone(); - let plugin_path = ba.plugin_path.clone(); + let plugin_path = dirs::PLUGINS.join(ba.short.to_kebab_case()); + let plugin = AsdfPlugin::new(name.clone(), plugin_path.clone()); let mut toml_path = plugin_path.join("mise.plugin.toml"); if plugin_path.join("rtx.plugin.toml").exists() { toml_path = plugin_path.join("rtx.plugin.toml"); @@ -75,13 +76,7 @@ impl AsdfBackend { .with_fresh_file(plugin_path.join("bin/list-legacy-filenames")) .build(), plugin_path, - plugin: Box::new(AsdfPlugin::new( - ba.plugin_path - .file_name() - .unwrap() - .to_string_lossy() - .to_string(), - )), + plugin: Box::new(plugin), repo_url: None, toml, name, diff --git a/src/backend/vfox.rs b/src/backend/vfox.rs index 8173ed80a4..38cbe00f71 100644 --- a/src/backend/vfox.rs +++ b/src/backend/vfox.rs @@ -112,7 +112,7 @@ impl VfoxBackend { pub fn from_arg(ba: BackendArg) -> Self { let pathname = ba.short.to_kebab_case(); let plugin_path = dirs::PLUGINS.join(&pathname); - let mut plugin = VfoxPlugin::new(pathname.clone()); + let mut plugin = VfoxPlugin::new(pathname.clone(), plugin_path.clone()); plugin.full = Some(ba.full()); Self { remote_version_cache: CacheManagerBuilder::new( diff --git a/src/cli/args/backend_arg.rs b/src/cli/args/backend_arg.rs index 69df6fc52b..3d636c9f5a 100644 --- a/src/cli/args/backend_arg.rs +++ b/src/cli/args/backend_arg.rs @@ -27,8 +27,6 @@ pub struct BackendArg { pub installs_path: PathBuf, /// ~/.local/share/mise/downloads/ pub downloads_path: PathBuf, - /// ~/.local/share/mise/plugins/ - pub plugin_path: PathBuf, pub opts: Option, // TODO: make this not a hash key anymore to use this // backend: OnceCell, @@ -80,7 +78,6 @@ impl BackendArg { tool_name, short, full, - plugin_path: dirs::PLUGINS.join(&pathname), cache_path: dirs::CACHE.join(&pathname), installs_path: dirs::INSTALLS.join(&pathname), downloads_path: dirs::DOWNLOADS.join(&pathname), diff --git a/src/cli/plugins/install.rs b/src/cli/plugins/install.rs index 02ad6a8438..101490dfc2 100644 --- a/src/cli/plugins/install.rs +++ b/src/cli/plugins/install.rs @@ -1,11 +1,13 @@ use color_eyre::eyre::{bail, eyre, Result}; use contracts::ensures; +use heck::ToKebabCase; use rayon::prelude::*; use rayon::ThreadPoolBuilder; use url::Url; use crate::backend::unalias_backend; use crate::config::{Config, Settings}; +use crate::dirs; use crate::plugins::asdf_plugin::AsdfPlugin; use crate::plugins::core::CORE_PLUGINS; use crate::plugins::Plugin; @@ -112,7 +114,8 @@ impl PluginsInstall { git_url: Option, mpr: &MultiProgressReport, ) -> Result<()> { - let mut plugin = AsdfPlugin::new(name.clone()); + let path = dirs::PLUGINS.join(name.to_kebab_case()); + let mut plugin = AsdfPlugin::new(name.clone(), path); plugin.repo_url = git_url; if !self.force && plugin.is_installed() { warn!("Plugin {name} already installed"); @@ -170,24 +173,3 @@ static AFTER_LONG_HELP: &str = color_print::cstr!( $ mise plugins install node https://github.com/mise-plugins/rtx-nodejs.git#v1.0.0 "# ); - -#[cfg(test)] -mod tests { - use insta::assert_snapshot; - use test_log::test; - - use crate::test::reset; - #[test] - fn test_plugin_install_invalid_url() { - reset(); - let err = assert_cli_err!("plugin", "add", "tiny*"); - assert_snapshot!(err, @"No repository found for plugin tiny*"); - } - - #[test] - fn test_plugin_install_core_plugin() { - reset(); - let err = assert_cli_err!("plugin", "add", "node"); - assert_snapshot!(err, @"node is a core plugin and does not need to be installed"); - } -} diff --git a/src/config/env_directive.rs b/src/config/env_directive.rs index ce8a700949..b488e88da6 100644 --- a/src/config/env_directive.rs +++ b/src/config/env_directive.rs @@ -4,6 +4,7 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use eyre::{eyre, Context}; +use heck::ToKebabCase; use indexmap::IndexMap; use serde::{Deserialize, Deserializer}; @@ -302,7 +303,8 @@ impl EnvResults { } } EnvDirective::Module(name, value) => { - let plugin = VfoxPlugin::new(name); + let path = dirs::PLUGINS.join(name.to_kebab_case()); + let plugin = VfoxPlugin::new(name, path); if let Some(env) = plugin.mise_env(&value)? { for (k, v) in env { r.env.insert(k, (v, source.clone())); diff --git a/src/config/mod.rs b/src/config/mod.rs index 219448d18e..550e97a243 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -199,8 +199,15 @@ impl Config { .get(plugin_name) .map(|full| registry::full_to_url(&full[0])) .or_else(|| { - if plugin_name.starts_with("https://") || plugin_name.split('/').count() == 2 { - Some(registry::full_to_url(plugin_name)) + if plugin_name.starts_with("https://") + || plugin_name.starts_with("http://") + || plugin_name.starts_with("git@") + || plugin_name.starts_with("ssh://") + || plugin_name.starts_with("git://") + { + Some(plugin_name.to_string()) + } else if plugin_name.split('/').count() == 2 { + Some(format!("https://github.com/{}.git", plugin_name)) } else { None } diff --git a/src/file.rs b/src/file.rs index 24992edb0b..999af0de5b 100644 --- a/src/file.rs +++ b/src/file.rs @@ -719,7 +719,7 @@ mod tests { fn test_dir_subdirs() { reset(); let subdirs = dir_subdirs(&dirs::HOME).unwrap(); - assert!(subdirs.contains(&"cwd".to_string())); + assert!(subdirs.contains("cwd")); } #[test] diff --git a/src/plugins/asdf_plugin.rs b/src/plugins/asdf_plugin.rs index a620809a38..bdd3b7adf1 100644 --- a/src/plugins/asdf_plugin.rs +++ b/src/plugins/asdf_plugin.rs @@ -23,7 +23,7 @@ use xx::regex; #[derive(Debug)] pub struct AsdfPlugin { pub name: String, - pub plugin_path: PathBuf, + pub path: PathBuf, pub repo: Mutex, pub repo_url: Option, pub script_man: ScriptManager, @@ -31,15 +31,14 @@ pub struct AsdfPlugin { impl AsdfPlugin { #[requires(!name.is_empty())] - pub fn new(name: String) -> Self { - let plugin_path = dirs::PLUGINS.join(&name); - let repo = Git::new(&plugin_path); + pub fn new(name: String, path: PathBuf) -> Self { + let repo = Git::new(&path); Self { - script_man: build_script_man(&name, &plugin_path), + script_man: build_script_man(&name, &path), name, repo_url: None, repo: Mutex::new(repo), - plugin_path, + path, } } @@ -185,7 +184,7 @@ impl Plugin for AsdfPlugin { } fn path(&self) -> PathBuf { - self.plugin_path.clone() + self.path.clone() } fn get_plugin_type(&self) -> PluginType { @@ -216,7 +215,7 @@ impl Plugin for AsdfPlugin { } fn is_installed(&self) -> bool { - self.plugin_path.exists() + self.path.exists() } fn is_installed_err(&self) -> eyre::Result<()> { @@ -256,12 +255,12 @@ impl Plugin for AsdfPlugin { } let prefix = format!("plugin:{}", style(&self.name).blue().for_stderr()); let pr = mpr.add(&prefix); - let _lock = lock_file::get(&self.plugin_path, force)?; + let _lock = lock_file::get(&self.path, force)?; self.install(pr.as_ref()) } fn update(&self, pr: &dyn SingleReport, gitref: Option) -> Result<()> { - let plugin_path = self.plugin_path.to_path_buf(); + let plugin_path = self.path.to_path_buf(); if plugin_path.is_symlink() { warn!( "plugin:{} is a symlink, not updating", @@ -309,7 +308,7 @@ impl Plugin for AsdfPlugin { }) }; - rmdir(&self.plugin_path)?; + rmdir(&self.path)?; Ok(()) } @@ -331,7 +330,7 @@ If you are trying to link to a local directory, use `mise plugins link` instead. Plugins could support local directories in the future but for now a symlink is required which `mise plugins link` will create for you."# ))?; } - let git = Git::new(&self.plugin_path); + let git = Git::new(&self.path); pr.set_message(format!("cloning {repo_url}")); git.clone(&repo_url)?; if let Some(ref_) = &repo_ref { @@ -349,7 +348,7 @@ Plugins could support local directories in the future but for now a symlink is r } fn external_commands(&self) -> eyre::Result> { - let command_path = self.plugin_path.join("lib/commands"); + let command_path = self.path.join("lib/commands"); if !self.is_installed() || !command_path.exists() || self.name == "direnv" { // asdf-direnv is disabled since it conflicts with mise's built-in direnv functionality return Ok(vec![]); @@ -394,7 +393,7 @@ Plugins could support local directories in the future but for now a symlink is r return Err(PluginNotInstalled(self.name.clone()).into()); } let script = Script::RunExternalCommand( - self.plugin_path + self.path .join("lib/commands") .join(format!("command-{command}.bash")), args, diff --git a/src/plugins/core/erlang.rs b/src/plugins/core/erlang.rs index 371ea4ed9a..e255416eab 100644 --- a/src/plugins/core/erlang.rs +++ b/src/plugins/core/erlang.rs @@ -51,6 +51,7 @@ impl ErlangPlugin { self.install_kerl()?; cmd!(self.kerl_path(), "update", "releases") .env("KERL_BASE_DIR", self.kerl_base_dir()) + .stdout_to_stderr() .run()?; Ok(()) } @@ -104,6 +105,7 @@ impl Backend for ErlangPlugin { ctx.tv.install_path() ) .env("KERL_BASE_DIR", self.ba.cache_path.join("kerl")) + .stdout_to_stderr() .run()?; } } diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index 57f6be8d49..0c18f34839 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -1,3 +1,4 @@ +use crate::dirs; use crate::errors::Error::PluginNotInstalled; use crate::plugins::asdf_plugin::AsdfPlugin; use crate::plugins::vfox_plugin::VfoxPlugin; @@ -6,6 +7,7 @@ use crate::ui::multi_progress_report::MultiProgressReport; use crate::ui::progress_report::SingleReport; use clap::Command; use eyre::{eyre, Result}; +use heck::ToKebabCase; use once_cell::sync::Lazy; use regex::Regex; pub use script_manager::{Script, ScriptManager}; @@ -35,9 +37,10 @@ impl PluginType { } pub fn plugin(&self, short: String) -> APlugin { + let path = dirs::PLUGINS.join(short.to_kebab_case()); match self { - PluginType::Asdf => Box::new(AsdfPlugin::new(short)), - PluginType::Vfox => Box::new(VfoxPlugin::new(short)), + PluginType::Asdf => Box::new(AsdfPlugin::new(short, path)), + PluginType::Vfox => Box::new(VfoxPlugin::new(short, path)), } } } diff --git a/src/plugins/vfox_plugin.rs b/src/plugins/vfox_plugin.rs index daa8774528..60a40ea8f3 100644 --- a/src/plugins/vfox_plugin.rs +++ b/src/plugins/vfox_plugin.rs @@ -27,8 +27,7 @@ pub struct VfoxPlugin { impl VfoxPlugin { #[requires(!name.is_empty())] - pub fn new(name: String) -> Self { - let plugin_path = dirs::PLUGINS.join(&name); + pub fn new(name: String, plugin_path: PathBuf) -> Self { let repo = Git::new(&plugin_path); Self { name, diff --git a/src/registry.rs b/src/registry.rs index 3319e63c5e..0904f54a7b 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -98,7 +98,12 @@ fn normalize_remote(remote: &str) -> eyre::Result { pub fn full_to_url(full: &str) -> String { let (_backend, url) = full.split_once(':').unwrap_or(("", full)); - if url.starts_with("https://") { + if url.starts_with("https://") + || url.starts_with("http://") + || url.starts_with("git@") + || url.starts_with("ssh://") + || url.starts_with("git://") + { url.to_string() } else { format!("https://github.com/{url}.git")