use crate::alias::create_alias; use crate::arch::get_safe_arch; use crate::config::FnmConfig; use crate::downloader::{install_node_dist, Error as DownloaderError}; use crate::lts::LtsType; use crate::outln; use crate::remote_node_index; use crate::user_version::UserVersion; use crate::version::Version; use crate::version_files::get_user_version_for_directory; use colored::Colorize; use log::debug; use structopt::StructOpt; use thiserror::Error; #[derive(StructOpt, Debug, Default)] pub struct Install { /// A version string. Can be a partial semver or a LTS version name by the format lts/NAME pub version: Option, /// Install latest LTS #[structopt(long, conflicts_with = "version")] pub lts: bool, } impl Install { fn version(self) -> Result, Error> { match self { Self { version: Some(_), lts: true, } => Err(Error::TooManyVersionsProvided), Self { version: v, lts: false, } => Ok(v), Self { version: None, lts: true, } => Ok(Some(UserVersion::Full(Version::Lts(LtsType::Latest)))), } } } impl super::command::Command for Install { type Error = Error; fn apply(self, config: &FnmConfig) -> Result<(), Self::Error> { let current_dir = std::env::current_dir().unwrap(); let current_version = self .version()? .or_else(|| get_user_version_for_directory(current_dir, config)) .ok_or(Error::CantInferVersion)?; let version = match current_version.clone() { UserVersion::Full(Version::Semver(actual_version)) => Version::Semver(actual_version), UserVersion::Full(v @ (Version::Bypassed | Version::Alias(_))) => { return Err(Error::UninstallableVersion { version: v }); } UserVersion::Full(Version::Lts(lts_type)) => { let available_versions: Vec<_> = remote_node_index::list(&config.node_dist_mirror) .map_err(|source| Error::CantListRemoteVersions { source })?; let picked_version = lts_type .pick_latest(&available_versions) .ok_or_else(|| Error::CantFindRelevantLts { lts_type: lts_type.clone(), })? .version .clone(); debug!( "Resolved {} into Node version {}", Version::Lts(lts_type).v_str().cyan(), picked_version.v_str().cyan() ); picked_version } current_version => { let available_versions: Vec<_> = remote_node_index::list(&config.node_dist_mirror) .map_err(|source| Error::CantListRemoteVersions { source })? .drain(..) .map(|x| x.version) .collect(); current_version .to_version(&available_versions, config) .ok_or(Error::CantFindNodeVersion { requested_version: current_version, })? .clone() } }; // Automatically swap Apple Silicon to x64 arch for appropriate versions. let safe_arch = get_safe_arch(&config.arch, &version); let version_str = format!("Node {}", &version); outln!( config, Info, "Installing {} ({})", version_str.cyan(), safe_arch.to_string() ); match install_node_dist( &version, &config.node_dist_mirror, config.installations_dir(), safe_arch, ) { Err(err @ DownloaderError::VersionAlreadyInstalled { .. }) => { outln!(config, Error, "{} {}", "warning:".bold().yellow(), err); } other_err => other_err.map_err(|source| Error::DownloadError { source })?, }; if let UserVersion::Full(Version::Lts(lts_type)) = current_version { let alias_name = Version::Lts(lts_type).v_str(); debug!( "Tagging {} as alias for {}", alias_name.cyan(), version.v_str().cyan() ); create_alias(config, &alias_name, &version)?; } if !config.default_version_dir().exists() { debug!("Tagging {} as the default version", version.v_str().cyan()); create_alias(config, "default", &version)?; } Ok(()) } } #[derive(Debug, Error)] pub enum Error { #[error("Can't download the requested binary: {}", source)] DownloadError { source: DownloaderError }, #[error(transparent)] IoError { #[from] source: std::io::Error, }, #[error("Can't find version in dotfiles. Please provide a version manually to the command.")] CantInferVersion, #[error("Having a hard time listing the remote versions: {}", source)] CantListRemoteVersions { source: crate::http::Error }, #[error( "Can't find a Node version that matches {} in remote", requested_version )] CantFindNodeVersion { requested_version: UserVersion }, #[error("Can't find relevant LTS named {}", lts_type)] CantFindRelevantLts { lts_type: crate::lts::LtsType }, #[error("The requested version is not installable: {}", version.v_str())] UninstallableVersion { version: Version }, #[error("Too many versions provided. Please don't use --lts with a version string.")] TooManyVersionsProvided, } #[cfg(test)] mod tests { use super::super::command::Command; use super::*; use pretty_assertions::assert_eq; use std::str::FromStr; #[test] fn test_set_default_on_new_installation() { let base_dir = tempfile::tempdir().unwrap(); let config = FnmConfig::default().with_base_dir(Some(base_dir.path().to_path_buf())); assert!(!config.default_version_dir().exists()); Install { version: UserVersion::from_str("12.0.0").ok(), lts: false, } .apply(&config) .expect("Can't install"); assert!(config.default_version_dir().exists()); assert_eq!( config.default_version_dir().canonicalize().ok(), config .installations_dir() .join("v12.0.0") .join("installation") .canonicalize() .ok() ); } }