use std::process; use args::Args; use clap::Parser; use std::ffi::OsString; use consts::VERSION_FILE_PATH; use crossterm::style::Stylize; use mapper::Mapper; use repository::{config::Config, NodeVersion, Repository}; mod consts; pub mod error; pub mod mapper; pub mod repository; mod utils; mod web_api; use dialoguer::Confirm; use miette::{IntoDiagnostic, Result}; use tokio::fs; use crate::error::VersionError; mod args; #[tokio::main(flavor = "current_thread")] async fn main() -> Result<()> { miette::set_panic_hook(); let args: Args = Args::parse(); match args.commmand { args::Command::Version => { print_version(); Ok(()) }, args::Command::Install(v) => install_version(v.version).await, args::Command::Default(v) => set_default_version(v.version).await, args::Command::Exec(args) => { let exit_code = exec(args.command, args.args).await?; process::exit(exit_code); } args::Command::Refresh => refresh().await, args::Command::ListVersions => list_versions().await, }?; Ok(()) } fn print_version() { println!("{} v{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")); } pub async fn install_version(version: NodeVersion) -> Result<()> { if VERSION_FILE_PATH.exists() { fs::remove_file(&*VERSION_FILE_PATH) .await .into_diagnostic()?; } let repo = get_repository().await?; if repo.is_installed(&version)? && !Confirm::new() .with_prompt(format!( "The version {} is already installed. Reinstall?", version.to_string().bold() )) .default(false) .interact() .unwrap() { return Ok(()); } repo.install_version(&version).await?; println!("Installed {}", version.to_string().bold()); Ok(()) } pub async fn set_default_version(version: NodeVersion) -> Result<()> { let mut mapper = get_mapper().await?; if !mapper.repository().is_installed(&version)? && Confirm::new() .with_prompt(format!( "The version {version} is not installed. Do you want to install it?" )) .default(false) .interact() .unwrap() { mapper.repository().install_version(&version).await?; } mapper.set_default_version(&version).await?; println!("Now using {}", version.to_string().bold()); Ok(()) } #[inline] pub async fn exec(command: String, args: Vec) -> Result { let mapper = get_mapper().await?; let active_version = mapper.active_version(); if !mapper.repository().is_installed(active_version)? { mapper.repository().install_version(active_version).await?; } let exit_status = mapper.exec(command, args).await?; Ok(exit_status.code().unwrap_or(0)) } pub async fn refresh() -> Result<()> { get_mapper().await?.remap().await?; fs::remove_file(&*VERSION_FILE_PATH) .await .into_diagnostic()?; println!("Remapped binaries and cleared version cache"); Ok(()) } pub async fn list_versions() -> Result<()> { let mapper = get_mapper().await?; let versions = mapper.repository().installed_versions().await?; let active_version = mapper .repository() .lookup_version(mapper.active_version())?; println!("{}", "Installed versions:".bold()); for version in versions { let info = mapper .repository() .all_versions() .get(&version) .ok_or_else(|| VersionError::unknown_version(version.to_string()))?; let lts = info .lts .as_ref() .map(|l| format!(" ({})", l.to_owned().green())) .unwrap_or_default(); if version == active_version.version { println!(" {}{} [current]", version.to_string().blue().bold(), lts) } else { println!(" {}{}", version.to_string().blue(), lts) } } Ok(()) } async fn get_repository() -> Result { Repository::init(Config::load().await?).await } async fn get_mapper() -> Result { Ok(Mapper::load(get_repository().await?).await) }