Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow to uninstall multiples versions using semver pattern #1295

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ clap_complete = "4.5.2"
anyhow = "1.0.86"
indicatif = { version = "0.17.8", features = ["improved_unicode"] }
flate2 = "1.0.30"
semver = "1.0.23"

[dev-dependencies]
pretty_assertions = "1.4.0"
Expand Down
2 changes: 1 addition & 1 deletion docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -856,7 +856,7 @@ Options:
# `fnm uninstall`

```
Uninstall a Node.js version
Uninstall a Node.js version. Allow multiple versions using semantic versioning to be uninstalled at once. Example: `fnm uninstall "<v22.0.0"`

> Warning: when providing an alias, it will remove the Node version the alias > is pointing to, along with the other aliases that point to the same version.

Expand Down
97 changes: 45 additions & 52 deletions src/commands/uninstall.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use semver::VersionReq;
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;
Expand All @@ -12,7 +12,7 @@ use thiserror::Error;

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

impl Command for Uninstall {
Expand All @@ -21,63 +21,58 @@ impl Command for Uninstall {
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 requested_version_str = self.version.or_else(|| {
std::env::current_dir()
.ok()
.and_then(|dir| get_user_version_for_directory(dir, config).map(|v| v.to_string()))
})
.ok_or(Error::CantInferVersion)?;

let available_versions: Vec<&Version> = all_versions
.iter()
.filter(|v| requested_version.matches(v, config))
.collect();
let requested_version = VersionReq::parse(&requested_version_str.replace("v", ""))
.map_err(|_| Error::InvalidVersionFormat { version: requested_version_str.clone() })?;

if available_versions.len() >= 2 {
return Err(Error::PleaseBeMoreSpecificToDelete {
matched_versions: available_versions
.iter()
.map(std::string::ToString::to_string)
.collect(),
});
}
let matching_versions: Vec<&Version> = all_versions.iter().filter_map(|v| {
let ver = v.v_str().trim_start_matches('v').to_string();
semver::Version::parse(&ver)
.ok()
.filter(|parsed_version| requested_version.matches(parsed_version))
.map(|_| v)
}).collect();

let version = requested_version
.to_version(&all_versions, config)
.ok_or(Error::CantFindVersion)?;
if matching_versions.is_empty() {
return Err(Error::CantFindVersion);
}

let matching_aliases = version.find_aliases(config)?;
let root_path = version
.root_path(config)
.ok_or_else(|| Error::RootPathNotFound {
for version in matching_versions {
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 })?;
debug!("Removing Node version from {:?}", root_path);
std::fs::remove_dir_all(root_path)
.map_err(|source| Error::CantDeleteNodeVersion { source })?;
outln!(
config,
Info,
"Alias {} was removed successfully",
alias.name().cyan()
"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(())
Expand All @@ -90,11 +85,9 @@ pub enum Error {
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")]
#[error("Invalid version format: {}", version)]
InvalidVersionFormat { version: String },
#[error("Can't find any matching versions")]
CantFindVersion,
#[error("Root path not found for version {}", version)]
RootPathNotFound { version: Version },
Expand Down