use std::sync::Arc;
use atty::Stream::Stderr;
use color_eyre::eyre::Result;
use owo_colors::Stream;
use spinners_jdxcode::{Spinner, Spinners};
use crate::cli::args::runtime::{RuntimeArg, RuntimeArgParser};
use crate::cli::command::Command;
use crate::config::Config;
use crate::config::MissingRuntimeBehavior::AutoInstall;
use crate::config::Settings;
use crate::errors::Error::PluginNotInstalled;
use crate::output::Output;
use crate::plugins::InstallType::Version;
use crate::plugins::{Plugin, PluginName};
use crate::runtimes::RuntimeVersion;
use crate::ui::color::{bright_green, cyan};
#[derive(Debug, clap::Args)]
#[clap(visible_alias = "i", verbatim_doc_comment, after_long_help = AFTER_LONG_HELP)]
pub struct Install {
#[clap(value_parser = RuntimeArgParser)]
runtime: Option<Vec<RuntimeArg>>,
#[clap(long, short, conflicts_with = "runtime")]
plugin: Option<Vec<PluginName>>,
#[clap(long, short, requires = "runtime")]
force: bool,
#[clap(long, short, conflicts_with_all = ["runtime", "plugin", "force"])]
all: bool,
#[clap(long, short)]
verbose: bool,
}
impl Command for Install {
fn run(self, config: Config, out: &mut Output) -> Result<()> {
match &self.runtime {
Some(runtime) => self.install_runtimes(config, out, runtime)?,
None => self.install_missing_runtimes(config, out)?,
}
Ok(())
}
}
impl Install {
fn install_runtimes(
&self,
config: Config,
out: &mut Output,
runtimes: &[RuntimeArg],
) -> Result<()> {
let settings = Settings {
missing_runtime_behavior: AutoInstall,
..config.settings.clone()
};
for r in RuntimeArg::double_runtime_condition(runtimes) {
if r.version == "system" {
continue;
}
let plugin = Plugin::load_ensure_installed(&r.plugin, &settings)?;
let version = config.resolve_alias(&r.plugin, r.version.clone());
let version = plugin.latest_version(&version)?.unwrap_or(version);
let rtv = RuntimeVersion::new(Arc::new(plugin), &version);
if rtv.is_installed() && self.force {
rtv.uninstall()?;
} else if rtv.is_installed() {
warn!(
"{} is already installed",
cyan(Stream::Stderr, &rtv.to_string())
);
continue;
}
self.do_install(&config, out, &rtv)?;
}
Ok(())
}
fn install_missing_runtimes(&self, mut config: Config, out: &mut Output) -> Result<()> {
for rtv in config.ts.list_current_versions() {
let plugins = match self.all {
true => Some(get_all_plugin_names(&config)),
false => self.plugin.clone(),
};
if let Some(plugins) = &plugins {
config.settings.missing_runtime_behavior = AutoInstall;
if !plugins.contains(&rtv.plugin.name) {
continue;
}
if !rtv.plugin.ensure_installed(&config.settings)? {
Err(PluginNotInstalled(rtv.plugin.name.to_string()))?;
}
}
if !rtv.plugin.is_installed() {
warn_plugin_not_installed(&rtv.plugin);
continue;
}
if rtv.version == "system" || rtv.is_installed() {
continue;
}
let version = rtv
.plugin
.latest_version(&rtv.version)?
.unwrap_or_else(|| rtv.version.clone());
let rtv = RuntimeVersion::new(rtv.plugin.clone(), &version);
self.do_install(&config, out, &rtv)?;
}
Ok(())
}
fn do_install(&self, config: &Config, out: &mut Output, rtv: &RuntimeVersion) -> Result<()> {
let rtv_label = cyan(Stderr, &rtv.to_string());
let install_message = format!("Installing runtime: {rtv_label}...");
let sp = if self.verbose {
out.stderr.writeln(install_message);
None
} else {
Some(Spinner::with_stream(
Spinners::Dots10,
install_message,
spinners_jdxcode::Stream::Stderr,
))
};
rtv.install(Version, config, self.verbose)?;
if let Some(mut sp) = sp {
sp.stop_and_persist(
&bright_green(Stderr, "✔"),
format!("Runtime {rtv_label} installed"),
);
}
Ok(())
}
}
fn warn_plugin_not_installed(plugin: &Plugin) {
warn!(
"plugin {} is not installed. Install it with `rtx plugin add {}`",
cyan(Stderr, &plugin.name),
plugin.name,
);
}
fn get_all_plugin_names(config: &Config) -> Vec<String> {
config
.ts
.list_plugins()
.into_iter()
.map(|p| p.name.clone())
.collect()
}
const AFTER_LONG_HELP: &str = r#"
Examples:
$ rtx install nodejs@18.0.0 # install specific nodejs version
$ rtx install nodejs@18 # install fuzzy nodejs version
$ rtx install nodejs # install latest nodejs version—or what is specified in .tool-versions
$ rtx install # installs all runtimes specified in .tool-versions for installed plugins
$ rtx install --all # installs all runtimes and all plugins
"#;
#[cfg(test)]
mod test {
use crate::assert_cli;
#[test]
fn test_install_force() {
assert_cli!("install", "-f", "shfmt");
}
#[test]
fn test_install_asdf_style() {
assert_cli!("install", "shfmt", "2");
}
#[test]
fn test_install_with_alias() {
assert_cli!("install", "-f", "shfmt@my/alias");
assert_cli!("where", "shfmt@my/alias");
}
}