use color_eyre::eyre;
use crate::utils::{run_command, run_command_output_os, which};
#[derive(Debug, thiserror::Error)]
pub enum WingetInstallError {
#[error(
"winget is not available. Install Microsoft App Installer, then retry `water doctor --fix`."
)]
WingetNotFound,
#[error("winget command failed: {0}")]
CommandFailed(#[from] eyre::Report),
#[error("winget install completed but package `{package_id}` is still not detected")]
NotInstalled { package_id: &'static str },
}
const WINGET_COMMON_FLAGS: &[&str] = &[
"--exact",
"--silent",
"--accept-package-agreements",
"--accept-source-agreements",
];
pub async fn ensure_package_installed(package_id: &'static str) -> Result<(), WingetInstallError> {
if which("winget").await.is_err() {
return Err(WingetInstallError::WingetNotFound);
}
if is_package_installed(package_id).await? {
return Ok(());
}
let mut args = vec!["install", "--id", package_id];
args.extend(WINGET_COMMON_FLAGS);
run_command("winget", args)
.await
.map_err(WingetInstallError::CommandFailed)?;
if is_package_installed(package_id).await? {
Ok(())
} else {
Err(WingetInstallError::NotInstalled { package_id })
}
}
async fn is_package_installed(package_id: &'static str) -> Result<bool, WingetInstallError> {
let output = run_command_output_os("winget", ["list", "--id", package_id, "--exact"])
.await
.map_err(WingetInstallError::CommandFailed)?;
Ok(output.status.success())
}