use anyhow::Result;
use clap::Parser;
use itertools::Itertools;
use node_semver::Range;
use crate::{
node_version,
node_version::{parse_range, InstalledNodeVersion, NodeVersion, OnlineNodeVersion},
subcommand::Action,
Config,
};
enum VersionStatus<'p> {
Latest,
NotInstalled,
Outdated(&'p OnlineNodeVersion),
}
impl<'p> VersionStatus<'p> {
fn from<T: NodeVersion>(versions: &[&T], latest: &'p OnlineNodeVersion) -> VersionStatus<'p> {
if versions.is_empty() {
VersionStatus::NotInstalled
} else if versions
.iter()
.all(|version| version.version() < latest.version())
{
VersionStatus::Outdated(latest)
} else {
VersionStatus::Latest
}
}
fn to_emoji(&self) -> char {
match self {
VersionStatus::Latest => '✅',
VersionStatus::NotInstalled => '〰',
VersionStatus::Outdated(_) => '⏫',
}
}
fn to_version_string(&self) -> String {
match self {
VersionStatus::Outdated(version) => format!("-> {}", version.to_string()),
_ => "".to_string(),
}
}
}
#[derive(Parser, Clone, Debug)]
#[command(about = "List installed and released node versions", alias = "ls")]
pub struct ListCommand {
#[arg(short, long, alias = "installed")]
pub local: bool,
#[arg(short('F'), long, value_parser = parse_range)]
pub filter: Option<Range>,
}
impl Action<ListCommand> for ListCommand {
fn run(config: &Config, options: &ListCommand) -> Result<()> {
let mut installed_versions = InstalledNodeVersion::list(config);
if let Some(filter) = &options.filter {
installed_versions = node_version::filter_version_req(installed_versions, filter);
}
if options.local {
println!(
"{}",
installed_versions
.iter()
.map(|version| version.to_string())
.join("\n")
);
return Ok(());
}
let mut latest_per_major = Vec::<&OnlineNodeVersion>::new();
let online_versions = OnlineNodeVersion::fetch_all()?;
if !online_versions.is_empty() {
latest_per_major = node_version::get_latest_of_each_major(&online_versions);
latest_per_major.sort();
latest_per_major.reverse();
}
let majors_and_installed_versions: Vec<(&OnlineNodeVersion, Vec<&InstalledNodeVersion>)> =
latest_per_major
.into_iter()
.map(|latest| {
(
latest,
installed_versions
.iter()
.filter(|installed| installed.version().major == latest.version().major)
.collect(),
)
})
.collect();
let mut versions_to_show = Vec::<(&OnlineNodeVersion, &Vec<&InstalledNodeVersion>)>::new();
for (i, (latest, installed)) in majors_and_installed_versions.iter().enumerate() {
if i < 5 || !installed.is_empty() {
versions_to_show.push((latest, installed));
}
}
let output = versions_to_show
.iter()
.map(|(online_version, installed_versions)| {
let version_status = VersionStatus::from(installed_versions, online_version);
let version_to_show = if installed_versions.is_empty() {
online_version.to_string()
} else {
installed_versions[0].to_string()
};
format!(
"{} {} {}",
&version_status.to_emoji(),
version_to_show,
&version_status.to_version_string(),
)
})
.join("\n");
println!("{output}");
Ok(())
}
}