Skip to main content

vs_core/service/
install.rs

1//! Services for planning and performing plugin installations.
2
3use crate::{App, CoreError, InstalledVersion};
4
5impl App {
6    /// Installs a plugin version, choosing the first available version when omitted.
7    pub fn install_plugin_version(
8        &self,
9        plugin_name: &str,
10        version: Option<&str>,
11        progress: Option<&crate::ProgressFn<'_>>,
12    ) -> Result<InstalledVersion, CoreError> {
13        let entry = self.resolve_registry_entry(plugin_name)?;
14        let plugin = self.load_plugin(&entry)?;
15        let available_versions = self.cached_available_versions(plugin_name, &[])?;
16        let selected_version = version
17            .map(str::to_string)
18            .or_else(|| {
19                available_versions
20                    .first()
21                    .map(|candidate| candidate.version.clone())
22            })
23            .ok_or_else(|| {
24                CoreError::Unsupported(format!(
25                    "plugin {plugin_name} does not expose installable versions"
26                ))
27            })?;
28
29        let plan = plugin.install_plan(&selected_version)?;
30        let runtime = self.installer.install(&plan, progress)?;
31
32        // Run PostInstall inside a logical transaction: if it fails, roll back
33        // the freshly committed install directory so we never leave a half-
34        // configured runtime on disk.
35        if let Err(error) = plugin.post_install(&runtime) {
36            let _ = std::fs::remove_dir_all(&runtime.root_dir);
37            return Err(error.into());
38        }
39
40        Ok(InstalledVersion {
41            plugin: plugin_name.to_string(),
42            version: runtime.version,
43            install_dir: runtime.root_dir,
44        })
45    }
46
47    /// Returns the version requested by the project config for a plugin, when present.
48    pub fn project_tool_version(&self, plugin_name: &str) -> Result<Option<String>, CoreError> {
49        Ok(self
50            .resolve_project_tool_version_internal(plugin_name)?
51            .map(|resolved| resolved.version))
52    }
53
54    /// Lists configured tools that should be installed for the current context.
55    pub fn configured_tools_for_install(&self) -> Result<Vec<(String, String)>, CoreError> {
56        Ok(self
57            .collect_current_tools()?
58            .into_iter()
59            .map(|tool| (tool.plugin, tool.version))
60            .collect())
61    }
62}