use std::sync::Arc;
use eyre::Result;
use jiff::Timestamp;
use serde::Serialize;
use crate::backend::{Backend, VersionInfo};
use crate::cli::args::ToolArg;
use crate::config::Settings;
use crate::install_before::{resolve_before_date_for_backend, resolve_cli_minimum_release_age};
use crate::toolset::{ToolRequest, tool_request};
use crate::ui::multi_progress_report::MultiProgressReport;
use crate::{backend, config::Config};
#[derive(Serialize)]
struct VersionOutputAll {
tool: String,
version: String,
#[serde(skip_serializing_if = "Option::is_none")]
created_at: Option<String>,
prerelease: bool,
}
#[derive(Debug, clap::Args)]
#[clap(verbatim_doc_comment, after_long_help = AFTER_LONG_HELP, aliases = ["list-all", "list-remote"]
)]
pub struct LsRemote {
#[clap(value_name = "TOOL@VERSION", required_unless_present = "all")]
pub plugin: Option<ToolArg>,
#[clap(verbatim_doc_comment)]
pub prefix: Option<String>,
#[clap(long, verbatim_doc_comment, conflicts_with_all = ["plugin", "prefix"])]
pub all: bool,
#[clap(
long,
alias = "before",
value_name = "MINIMUM_RELEASE_AGE",
verbatim_doc_comment
)]
pub minimum_release_age: Option<String>,
#[clap(short = 'J', long, verbatim_doc_comment)]
pub json: bool,
#[clap(long, verbatim_doc_comment)]
pub no_versions_host: bool,
#[clap(long, verbatim_doc_comment)]
pub prerelease: bool,
#[clap(long, verbatim_doc_comment, requires_all = ["json", "no_versions_host"])]
pub strict_metadata: bool,
}
impl LsRemote {
pub async fn run(self) -> Result<()> {
if self.prerelease {
Settings::override_with(|s| s.prereleases = Some(true));
}
if self.no_versions_host {
Settings::override_with(|s| s.use_versions_host = Some(false));
}
backend::set_strict_metadata(self.strict_metadata);
let config = Config::get().await?;
let before_date = resolve_cli_minimum_release_age(self.minimum_release_age.as_deref())?;
if let Some(plugin) = self.get_plugin(&config).await? {
self.run_single(&config, plugin, before_date).await
} else {
self.run_all(&config, before_date).await
}
}
async fn run_single(
self,
config: &Arc<Config>,
plugin: Arc<dyn Backend>,
before_date: Option<Timestamp>,
) -> Result<()> {
let before_date =
resolve_before_date_for_backend(config, plugin.as_ref(), before_date).await?;
let prefix = match &self.plugin {
Some(tool_arg) => match &tool_arg.tvr {
Some(ToolRequest::Version { version: v, .. }) => Some(v.clone()),
Some(ToolRequest::Sub {
sub, orig_version, ..
}) => Some(tool_request::version_sub(orig_version, sub)),
_ => self.prefix.clone(),
},
_ => self.prefix.clone(),
};
let matches_prefix = |v: &str| prefix.as_ref().is_none_or(|p| v.starts_with(p));
let versions_matching_prefix = plugin
.list_remote_versions_with_info(config)
.await?
.into_iter()
.filter(|v| matches_prefix(&v.version))
.collect::<Vec<_>>();
let hidden_versions = before_date
.map(|before| VersionInfo::count_hidden_by_date(&versions_matching_prefix, before))
.unwrap_or_default();
let versions = filter_versions_by_date(versions_matching_prefix, before_date);
if self.json {
miseprintln!("{}", serde_json::to_string(&versions)?);
} else {
for v in versions {
miseprintln!("{}", v.version);
}
warn_if_versions_hidden_by_minimum_release_age(plugin.id(), hidden_versions);
}
Ok(())
}
async fn run_all(self, config: &Arc<Config>, before_date: Option<Timestamp>) -> Result<()> {
let mut versions = vec![];
let mut hidden_versions = 0;
for b in backend::list() {
let tool = b.id().to_string();
let before_date =
resolve_before_date_for_backend(config, b.as_ref(), before_date).await?;
let all_versions = b.list_remote_versions_with_info(config).await?;
if let Some(before) = before_date {
hidden_versions += VersionInfo::count_hidden_by_date(&all_versions, before);
}
for v in filter_versions_by_date(all_versions, before_date) {
versions.push(VersionOutputAll {
tool: tool.clone(),
version: v.version,
created_at: v.created_at,
prerelease: v.prerelease,
});
}
}
versions.sort_by(|a, b| a.tool.cmp(&b.tool));
if self.json {
miseprintln!("{}", serde_json::to_string(&versions)?);
} else {
for v in versions {
miseprintln!("{}@{}", v.tool, v.version);
}
warn_if_versions_hidden_by_minimum_release_age("tools", hidden_versions);
}
Ok(())
}
async fn get_plugin(&self, config: &Arc<Config>) -> Result<Option<Arc<dyn Backend>>> {
match &self.plugin {
Some(tool_arg) => {
let mut backend = tool_arg.ba.backend()?;
let mpr = MultiProgressReport::get();
if let Some(plugin) = backend.plugin() {
plugin.ensure_installed(config, &mpr, false, false).await?;
backend = tool_arg.ba.backend()?;
}
Ok(Some(backend))
}
None => Ok(None),
}
}
}
fn warn_if_versions_hidden_by_minimum_release_age(tool: &str, hidden_versions: usize) {
if hidden_versions == 0 {
return;
}
let s = if hidden_versions == 1 { "" } else { "s" };
warn!("{hidden_versions} newer {tool} release{s} hidden by minimum_release_age");
}
fn filter_versions_by_date(
versions: Vec<VersionInfo>,
before_date: Option<Timestamp>,
) -> Vec<VersionInfo> {
match before_date {
Some(before) => VersionInfo::filter_by_date(versions, before),
None => versions,
}
}
static AFTER_LONG_HELP: &str = color_print::cstr!(
r#"<bold><underline>Examples:</underline></bold>
$ <bold>mise ls-remote node</bold>
18.0.0
20.0.0
$ <bold>mise ls-remote node@20</bold>
20.0.0
20.1.0
$ <bold>mise ls-remote node 20</bold>
20.0.0
20.1.0
$ <bold>mise ls-remote node --minimum-release-age 2024-01-01</bold>
20.0.0
$ <bold>mise ls-remote github:cli/cli --json</bold>
[{"version":"2.62.0","created_at":"2024-11-14T15:40:35Z","prerelease":false},{"version":"2.61.0","created_at":"2024-10-23T19:22:15Z","prerelease":false}]
"#
);