mise 2026.9.4

Dev tools, env vars, and tasks in one CLI
use std::sync::Arc;

use console::style;
use eyre::Result;
use tokio::{sync::Semaphore, task::JoinSet};

use crate::config::Settings;
use crate::plugins;
use crate::toolset::install_state;
use crate::ui::multi_progress_report::MultiProgressReport;

use super::{PluginTaskNames, PluginTaskResult, join_plugin_tasks, spawn_plugin_task};

/// Update a plugin to the latest version
///
/// With no names, updates every installed plugin. This updates plugin source,
/// not the tool versions it manages. Linked local plugins are skipped; archive
/// installations cannot be updated with Git.
#[derive(Debug, usage_rs::Args)]
#[usage(verbatim_doc_comment, visible_aliases = ["up", "upgrade"], example(r###"mise plugins update              # update all installed plugins
mise plugins update my-tool      # update one Git plugin
mise plugins update my-tool#main # select an upstream ref"###))]
pub(super) struct Update {
    /// Plugin(s) to update
    #[usage()]
    plugin: Option<Vec<String>>,

    /// Number of jobs to run in parallel
    /// Values below 1 are treated as 1
    /// Defaults to the `jobs` setting
    #[usage(long, short, verbatim_doc_comment)]
    jobs: Option<usize>,
}

impl Update {
    pub(super) async fn run(self) -> Result<()> {
        let plugins: Vec<_> = match self.plugin {
            Some(plugins) => plugins
                .into_iter()
                .map(|p| match p.split_once('#') {
                    Some((p, ref_)) => (p.to_string(), Some(ref_.to_string())),
                    None => (p, None),
                })
                .collect(),
            None => install_state::list_plugins()
                .keys()
                .map(|p| (p.clone(), None))
                .collect::<Vec<_>>(),
        };

        let settings = Settings::try_get()?;
        let mut jset: JoinSet<PluginTaskResult> = JoinSet::new();
        let mut task_names = PluginTaskNames::new();
        let jobs = crate::jobs::resolve(settings.jobs, self.jobs);
        let semaphore = Arc::new(Semaphore::new(jobs));
        for (short, ref_) in plugins {
            let semaphore = semaphore.clone();
            let plugin_name = short.clone();
            spawn_plugin_task(&mut jset, &mut task_names, plugin_name, async move {
                let _permit = semaphore.acquire_owned().await?;
                let plugin = plugins::get(&short)?;
                let prefix = format!("plugin:{}", style(plugin.name()).blue().for_stderr());
                let mpr = MultiProgressReport::get();
                let pr = mpr.add(&prefix);
                plugin.update(pr.as_ref(), ref_).await?;
                Ok(())
            });
        }

        join_plugin_tasks(jset, task_names, "update").await
    }
}