mise 2026.9.4

Dev tools, env vars, and tasks in one CLI
use eyre::Result;

use super::driver::{self, Action, DriverOpts};
use crate::config::Config;
use crate::system;
use crate::system::history::OperationScope;

/// Upgrade installed bootstrap packages from `[bootstrap.packages]`
///
/// Refreshes package manager metadata and upgrades the configured packages
/// that are already installed: apk/apt/aur/dnf/pacman upgrade to the newest
/// available version (apk, apt, and dnf honor a version pinned in config), brew
/// pours the formula's current bottle and replaces the old keg, brew-cask
/// installs the current cask artifact, flatpak and flatpak-user update
/// applications and runtimes, mas upgrades App Store apps, and winget upgrades
/// Windows packages. Packages that are not installed yet are skipped — use
/// `mise bootstrap packages apply` for those.
///
/// Packages can also be given explicitly in `manager:package` form.
#[derive(Debug, usage_rs::Args)]
#[usage(
    visible_alias = "up",
    verbatim_doc_comment,
    example(
        r###"mise bootstrap packages upgrade
mise bootstrap packages upgrade brew:postgresql@17
mise bootstrap packages upgrade --manager brew-cask
mise bootstrap packages upgrade --manager mas
mise bootstrap packages upgrade --manager winget
mise bootstrap packages upgrade --manager apt --yes
mise bootstrap packages upgrade --dry-run"###
    )
)]
pub(crate) struct SystemUpgrade {
    /// Packages in `manager:package` form; defaults to everything configured
    /// in [bootstrap.packages]
    #[usage(value_name = "PACKAGE")]
    packages: Vec<String>,

    /// Only upgrade packages for this built-in or plugin manager
    #[usage(long, short)]
    manager: Option<String>,

    /// Print the commands that would run without running them
    #[usage(long, short = 'n')]
    dry_run: bool,

    /// Skip the confirmation prompt
    #[usage(long, short)]
    yes: bool,
}

impl SystemUpgrade {
    pub(crate) async fn run(self) -> Result<()> {
        OperationScope::wrap("bootstrap packages upgrade", self.dry_run, self.run_inner()).await
    }

    async fn run_inner(self) -> Result<()> {
        let mgrs = if self.packages.is_empty() {
            let config = Config::get().await?;
            system::packages_from_config(&config)
        } else {
            let config = Config::get().await?;
            system::packages_from_specs_with_config(&self.packages, Some(&config))?
        };
        let opts = DriverOpts {
            manager: self.manager,
            explicit: !self.packages.is_empty(),
            allow_unavailable_manager: false,
            dry_run: self.dry_run,
            // upgrades refresh metadata themselves (stale lists would make
            // them silent no-ops), so no separate --update flag
            update: false,
            yes: self.yes,
        };
        driver::run(mgrs, Action::Upgrade, &opts).await
    }
}