use anyhow::{Context, Result};
use clap::Args as ClapArgs;
use console::style;
use std::collections::HashSet;
use stout_install::unlink_package;
use stout_state::{InstalledPackages, Paths};
use super::services;
#[derive(ClapArgs)]
pub struct Args {
#[arg(long, short = 'n')]
pub dry_run: bool,
}
pub async fn run(args: Args) -> Result<()> {
let paths = Paths::default();
let mut installed = InstalledPackages::load(&paths)?;
println!("\n{}...", style("Checking for unused dependencies").cyan());
let mut needed_deps: HashSet<String> = HashSet::new();
for name in installed.names() {
let pkg = installed
.get(name)
.with_context(|| format!("failed to get package '{}' from installed", name))?;
for dep in &pkg.dependencies {
needed_deps.insert(dep.clone());
}
}
let mut orphans: Vec<String> = Vec::new();
for name in installed.names() {
let pkg = installed
.get(name)
.with_context(|| format!("failed to get package '{}' from installed", name))?;
if pkg.requested {
continue;
}
if needed_deps.contains(name) {
continue;
}
orphans.push(name.to_string());
}
if orphans.is_empty() {
println!("\n{}", style("No unused dependencies to remove.").green());
return Ok(());
}
orphans.sort();
println!(
"\n{} {} unused dependencies:",
style("==>").blue().bold(),
orphans.len()
);
for name in &orphans {
let pkg = installed.get(name).with_context(|| {
format!(
"package '{}' was in orphan list but not found in installed",
name
)
})?;
println!(
" {} {} {}",
style("•").dim(),
name,
style(&pkg.version).dim()
);
}
if args.dry_run {
println!(
"\n{} Would remove {} package{}",
style("Dry run:").yellow(),
orphans.len(),
if orphans.len() == 1 { "" } else { "s" }
);
return Ok(());
}
println!();
let mut removed_count = 0;
for name in &orphans {
let pkg = installed
.get(name)
.with_context(|| format!("package '{}' was in orphan list but not found", name))?;
let install_path = paths.cellar.join(name).join(&pkg.version);
if install_path.exists() {
services::stop_package_service(name, &install_path);
if let Err(e) = unlink_package(&install_path, &paths.prefix) {
eprintln!(" {} Failed to unlink {}: {}", style("⚠").yellow(), name, e);
continue;
}
if let Err(e) = std::fs::remove_dir_all(&install_path) {
eprintln!(" {} Failed to remove {}: {}", style("⚠").yellow(), name, e);
continue;
}
let parent = paths.cellar.join(name);
if parent
.read_dir()
.map(|mut d| d.next().is_none())
.unwrap_or(false)
{
let _ = std::fs::remove_dir(&parent);
}
}
installed.remove(name);
removed_count += 1;
println!(" {} Removed {}", style("✓").green(), name);
}
installed.save(&paths)?;
println!(
"\n{} Removed {} unused dependency{}",
style("Autoremove").green().bold(),
removed_count,
if removed_count == 1 { "" } else { "ies" }
);
Ok(())
}