mi6_cli/commands/
uninstall.rs1use std::path::PathBuf;
9use std::process::Command;
10
11use anyhow::{Context, Result};
12
13use mi6_core::{
14 Config, FrameworkAdapter, FrameworkResolutionMode, all_adapters, resolve_frameworks,
15};
16
17use super::disable::disable_framework_silent;
18use super::upgrade::InstallMethod;
19use crate::display::{StderrColors, confirm};
20
21pub struct UninstallOptions {
23 pub confirm: bool,
25 pub keep_data: bool,
27 pub dry_run: bool,
29}
30
31pub fn run_uninstall(options: UninstallOptions) -> Result<()> {
33 let colors = StderrColors::new();
34 let method = InstallMethod::detect()?;
35
36 eprintln!("{}mi6 uninstall{}", colors.bold, colors.reset);
37 eprintln!("Detected installation method: {}", method.name());
38 eprintln!();
39
40 let enabled_frameworks = find_enabled_frameworks();
42 let mi6_dir = get_mi6_dir();
43 let data_exists = mi6_dir.as_ref().is_some_and(|p| p.exists());
44
45 if enabled_frameworks.is_empty() {
47 eprintln!(
48 "{}Step 1:{} No enabled frameworks found",
49 colors.cyan, colors.reset
50 );
51 eprintln!();
52 } else {
53 eprintln!("{}Step 1:{} Disable mi6 hooks", colors.cyan, colors.reset);
54 for adapter in &enabled_frameworks {
55 eprintln!(" - {}", adapter.display_name());
56 }
57 eprintln!();
58 }
59
60 if !options.keep_data && data_exists {
62 eprintln!("{}Step 2:{} Delete mi6 data", colors.cyan, colors.reset);
63 if let Some(ref dir) = mi6_dir
64 && dir.exists()
65 {
66 eprintln!(" - {} (database, config, themes)", dir.display());
67 }
68 eprintln!();
69 } else if options.keep_data {
70 eprintln!(
71 "{}Step 2:{} Keeping mi6 data (--keep-data)",
72 colors.cyan, colors.reset
73 );
74 eprintln!();
75 } else {
76 eprintln!(
77 "{}Step 2:{} No mi6 data to delete",
78 colors.cyan, colors.reset
79 );
80 eprintln!();
81 }
82
83 eprintln!(
85 "{}Step 3:{} Uninstall mi6 binary",
86 colors.cyan, colors.reset
87 );
88 match method {
89 InstallMethod::Homebrew => eprintln!(" brew uninstall mi6"),
90 InstallMethod::Cargo => eprintln!(" cargo uninstall mi6"),
91 InstallMethod::Standalone => {
92 if let Ok(exe_path) = std::env::current_exe() {
93 eprintln!(" rm {}", exe_path.display());
94 } else {
95 eprintln!(" (remove mi6 binary manually)");
96 }
97 }
98 }
99 eprintln!();
100
101 if options.dry_run {
103 eprintln!("{}Dry run:{} No changes made.", colors.yellow, colors.reset);
104 return Ok(());
105 }
106
107 if !options.confirm {
109 eprintln!(
110 "{}This will permanently uninstall mi6.{}",
111 colors.yellow, colors.reset
112 );
113
114 if !options.keep_data && data_exists {
116 eprintln!(
117 "{}All mi6 data (sessions, events, config) will be deleted.{}",
118 colors.yellow, colors.reset
119 );
120 }
121
122 if !confirm("Proceed with uninstall?")? {
123 eprintln!("Aborted.");
124 return Ok(());
125 }
126 eprintln!();
127 }
128
129 if !enabled_frameworks.is_empty() {
131 eprint!("{}Disabling{} mi6 hooks... ", colors.green, colors.reset);
132 for adapter in &enabled_frameworks {
133 disable_framework_silent(*adapter)?;
134 }
135 eprintln!("{}done{}", colors.green, colors.reset);
136 }
137
138 if !options.keep_data
140 && let Some(ref dir) = mi6_dir
141 && dir.exists()
142 {
143 eprint!(
144 "{}Deleting{} {}... ",
145 colors.green,
146 colors.reset,
147 dir.display()
148 );
149 std::fs::remove_dir_all(dir)
150 .with_context(|| format!("failed to delete {}", dir.display()))?;
151 eprintln!("{}done{}", colors.green, colors.reset);
152 }
153
154 eprint!(
156 "{}Uninstalling{} mi6 binary... ",
157 colors.green, colors.reset
158 );
159 match method {
160 InstallMethod::Homebrew => uninstall_homebrew()?,
161 InstallMethod::Cargo => uninstall_cargo()?,
162 InstallMethod::Standalone => uninstall_standalone()?,
163 }
164 eprintln!("{}done{}", colors.green, colors.reset);
165
166 eprintln!();
167 eprintln!("{}mi6 has been uninstalled.{}", colors.bold, colors.reset);
168
169 Ok(())
170}
171
172fn find_enabled_frameworks() -> Vec<&'static dyn FrameworkAdapter> {
174 let mode = FrameworkResolutionMode::Active {
176 local: false,
177 settings_local: false,
178 };
179
180 match resolve_frameworks(&[], Some(mode)) {
182 Ok(adapters) => adapters,
183 Err(_) => {
184 all_adapters()
186 .into_iter()
187 .filter(|a| a.has_mi6_hooks(false, false))
188 .collect()
189 }
190 }
191}
192
193fn get_mi6_dir() -> Option<PathBuf> {
195 Config::mi6_dir().ok()
196}
197
198fn uninstall_homebrew() -> Result<()> {
200 let status = Command::new("brew")
201 .args(["uninstall", "mi6"])
202 .status()
203 .context("failed to run brew uninstall")?;
204
205 if !status.success() {
206 anyhow::bail!("brew uninstall failed with exit code: {:?}", status.code());
207 }
208
209 Ok(())
210}
211
212fn uninstall_cargo() -> Result<()> {
214 let status = Command::new("cargo")
215 .args(["uninstall", "mi6"])
216 .status()
217 .context("failed to run cargo uninstall")?;
218
219 if !status.success() {
220 anyhow::bail!("cargo uninstall failed with exit code: {:?}", status.code());
221 }
222
223 Ok(())
224}
225
226fn uninstall_standalone() -> Result<()> {
228 let exe_path = std::env::current_exe().context("failed to get current executable path")?;
229
230 std::fs::remove_file(&exe_path)
233 .with_context(|| format!("failed to delete {}", exe_path.display()))?;
234
235 Ok(())
236}