diff --git a/.changeset/fifty-emus-type.md b/.changeset/fifty-emus-type.md new file mode 100644 index 000000000..406a88ae0 --- /dev/null +++ b/.changeset/fifty-emus-type.md @@ -0,0 +1,5 @@ +--- +"fnm": minor +--- + +feat: add remote version sorting and filtering diff --git a/docs/commands.md b/docs/commands.md index 8468c8673..616d520ea 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -81,6 +81,9 @@ List all remote Node.js versions Usage: fnm list-remote [OPTIONS] Options: + --filter + Filter versions by a user-defined version or a semver range + --node-dist-mirror mirror @@ -92,6 +95,21 @@ Options: [env: FNM_DIR] + --lts [] + Show only LTS versions (optionally filter by LTS codename) + + --sort + Version sorting order + + [default: asc] + + Possible values: + - desc: Sort versions in descending order (latest to earliest) + - asc: Sort versions in ascending order (earliest to latest) + + --latest + Only show the latest matching version + --log-level The log level of fnm commands diff --git a/src/commands/ls_remote.rs b/src/commands/ls_remote.rs index 015b3ac5f..32f744931 100644 --- a/src/commands/ls_remote.rs +++ b/src/commands/ls_remote.rs @@ -1,20 +1,79 @@ use crate::config::FnmConfig; use crate::remote_node_index; +use crate::user_version::UserVersion; + +use colored::Colorize; use thiserror::Error; #[derive(clap::Parser, Debug)] -pub struct LsRemote {} +pub struct LsRemote { + /// Filter versions by a user-defined version or a semver range + #[arg(long)] + filter: Option, + + /// Show only LTS versions (optionally filter by LTS codename) + #[arg(long)] + #[allow(clippy::option_option)] + lts: Option>, + + /// Version sorting order + #[arg(long, default_value = "asc")] + sort: SortingMethod, + + /// Only show the latest matching version + #[arg(long)] + latest: bool, +} + +#[derive(clap::ValueEnum, Clone, Debug, PartialEq)] +pub enum SortingMethod { + #[clap(name = "desc")] + /// Sort versions in descending order (latest to earliest) + Descending, + #[clap(name = "asc")] + /// Sort versions in ascending order (earliest to latest) + Ascending, +} impl super::command::Command for LsRemote { type Error = Error; fn apply(self, config: &FnmConfig) -> Result<(), Self::Error> { - let all_versions = remote_node_index::list(&config.node_dist_mirror)?; + let mut all_versions = remote_node_index::list(&config.node_dist_mirror)?; + + if let Some(lts) = &self.lts { + match lts { + Some(codename) => all_versions.retain(|v| { + v.lts + .as_ref() + .is_some_and(|v_lts| v_lts.eq_ignore_ascii_case(codename)) + }), + None => all_versions.retain(|v| v.lts.is_some()), + }; + } + + if let Some(filter) = &self.filter { + all_versions.retain(|v| filter.matches(&v.version, config)); + } + + if self.latest { + all_versions.truncate(1); + } + + all_versions.sort_by_key(|v| v.version.clone()); + if let SortingMethod::Descending = self.sort { + all_versions.reverse(); + } + + if all_versions.is_empty() { + eprintln!("{}", "No versions were found!".red()); + return Ok(()); + } - for version in all_versions { + for version in &all_versions { print!("{}", version.version); if let Some(lts) = &version.lts { - print!(" ({lts})"); + print!("{}", format!(" ({lts})").cyan()); } println!(); }