Skip to main content

envvault/cli/commands/
update.rs

1//! `envvault update` — self-update to the latest version.
2//!
3//! Detects how envvault was installed and runs the appropriate update command:
4//! - cargo  → `cargo install envvault-cli --force`
5//! - brew   → `brew upgrade envvault`
6//! - script → re-runs the curl installer
7
8use std::process::Command;
9
10use console::style;
11
12use crate::cli::output;
13use crate::errors::{EnvVaultError, Result};
14
15/// How envvault was installed.
16#[derive(Debug, PartialEq)]
17pub enum InstallMethod {
18    Cargo,
19    Homebrew,
20    Script,
21}
22
23/// Detect how envvault was installed by inspecting the binary path.
24pub fn detect_install_method() -> InstallMethod {
25    let exe = match std::env::current_exe() {
26        Ok(p) => p.to_string_lossy().to_string(),
27        Err(_) => return InstallMethod::Script,
28    };
29
30    if exe.contains(".cargo/bin") {
31        InstallMethod::Cargo
32    } else if exe.contains("Cellar") || exe.contains("homebrew") || exe.contains("linuxbrew") {
33        InstallMethod::Homebrew
34    } else {
35        InstallMethod::Script
36    }
37}
38
39/// Execute the `update` command.
40pub fn execute() -> Result<()> {
41    let current = env!("CARGO_PKG_VERSION");
42
43    // Check for updates first.
44    let latest = crate::version_check::check_latest_version(current);
45    match &latest {
46        Some(ver) => {
47            output::info(&format!(
48                "Update available: {} → {}",
49                style(current).red(),
50                style(ver).green().bold()
51            ));
52        }
53        None => {
54            output::success(&format!("Already on the latest version ({current})."));
55            return Ok(());
56        }
57    }
58
59    let method = detect_install_method();
60
61    match method {
62        InstallMethod::Cargo => run_cargo_update(),
63        InstallMethod::Homebrew => run_brew_update(),
64        InstallMethod::Script => run_script_update(),
65    }
66}
67
68fn run_cargo_update() -> Result<()> {
69    output::info("Detected cargo install. Running: cargo install envvault-cli --force");
70    println!();
71
72    let status = Command::new("cargo")
73        .args(["install", "envvault-cli", "--force"])
74        .status()
75        .map_err(|e| {
76            EnvVaultError::CommandFailed(format!(
77                "failed to run cargo: {e}. Run manually: cargo install envvault-cli --force"
78            ))
79        })?;
80
81    if status.success() {
82        println!();
83        output::success("Update complete!");
84        Ok(())
85    } else {
86        Err(EnvVaultError::CommandFailed(
87            "cargo install failed. Run manually: cargo install envvault-cli --force".into(),
88        ))
89    }
90}
91
92fn run_brew_update() -> Result<()> {
93    output::info("Detected Homebrew install. Running: brew upgrade envvault");
94    println!();
95
96    let status = Command::new("brew")
97        .args(["upgrade", "envvault"])
98        .status()
99        .map_err(|e| {
100            EnvVaultError::CommandFailed(format!(
101                "failed to run brew: {e}. Run manually: brew upgrade envvault"
102            ))
103        })?;
104
105    if status.success() {
106        println!();
107        output::success("Update complete!");
108        Ok(())
109    } else {
110        Err(EnvVaultError::CommandFailed(
111            "brew upgrade failed. Run manually: brew upgrade envvault".into(),
112        ))
113    }
114}
115
116fn run_script_update() -> Result<()> {
117    output::info("Running install script to update...");
118    println!();
119
120    let status = Command::new("sh")
121        .args([
122            "-c",
123            "curl -fsSL https://raw.githubusercontent.com/whynaidu/envvault/main/install.sh | sh",
124        ])
125        .status()
126        .map_err(|e| {
127            EnvVaultError::CommandFailed(format!(
128                "failed to run install script: {e}. Run manually:\n  \
129                 curl -fsSL https://raw.githubusercontent.com/whynaidu/envvault/main/install.sh | sh"
130            ))
131        })?;
132
133    if status.success() {
134        println!();
135        output::success("Update complete!");
136        Ok(())
137    } else {
138        Err(EnvVaultError::CommandFailed(
139            "install script failed. Run manually:\n  \
140             curl -fsSL https://raw.githubusercontent.com/whynaidu/envvault/main/install.sh | sh"
141                .into(),
142        ))
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[test]
151    fn detect_method_from_cargo_path() {
152        // We're running under cargo test, so the binary should be in a cargo-related path.
153        // Just verify the function doesn't panic.
154        let method = detect_install_method();
155        assert!(
156            method == InstallMethod::Cargo
157                || method == InstallMethod::Script
158                || method == InstallMethod::Homebrew
159        );
160    }
161}