pub use super::manage_error::ProtoManageError;
use crate::cfg;
use crate::config::{PinLocation, ProtoConfig};
use crate::flow::install::{InstallOptions, Installer, ProtoInstallError};
use crate::flow::link::Linker;
use crate::flow::locate::Locator;
use crate::flow::lock::Locker;
use crate::flow::resolve::Resolver;
use crate::layout::BinManager;
use crate::lockfile::LockRecord;
use crate::tool::Tool;
use crate::tool_manifest::ToolManifestVersion;
use crate::tool_spec::ToolSpec;
use proto_pdk_api::{PluginFunction, SyncManifestInput, SyncManifestOutput};
use starbase_utils::fs;
use std::collections::{BTreeMap, BTreeSet};
use tracing::{debug, instrument};
pub struct Manager<'tool> {
tool: &'tool mut Tool,
}
impl<'tool> Manager<'tool> {
pub fn new(tool: &'tool mut Tool) -> Self {
Self { tool }
}
#[instrument(skip(self, options))]
pub async fn install(
&mut self,
spec: &mut ToolSpec,
options: InstallOptions,
) -> Result<Option<LockRecord>, ProtoManageError> {
let version = Resolver::resolve(self.tool, spec, false).await?;
let record = match Installer::new(self.tool, spec).install(options).await? {
Some(mut record) => {
record.version = Some(version.clone());
record.spec = Some(spec.req.clone());
record
}
None => {
self.post_install(spec, false).await?;
return Ok(Locker::new(self.tool)
.get_resolved_locked_record(spec)
.cloned());
}
};
if spec.update_lockfile {
Locker::new(self.tool).insert_record_into_lockfile(&record)?;
}
self.tool.inventory.manifest.add_version(
&version,
ToolManifestVersion {
lock: Some(record.for_manifest()),
suffix: self.tool.inventory.config.version_suffix.clone(),
..Default::default()
},
);
ProtoConfig::update_document(self.tool.proto.get_config_dir(PinLocation::Global), |doc| {
if !doc.contains_key(self.tool.get_id()) {
doc[self.tool.context.as_str()] = cfg::value(
ToolSpec::new(
self.tool
.metadata
.default_version
.clone()
.unwrap_or_else(|| version.to_unresolved_spec()),
)
.to_string(),
);
}
})?;
self.post_install(spec, true).await?;
Ok(Some(record))
}
async fn post_install(&self, spec: &mut ToolSpec, force: bool) -> Result<(), ProtoManageError> {
Linker::link(self.tool, spec, force).await?;
self.cleanup().await?;
Ok(())
}
#[instrument(skip_all)]
pub async fn uninstall(&mut self, spec: &mut ToolSpec) -> Result<bool, ProtoManageError> {
self.cleanup().await?;
let version = Resolver::resolve(self.tool, spec, false).await?;
if !Installer::new(self.tool, spec).uninstall().await? {
return Ok(false);
}
if spec.update_lockfile {
Locker::new(self.tool).remove_version_from_lockfile(&version)?;
}
let mut bin_manager = BinManager::from_manifest(&self.tool.inventory.manifest);
let locator = Locator::new(self.tool, spec);
let proto = &self.tool.proto;
if self.tool.inventory.manifest.installed_versions.is_empty()
|| self.tool.inventory.manifest.is_only_version(&version)
{
for bin in locator.locate_bins_with_manager(bin_manager, None).await? {
proto.store.unlink_bin(&bin.path)?;
}
for shim in locator.locate_shims().await? {
proto.store.remove_shim(&shim.path)?;
}
}
else if bin_manager.remove_version(&version) {
for bin in locator
.locate_bins_with_manager(bin_manager, Some(&version))
.await?
{
proto.store.unlink_bin(&bin.path)?;
}
}
ProtoConfig::update_document(proto.get_config_dir(PinLocation::Global), |doc| {
if doc
.get(self.tool.context.as_str())
.and_then(|item| item.as_str())
.is_some_and(|v| version == v)
{
debug!("Unpinning global version");
doc.as_table_mut().remove(self.tool.context.as_str());
}
})?;
self.tool.inventory.manifest.remove_version(&version);
Ok(true)
}
#[instrument(skip_all)]
pub async fn cleanup(&self) -> Result<(), ProtoManageError> {
debug!(
tool = self.tool.context.as_str(),
"Cleaning up temporary files and downloads"
);
fs::remove_dir_all(self.tool.get_temp_dir()).map_err(|error| {
ProtoManageError::Install(Box::new(ProtoInstallError::Fs(Box::new(error))))
})?;
Ok(())
}
#[instrument(skip_all)]
pub async fn sync_manifest(self) -> Result<(), ProtoManageError> {
if !self
.tool
.plugin
.has_func(PluginFunction::SyncManifest)
.await
{
self.tool.inventory.manifest.save()?;
return Ok(());
}
debug!(
tool = self.tool.context.as_str(),
"Syncing manifest with changes"
);
let output: SyncManifestOutput = self
.tool
.plugin
.call_func_with(
PluginFunction::SyncManifest,
SyncManifestInput {
context: self.tool.create_plugin_unresolved_context(),
},
)
.await?;
if !output.skip_sync
&& let Some(versions) = output.versions
{
let mut entries = BTreeMap::default();
let mut installed = BTreeSet::default();
for key in versions {
let value = self
.tool
.inventory
.manifest
.versions
.get(&key)
.cloned()
.unwrap_or_default();
installed.insert(key.clone());
entries.insert(key, value);
}
self.tool.inventory.manifest.versions = entries;
self.tool.inventory.manifest.installed_versions = installed;
}
self.tool.inventory.manifest.save()?;
Ok(())
}
}