use std::collections::HashSet;
use crate::cli::args::ToolArg;
use crate::config::Config;
use crate::toolset::outdated_info::OutdatedInfo;
use crate::toolset::{ConfigScope, ResolveOptions, ToolsetBuilder};
use crate::ui::table;
use eyre::Result;
use indexmap::IndexMap;
use tabled::settings::Remove;
use tabled::settings::location::ByColumnName;
#[derive(Debug, clap::Args)]
#[clap(verbatim_doc_comment, after_long_help = AFTER_LONG_HELP)]
pub struct Outdated {
#[clap(value_name = "TOOL@VERSION", verbatim_doc_comment)]
pub tool: Vec<ToolArg>,
#[clap(long, short = 'b', verbatim_doc_comment)]
pub bump: bool,
#[clap(short = 'J', long, verbatim_doc_comment)]
pub json: bool,
#[clap(short = 'l', hide = true)]
pub legacy_bump: bool,
#[clap(long, verbatim_doc_comment, conflicts_with = "local")]
pub inactive: bool,
#[clap(long, verbatim_doc_comment)]
pub local: bool,
#[clap(long, verbatim_doc_comment)]
pub monorepo: bool,
#[clap(long)]
pub no_header: bool,
}
impl Outdated {
pub async fn run(mut self) -> Result<()> {
if self.legacy_bump {
deprecated_at!(
"2026.8.5",
"2027.8.5",
"cli.outdated.bump-l",
"`mise outdated -l` is deprecated. Use `mise outdated -b` or `mise outdated --bump` instead. After removal, `-l` will become shorthand for `--local`."
);
self.bump = true;
}
if self.monorepo {
unimplemented!("mise outdated --monorepo is not implemented yet");
}
let config = Config::get().await?;
let scope = if self.local {
ConfigScope::LocalOnly
} else {
ConfigScope::All
};
let mut ts = ToolsetBuilder::new()
.with_args(&self.tool)
.with_scope(scope)
.build(&config)
.await?;
let tool_set = self
.tool
.iter()
.map(|t| t.ba.clone())
.collect::<HashSet<_>>();
ts.versions
.retain(|_, tvl| tool_set.is_empty() || tool_set.contains(&tvl.backend));
let outdated = ts
.list_outdated_versions(
&config,
self.bump,
&ResolveOptions {
inactive: self.inactive,
..Default::default()
},
)
.await;
let bump_available = if !self.json && !self.bump && outdated.is_empty() {
ts.list_outdated_versions(
&config,
true,
&ResolveOptions {
inactive: self.inactive,
..Default::default()
},
)
.await
.iter()
.any(|o| o.bump.is_some())
} else {
false
};
self.display(outdated, bump_available)?;
Ok(())
}
fn display(&self, outdated: Vec<OutdatedInfo>, bump_available: bool) -> Result<()> {
match self.json {
true => self.display_json(outdated)?,
false => self.display_table(outdated, bump_available)?,
}
Ok(())
}
fn display_table(&self, outdated: Vec<OutdatedInfo>, bump_available: bool) -> Result<()> {
if outdated.is_empty() {
info!("All tools are up to date");
if bump_available {
info!(
"Newer versions are available outside the configured version ranges. Use `mise outdated --bump` to view them."
);
}
return Ok(());
}
let mut table = tabled::Table::new(outdated);
if !self.bump {
table.with(Remove::column(ByColumnName::new("bump")));
}
table::default_style(&mut table, self.no_header);
miseprintln!("{table}");
Ok(())
}
fn display_json(&self, outdated: Vec<OutdatedInfo>) -> Result<()> {
let mut map = IndexMap::new();
for o in outdated {
map.insert(o.name.to_string(), o);
}
miseprintln!("{}", serde_json::to_string_pretty(&map)?);
Ok(())
}
}
static AFTER_LONG_HELP: &str = color_print::cstr!(
r#"<bold><underline>Deprecation:</underline></bold>
The `-l` shorthand for `--bump` is deprecated and will be removed in mise 2027.8.5.
After removal, `-l` will become shorthand for `--local`. Use `-b` or `--bump` instead.
<bold><underline>Examples:</underline></bold>
$ <bold>mise outdated</bold>
Plugin Requested Current Latest
python 3.11 3.11.0 3.11.1
node 20 20.0.0 20.1.0
$ <bold>mise outdated node</bold>
Plugin Requested Current Latest
node 20 20.0.0 20.1.0
$ <bold>mise outdated --json</bold>
{"python": {"requested": "3.11", "current": "3.11.0", "latest": "3.11.1"}, ...}
$ <bold>mise outdated --local</bold>
Plugin Requested Current Latest
node 20 20.0.0 20.1.0
"#
);