Skip to main content

atomcode_core/uninstall/
paths.rs

1//! Install-location detection mirroring scripts/install.sh and install.ps1.
2
3// `Path` only appears in `unix_rc_paths_for_home`, which is gated to
4// `cfg(unix)` for the production path and `cfg(all(not(unix), test))`
5// for the cross-platform test stub. A Windows non-test build sees
6// neither, so the import would be flagged unused.
7use std::path::PathBuf;
8#[cfg(any(unix, test))]
9use std::path::Path;
10
11/// Return the atomcode data root (`$ATOMCODE_HOME` when set, or
12/// `~/.atomcode/` by default). Routes through [`crate::config::Config::config_dir`]
13/// so install/setup/skill/plugin/uninstall all agree on a single root —
14/// previously this module looked at a separate `ATOMCODE_HOME_OVERRIDE`
15/// variable, which let users customise their data dir but then lose track
16/// of it at uninstall time. One variable, one semantics.
17pub fn atomcode_dir() -> PathBuf {
18    crate::config::Config::config_dir()
19}
20
21/// Filenames inside `$ATOMCODE_HOME/` that the uninstaller knows about, grouped.
22pub struct UninstallManifest {
23    pub credential_files: &'static [&'static str],
24    pub state_files: &'static [&'static str],
25    pub state_dirs: &'static [&'static str],
26    pub state_prefixes: &'static [&'static str],
27}
28
29pub fn uninstall_manifest() -> UninstallManifest {
30    UninstallManifest {
31        credential_files: &["auth.toml", "mcp.json", "config.toml", "ATOMCODE.md"],
32        state_files: &[
33            "history",
34            "input_history.txt",
35            "recent_dirs.txt",
36            "codingplan_sync.json",
37            "device_id",
38        ],
39        state_dirs: &["staged", "telemetry", "plugins", "commands", "skills"],
40        state_prefixes: &["notice."],
41    }
42}
43
44#[cfg(unix)]
45pub struct UnixRcPaths {
46    pub zshrc: PathBuf,
47    pub bashrc: PathBuf,
48}
49
50#[cfg(unix)]
51pub fn unix_rc_paths() -> UnixRcPaths {
52    let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
53    unix_rc_paths_for_home(&home)
54}
55
56#[cfg(unix)]
57pub fn unix_rc_paths_for_home(home: &Path) -> UnixRcPaths {
58    UnixRcPaths {
59        zshrc: home.join(".zshrc"),
60        bashrc: home.join(".bashrc"),
61    }
62}
63
64// Test-only counterpart so the test compiles cross-platform.
65#[cfg(all(not(unix), test))]
66pub struct UnixRcPaths {
67    pub zshrc: PathBuf,
68    pub bashrc: PathBuf,
69}
70#[cfg(all(not(unix), test))]
71pub fn unix_rc_paths_for_home(home: &Path) -> UnixRcPaths {
72    UnixRcPaths {
73        zshrc: home.join(".zshrc"),
74        bashrc: home.join(".bashrc"),
75    }
76}
77
78/// Default Windows install-dir candidates (matches install.ps1).
79#[cfg(windows)]
80pub fn windows_install_dir_candidates() -> Vec<PathBuf> {
81    let mut out = Vec::new();
82    if let Some(p) = std::env::var_os("ATOMCODE_PREFIX") {
83        out.push(PathBuf::from(p));
84    }
85    if let Some(p) = std::env::var_os("LOCALAPPDATA") {
86        out.push(PathBuf::from(p).join("AtomCode"));
87    }
88    out
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use serial_test::serial;
95
96    #[test]
97    #[serial]
98    fn atomcode_dir_under_home() {
99        // Note: with no $HOME this returns "./.atomcode" — test passes vacuously then.
100        let p = atomcode_dir();
101        assert!(p.ends_with(".atomcode"), "got {:?}", p);
102    }
103
104    #[test]
105    #[serial]
106    fn atomcode_home_env_wins() {
107        // Under unified semantics, ATOMCODE_HOME IS the data root.
108        // The legacy ATOMCODE_HOME_OVERRIDE variable is gone.
109        std::env::set_var("ATOMCODE_HOME", "/tmp/override");
110        assert_eq!(atomcode_dir(), std::path::PathBuf::from("/tmp/override"));
111        std::env::remove_var("ATOMCODE_HOME");
112    }
113
114    #[test]
115    fn manifest_groups_credentials_correctly() {
116        let m = uninstall_manifest();
117        for f in ["auth.toml", "mcp.json", "config.toml", "ATOMCODE.md"] {
118            assert!(m.credential_files.contains(&f), "missing {f}");
119        }
120    }
121
122    #[test]
123    fn manifest_groups_state_correctly() {
124        let m = uninstall_manifest();
125        for f in [
126            "history",
127            "input_history.txt",
128            "recent_dirs.txt",
129            "codingplan_sync.json",
130            "device_id",
131        ] {
132            assert!(m.state_files.contains(&f), "missing {f}");
133        }
134        for d in ["staged", "telemetry", "plugins", "commands", "skills"] {
135            assert!(m.state_dirs.contains(&d), "missing {d}");
136        }
137    }
138
139    #[test]
140    fn rc_files_includes_zshrc_and_bashrc() {
141        let rc = unix_rc_paths_for_home(std::path::Path::new("/Users/test"));
142        assert_eq!(rc.zshrc, std::path::Path::new("/Users/test/.zshrc"));
143        assert_eq!(rc.bashrc, std::path::Path::new("/Users/test/.bashrc"));
144    }
145}