Skip to main content

vs_core/service/
uninstall.rs

1//! Services for uninstalling previously materialized runtimes.
2
3use std::fs;
4
5use crate::{App, CoreError, UninstallResult, UseScope};
6
7impl App {
8    /// Uninstalls a plugin version from the local cache.
9    ///
10    /// When the uninstalled version is currently active and other versions
11    /// remain, auto-switches to the first remaining version (global scope).
12    /// When no versions remain, removes the entire plugin cache directory.
13    pub fn uninstall_plugin_version(
14        &self,
15        plugin_name: &str,
16        version: &str,
17    ) -> Result<UninstallResult, CoreError> {
18        let was_current = self
19            .current_tool(plugin_name)?
20            .map(|current| current.version == version)
21            .unwrap_or(false);
22
23        // Call PreUninstall hook when the plugin and receipt are available.
24        if let Ok(Some(runtime)) = self.installer.read_receipt(plugin_name, version) {
25            if let Ok(entry) = self.resolve_registry_entry(plugin_name) {
26                if let Ok(plugin) = self.load_plugin(&entry) {
27                    if let Err(error) = plugin.pre_uninstall(&runtime) {
28                        eprintln!("PreUninstall hook failed for {plugin_name}@{version}: {error}");
29                    }
30                }
31            }
32        }
33
34        let removed = self.installer.uninstall(plugin_name, version)?;
35        if !removed {
36            return Ok(UninstallResult {
37                removed: false,
38                auto_switched: None,
39            });
40        }
41
42        let remaining = self.installer.installed_versions(plugin_name)?;
43
44        if remaining.is_empty() {
45            let plugin_cache = self.runtime_root().join(plugin_name);
46            if plugin_cache.exists() {
47                let _ = fs::remove_dir_all(plugin_cache);
48            }
49            return Ok(UninstallResult {
50                removed: true,
51                auto_switched: None,
52            });
53        }
54
55        if was_current {
56            let first = &remaining[0];
57            let _ = self.use_tool(plugin_name, first, UseScope::Global, false);
58            return Ok(UninstallResult {
59                removed: true,
60                auto_switched: Some(first.clone()),
61            });
62        }
63
64        Ok(UninstallResult {
65            removed: true,
66            auto_switched: None,
67        })
68    }
69}