use crate::{Ecosystem, IsolationLevel, PackageInfo, PackageManagerConfig, PackageSpec, Result};
use async_trait::async_trait;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
#[async_trait]
pub trait VxPackageManager: Send + Sync {
fn name(&self) -> &str;
fn ecosystem(&self) -> Ecosystem;
fn description(&self) -> &str {
"A package manager"
}
async fn is_available(&self) -> Result<bool> {
Ok(which::which(self.name()).is_ok())
}
fn is_preferred_for_project(&self, project_path: &Path) -> bool {
let config_files = self.get_config_files();
config_files
.iter()
.any(|file| project_path.join(file).exists())
}
fn get_config_files(&self) -> Vec<&str> {
vec![]
}
async fn install_packages(&self, packages: &[PackageSpec], project_path: &Path) -> Result<()>;
async fn remove_packages(&self, packages: &[String], project_path: &Path) -> Result<()> {
self.run_command(&["remove"], packages, project_path).await
}
async fn update_packages(&self, packages: &[String], project_path: &Path) -> Result<()> {
if packages.is_empty() {
self.run_command(&["update"], &[], project_path).await
} else {
self.run_command(&["update"], packages, project_path).await
}
}
async fn list_packages(&self, project_path: &Path) -> Result<Vec<PackageInfo>> {
self.default_list_packages(project_path).await
}
async fn search_packages(&self, query: &str) -> Result<Vec<PackageInfo>> {
self.run_search_command(query).await
}
async fn run_command(
&self,
command: &[&str],
args: &[String],
project_path: &Path,
) -> Result<()> {
let mut cmd = std::process::Command::new(self.name());
cmd.args(command);
cmd.args(args);
cmd.current_dir(project_path);
let status = cmd
.status()
.map_err(|e| anyhow::anyhow!("Failed to run {} command: {}", self.name(), e))?;
if !status.success() {
return Err(anyhow::anyhow!(
"{} command failed with exit code: {:?}",
self.name(),
status.code()
));
}
Ok(())
}
async fn default_list_packages(&self, _project_path: &Path) -> Result<Vec<PackageInfo>> {
Ok(vec![])
}
async fn run_search_command(&self, _query: &str) -> Result<Vec<PackageInfo>> {
Ok(vec![])
}
fn get_install_command(&self) -> Vec<&str> {
vec!["install"]
}
fn get_add_command(&self) -> Vec<&str> {
vec!["add"]
}
fn get_remove_command(&self) -> Vec<&str> {
vec!["remove"]
}
fn get_update_command(&self) -> Vec<&str> {
vec!["update"]
}
fn get_list_command(&self) -> Vec<&str> {
vec!["list"]
}
fn get_search_command(&self) -> Vec<&str> {
vec!["search"]
}
fn get_config(&self) -> PackageManagerConfig {
PackageManagerConfig {
name: self.name().to_string(),
version: None,
executable_path: which::which(self.name()).ok(),
config_files: self.get_config_files().iter().map(PathBuf::from).collect(),
cache_directory: None,
supports_lockfiles: true,
supports_workspaces: false,
isolation_level: IsolationLevel::Project,
}
}
async fn run_command_with_code(
&self,
command: &[&str],
args: &[String],
project_path: &Path,
) -> Result<i32> {
let mut cmd = std::process::Command::new(self.name());
cmd.args(command);
cmd.args(args);
cmd.current_dir(project_path);
let status = cmd
.status()
.map_err(|e| anyhow::anyhow!("Failed to run {} command: {}", self.name(), e))?;
Ok(status.code().unwrap_or(-1))
}
fn metadata(&self) -> HashMap<String, String> {
HashMap::new()
}
}
pub struct StandardPackageManager {
name: String,
description: String,
ecosystem: Ecosystem,
config_files: Vec<String>,
install_command: Vec<String>,
remove_command: Vec<String>,
update_command: Vec<String>,
list_command: Vec<String>,
search_command: Vec<String>,
}
impl StandardPackageManager {
pub fn new(
name: impl Into<String>,
description: impl Into<String>,
ecosystem: Ecosystem,
) -> Self {
let name = name.into();
Self {
name: name.clone(),
description: description.into(),
ecosystem,
config_files: Vec::new(),
install_command: vec!["install".to_string()],
remove_command: vec!["remove".to_string()],
update_command: vec!["update".to_string()],
list_command: vec!["list".to_string()],
search_command: vec!["search".to_string()],
}
}
pub fn with_config_file(mut self, config_file: impl Into<String>) -> Self {
self.config_files.push(config_file.into());
self
}
pub fn with_install_command(mut self, command: Vec<String>) -> Self {
self.install_command = command;
self
}
pub fn with_remove_command(mut self, command: Vec<String>) -> Self {
self.remove_command = command;
self
}
pub fn with_update_command(mut self, command: Vec<String>) -> Self {
self.update_command = command;
self
}
}
#[async_trait]
impl VxPackageManager for StandardPackageManager {
fn name(&self) -> &str {
&self.name
}
fn ecosystem(&self) -> Ecosystem {
self.ecosystem
}
fn description(&self) -> &str {
&self.description
}
fn get_config_files(&self) -> Vec<&str> {
self.config_files.iter().map(|s| s.as_str()).collect()
}
async fn install_packages(&self, packages: &[PackageSpec], project_path: &Path) -> Result<()> {
let package_names: Vec<String> = packages
.iter()
.map(|p| {
if let Some(version) = &p.version {
format!("{}@{}", p.name, version)
} else {
p.name.clone()
}
})
.collect();
let command_strs: Vec<&str> = self.install_command.iter().map(|s| s.as_str()).collect();
self.run_command(&command_strs, &package_names, project_path)
.await
}
fn get_install_command(&self) -> Vec<&str> {
self.install_command.iter().map(|s| s.as_str()).collect()
}
fn get_remove_command(&self) -> Vec<&str> {
self.remove_command.iter().map(|s| s.as_str()).collect()
}
fn get_update_command(&self) -> Vec<&str> {
self.update_command.iter().map(|s| s.as_str()).collect()
}
fn get_list_command(&self) -> Vec<&str> {
self.list_command.iter().map(|s| s.as_str()).collect()
}
fn get_search_command(&self) -> Vec<&str> {
self.search_command.iter().map(|s| s.as_str()).collect()
}
}