winbrew_cli\commands/
remove.rs

1//! Remove command wrapper for dependency checks, confirmation prompts, and
2//! removal outcomes.
3
4use anyhow::Result;
5
6use crate::commands::error::reported_with_hint;
7use crate::{CommandContext, app::remove};
8use winbrew_ui::Ui;
9
10pub fn run(ctx: &CommandContext, name: &[String], yes: bool, force: bool) -> Result<()> {
11    let mut ui = ctx.ui();
12    ui.page_title("Remove Package");
13
14    let name_text = name.join(" ").trim().to_owned();
15    if name_text.is_empty() {
16        return Err(anyhow::anyhow!("package name cannot be empty"));
17    }
18
19    ui.info(format!("Assessing impact for {name_text}..."));
20    let plan = remove::plan_removal(&name_text)?;
21
22    if !plan.dependents.is_empty() {
23        ui.warn(format!(
24            "Caution: {} is required by: {}",
25            plan.package.name,
26            plan.dependents.join(", ")
27        ));
28    }
29
30    if !should_proceed(&mut ui, &plan, yes, force)? {
31        ui.notice("Removal aborted.");
32        return Ok(());
33    }
34
35    let removal_result = ui.spinner(format!("Removing {}...", plan.package.name), || {
36        remove::execute_removal(&plan, force)
37    });
38
39    if let Err(err) = removal_result {
40        match err {
41            remove::RemovalError::DependentPackagesBlocked { name, dependents } => {
42                ui.warn(format!(
43                    "Removal of {name} was blocked because it is required by: {}",
44                    dependents
45                ));
46                let message = format!(
47                    "cannot remove '{name}' because it is required by: {}",
48                    dependents
49                );
50                ui.notice("Hint: re-run with --force if you intend to remove the package anyway.");
51                return Err(reported_with_hint(
52                    message,
53                    "Re-run with --force if you intend to remove the package anyway.",
54                ));
55            }
56            remove::RemovalError::UnsupportedPackageType { kind } => {
57                ui.error(format!("unsupported package type: {kind}"));
58                let message = format!("unsupported package type: {kind}");
59                ui.notice("Hint: check the package metadata or choose a supported installer type.");
60                return Err(reported_with_hint(
61                    message,
62                    "Check the package metadata or choose a supported installer type.",
63                ));
64            }
65            remove::RemovalError::Unexpected(err) => return Err(err),
66        }
67    }
68
69    ui.success(format!("Successfully removed {}.", plan.package.name));
70
71    Ok(())
72}
73
74fn should_proceed<W: std::io::Write>(
75    ui: &mut Ui<W>,
76    plan: &remove::RemovalPlan,
77    yes: bool,
78    force: bool,
79) -> Result<bool> {
80    if force || yes {
81        return Ok(true);
82    }
83
84    let prompt = if plan.dependents.is_empty() {
85        format!("Are you sure you want to remove {}?", plan.package.name)
86    } else {
87        format!(
88            "Removal of {} may break other packages. Proceed anyway?",
89            plan.package.name
90        )
91    };
92
93    ui.confirm(&prompt, false)
94}