mi6_cli/commands/
upgrade.rs1use std::process::Command;
9
10use anyhow::{Context, Result};
11
12use crate::display::confirm_stdout;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum InstallMethod {
17 Homebrew,
19 Cargo,
21 Standalone,
23}
24
25impl InstallMethod {
26 pub fn detect() -> Result<Self> {
28 let exe_path = std::env::current_exe().context("failed to get current executable path")?;
29 let path_str = exe_path.to_string_lossy();
30
31 if path_str.contains("/Cellar/") || path_str.contains("/homebrew/") {
32 Ok(Self::Homebrew)
33 } else if path_str.contains("/.cargo/bin/") {
34 Ok(Self::Cargo)
35 } else {
36 Ok(Self::Standalone)
37 }
38 }
39
40 pub const fn name(self) -> &'static str {
42 match self {
43 Self::Homebrew => "Homebrew",
44 Self::Cargo => "Cargo",
45 Self::Standalone => "Standalone",
46 }
47 }
48}
49
50pub struct UpgradeOptions {
52 pub version: Option<String>,
54 pub yes: bool,
56 pub dry_run: bool,
58}
59
60pub fn run_upgrade(options: UpgradeOptions) -> Result<()> {
62 let current_version = env!("CARGO_PKG_VERSION");
63 let method = InstallMethod::detect()?;
64
65 println!("mi6 v{}", current_version);
66 println!("Detected: {} install", method.name());
67 println!();
68
69 if options.version.is_some() && method != InstallMethod::Cargo {
71 println!(
72 "Warning: --version is only supported for Cargo installs (ignored for {})",
73 method.name()
74 );
75 println!();
76 }
77
78 if options.dry_run {
80 return check_for_updates(current_version, method);
81 }
82
83 match method {
84 InstallMethod::Homebrew => upgrade_homebrew(options.yes)?,
85 InstallMethod::Cargo => upgrade_cargo(options.version.as_deref(), options.yes)?,
86 InstallMethod::Standalone => upgrade_standalone(current_version, options.yes)?,
87 }
88
89 println!();
90 println!("Done! Run `mi6 --version` to verify.");
91
92 Ok(())
93}
94
95fn check_for_updates(current_version: &str, method: InstallMethod) -> Result<()> {
97 match method {
98 InstallMethod::Standalone => {
99 println!("Checking GitHub for updates...");
100 println!();
101
102 let update = self_update::backends::github::Update::configure()
103 .repo_owner("paradigmxyz")
104 .repo_name("mi6")
105 .bin_name("mi6")
106 .current_version(current_version)
107 .build()
108 .context("failed to configure self-updater")?;
109
110 let latest = update
111 .get_latest_release()
112 .context("failed to check for updates")?;
113
114 let latest_version = latest.version.trim_start_matches('v');
115
116 if latest_version == current_version {
117 println!("Already at the latest version (v{}).", current_version);
118 } else {
119 println!(
120 "Update available: v{} -> v{}",
121 current_version, latest_version
122 );
123 println!();
124 println!("Run `mi6 upgrade` to install.");
125 }
126 }
127 InstallMethod::Homebrew => {
128 println!("To check for Homebrew updates, run:");
129 println!(" brew outdated mi6");
130 }
131 InstallMethod::Cargo => {
132 println!("To check for Cargo updates, run:");
133 println!(" cargo search mi6");
134 }
135 }
136
137 Ok(())
138}
139
140fn upgrade_homebrew(skip_confirm: bool) -> Result<()> {
141 if !skip_confirm {
142 println!("This will run: brew upgrade mi6");
143 if !confirm_stdout("Proceed?")? {
144 println!("Aborted.");
145 return Ok(());
146 }
147 println!();
148 }
149
150 println!("Running: brew upgrade mi6");
151 println!();
152
153 let status = Command::new("brew")
154 .args(["upgrade", "mi6"])
155 .status()
156 .context("failed to run brew upgrade")?;
157
158 if !status.success() {
159 println!();
161 println!("Note: brew upgrade exited with non-zero status.");
162 println!("This usually means mi6 is already at the latest version.");
163 println!("If not, try: brew reinstall mi6");
164 }
165
166 Ok(())
167}
168
169fn upgrade_cargo(version: Option<&str>, skip_confirm: bool) -> Result<()> {
170 let mut args = vec!["install", "mi6"];
171 if let Some(v) = version {
172 args.extend(["--version", v]);
173 }
174
175 if !skip_confirm {
176 println!("This will run: cargo {}", args.join(" "));
177 if !confirm_stdout("Proceed?")? {
178 println!("Aborted.");
179 return Ok(());
180 }
181 println!();
182 }
183
184 println!("Running: cargo {}", args.join(" "));
185 println!();
186
187 let status = Command::new("cargo")
188 .args(&args)
189 .status()
190 .context("failed to run cargo install")?;
191
192 if !status.success() {
193 anyhow::bail!("cargo install failed with exit code: {:?}", status.code());
194 }
195
196 Ok(())
197}
198
199fn upgrade_standalone(current_version: &str, skip_confirm: bool) -> Result<()> {
200 println!("Checking GitHub for updates...");
201 println!();
202
203 let update = self_update::backends::github::Update::configure()
204 .repo_owner("paradigmxyz")
205 .repo_name("mi6")
206 .bin_name("mi6")
207 .current_version(current_version)
208 .build()
209 .context("failed to configure self-updater")?;
210
211 let latest = update
213 .get_latest_release()
214 .context("failed to check for updates")?;
215
216 let latest_version = latest.version.trim_start_matches('v');
217
218 if latest_version == current_version {
219 println!("Already at the latest version (v{}).", current_version);
220 return Ok(());
221 }
222
223 println!(
224 "New version available: v{} -> v{}",
225 current_version, latest_version
226 );
227
228 if !skip_confirm && !confirm_stdout("Proceed with update?")? {
229 println!("Aborted.");
230 return Ok(());
231 }
232 println!();
233
234 println!("Downloading and installing v{}...", latest_version);
235
236 let status = self_update::backends::github::Update::configure()
238 .repo_owner("paradigmxyz")
239 .repo_name("mi6")
240 .bin_name("mi6")
241 .current_version(current_version)
242 .show_download_progress(true)
243 .build()
244 .context("failed to configure self-updater")?
245 .update()
246 .context("failed to perform update")?;
247
248 println!();
249 println!("Updated to v{}!", status.version());
250
251 Ok(())
252}