fnm 1.35.0

Fast and simple Node.js version manager
use super::command::Command;
use crate::config::FnmConfig;
use crate::fs::remove_symlink_dir;
use crate::installed_versions;
use crate::outln;
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 thiserror::Error;

#[derive(clap::Parser, Debug)]
pub struct Uninstall {
    version: Option<UserVersion>,
}

impl Command for Uninstall {
    type Error = Error;

    fn apply(self, config: &FnmConfig) -> Result<(), Self::Error> {
        let all_versions = installed_versions::list(config.installations_dir())
            .map_err(|source| Error::VersionListingError { source })?;
        let requested_version = self
            .version
            .or_else(|| {
                let current_dir = std::env::current_dir().unwrap();
                get_user_version_for_directory(current_dir, config)
            })
            .ok_or(Error::CantInferVersion)?;

        if matches!(requested_version, UserVersion::Full(Version::Bypassed)) {
            return Err(Error::CantUninstallSystemVersion);
        }

        let available_versions: Vec<&Version> = all_versions
            .iter()
            .filter(|v| requested_version.matches(v, config))
            .collect();

        if available_versions.len() >= 2 {
            return Err(Error::PleaseBeMoreSpecificToDelete {
                matched_versions: available_versions
                    .iter()
                    .map(std::string::ToString::to_string)
                    .collect(),
            });
        }

        let version = requested_version
            .to_version(&all_versions, config)
            .ok_or(Error::CantFindVersion)?;

        let matching_aliases = version.find_aliases(config)?;
        let root_path = version
            .root_path(config)
            .ok_or_else(|| Error::RootPathNotFound {
                version: version.clone(),
            })?;

        debug!("Removing Node version from {:?}", root_path);
        std::fs::remove_dir_all(root_path)
            .map_err(|source| Error::CantDeleteNodeVersion { source })?;
        outln!(
            config,
            Info,
            "Node version {} was removed successfully",
            version.v_str().cyan()
        );

        for alias in matching_aliases {
            debug!("Removing alias from {:?}", alias.path());
            remove_symlink_dir(alias.path())
                .map_err(|source| Error::CantDeleteSymlink { source })?;
            outln!(
                config,
                Info,
                "Alias {} was removed successfully",
                alias.name().cyan()
            );
        }

        Ok(())
    }
}

#[derive(Debug, Error)]
pub enum Error {
    #[error("Can't get locally installed versions: {}", source)]
    VersionListingError { source: installed_versions::Error },
    #[error("Can't find version in dotfiles. Please provide a version manually to the command.")]
    CantInferVersion,
    #[error("Can't uninstall system version")]
    CantUninstallSystemVersion,
    #[error("Too many versions had matched, please be more specific.\nFound {} matching versions, expected 1:\n{}", matched_versions.len(), matched_versions.iter().map(|v| format!("* {v}")).collect::<Vec<_>>().join("\n"))]
    PleaseBeMoreSpecificToDelete { matched_versions: Vec<String> },
    #[error("Can't find a matching version")]
    CantFindVersion,
    #[error("Root path not found for version {}", version)]
    RootPathNotFound { version: Version },
    #[error("io error: {}", source)]
    IoError {
        #[from]
        source: std::io::Error,
    },
    #[error("Can't delete Node.js version: {}", source)]
    CantDeleteNodeVersion { source: std::io::Error },
    #[error("Can't delete symlink: {}", source)]
    CantDeleteSymlink { source: std::io::Error },
}