Skip to main content

stmo_cli/commands/
update.rs

1#![allow(clippy::missing_errors_doc)]
2
3use anyhow::Result;
4use std::fs;
5use std::path::{Path, PathBuf};
6
7fn cargo_home() -> Option<PathBuf> {
8    std::env::var("CARGO_HOME")
9        .ok()
10        .map(PathBuf::from)
11        .or_else(|| {
12            std::env::var("HOME")
13                .ok()
14                .map(|h| PathBuf::from(h).join(".cargo"))
15        })
16}
17
18fn dir_writable(dir: &Path) -> bool {
19    let test_path = dir.join(".stmo-cli-write-test");
20    match fs::write(&test_path, b"") {
21        Ok(()) => {
22            let _ = fs::remove_file(&test_path);
23            true
24        }
25        Err(e) => e.kind() != std::io::ErrorKind::PermissionDenied,
26    }
27}
28
29fn cargo_writable() -> bool {
30    let Some(home) = cargo_home() else {
31        return true;
32    };
33    if !home.exists() {
34        return true;
35    }
36    dir_writable(&home)
37}
38
39pub fn update() -> Result<()> {
40    if !cargo_writable() {
41        anyhow::bail!(
42            "Cannot update stmo-cli in the current environment \
43             (write access to ~/.cargo/ is restricted).\n\
44             Run this command outside the sandbox:\n  cargo binstall stmo-cli"
45        );
46    }
47
48    let status = std::process::Command::new("cargo")
49        .args(["binstall", "--no-confirm", "stmo-cli"])
50        .status();
51
52    match status {
53        Ok(s) if s.success() => {
54            println!("stmo-cli updated successfully.");
55            return Ok(());
56        }
57        _ => eprintln!("cargo binstall not available, falling back to cargo install"),
58    }
59
60    let status = std::process::Command::new("cargo")
61        .args(["install", "stmo-cli"])
62        .status()?;
63
64    if status.success() {
65        println!("stmo-cli updated successfully.");
66        Ok(())
67    } else {
68        anyhow::bail!("cargo install stmo-cli failed");
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use tempfile::TempDir;
76
77    #[test]
78    fn test_dir_writable_on_writable_dir() {
79        let temp = TempDir::new().unwrap();
80        assert!(dir_writable(temp.path()));
81    }
82
83    #[cfg(unix)]
84    #[test]
85    fn test_dir_writable_on_readonly_dir() {
86        use std::os::unix::fs::PermissionsExt;
87        let temp = TempDir::new().unwrap();
88        fs::set_permissions(temp.path(), fs::Permissions::from_mode(0o555)).unwrap();
89        let result = dir_writable(temp.path());
90        fs::set_permissions(temp.path(), fs::Permissions::from_mode(0o755)).unwrap();
91        assert!(!result);
92    }
93}