use anyhow::{bail, Context, Result};
use colored::Colorize;
use flk::flake::parsers::overlays::remove_pinned_package_with_cleanup;
use std::fs;
use std::path::Path;
use flk::flake::parsers::{packages::parse_packages_section, utils::resolve_profile};
pub fn run_remove(package: &str, target_profile: Option<String>) -> Result<()> {
let profile = resolve_profile(target_profile)?;
let flake_path = Path::new(".flk/profiles/").join(format!("{}.nix", profile));
if package.trim().is_empty() {
bail!("Package name cannot be empty!");
}
let flake_content = fs::read_to_string(&flake_path).with_context(|| {
format!(
"Failed to read profile file at '{}'. Have you run 'flk init'?",
flake_path.display()
)
})?;
let section = parse_packages_section(&flake_content)?;
if !section.package_exists(package) {
bail!(
"Package '{}' is not present in the packages declaration",
package
);
}
if section
.entries
.iter()
.any(|e| e.name == package && e.version.is_some())
{
let pins_path = ".flk/pins.nix";
let pins_content = fs::read_to_string(pins_path).context("Failed to read pins.nix file")?;
let updated_pins_content = remove_pinned_package_with_cleanup(&pins_content, package)?;
fs::write(pins_path, updated_pins_content).context("Failed to write pins.nix")?;
}
let updated_content = section.remove_package(&flake_content, package)?;
fs::write(flake_path, updated_content).context("Failed to write flake.nix")?;
println!(
"{} Package '{}' removed successfully!",
"✓".green().bold(),
package
);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::commands::cwd_test_guard;
use tempfile::TempDir;
fn setup_profile(dir: &TempDir, rust_packages: &str, pins: Option<&str>) {
let flk = dir.path().join(".flk");
std::fs::create_dir_all(flk.join("profiles")).unwrap();
std::fs::write(flk.join("default.nix"), "{ defaultShell = \"rust\"; }\n").unwrap();
std::fs::write(
flk.join("profiles").join("rust.nix"),
format!(
"{{pkgs, ...}}: {{\n packages = [\n{} ];\n}}\n",
rust_packages
),
)
.unwrap();
if let Some(pins_content) = pins {
std::fs::write(flk.join("pins.nix"), pins_content).unwrap();
}
std::env::set_current_dir(dir.path()).unwrap();
}
#[test]
fn rejects_empty_package_name() {
let _guard = cwd_test_guard();
let tmp = TempDir::new().unwrap();
setup_profile(&tmp, " pkgs.ripgrep\n", None);
let err = run_remove(" ", Some("rust".into())).unwrap_err();
assert!(err.to_string().contains("empty"), "got: {err}");
}
#[test]
fn errors_when_package_not_in_profile() {
let _guard = cwd_test_guard();
let tmp = TempDir::new().unwrap();
setup_profile(&tmp, " pkgs.ripgrep\n", None);
let err = run_remove("does-not-exist", Some("rust".into())).unwrap_err();
assert!(err.to_string().contains("not present"), "got: {err}");
}
#[test]
fn removes_unversioned_package_from_profile() {
let _guard = cwd_test_guard();
let tmp = TempDir::new().unwrap();
setup_profile(&tmp, " pkgs.ripgrep\n pkgs.fd\n", None);
run_remove("ripgrep", Some("rust".into())).unwrap();
let after = std::fs::read_to_string(tmp.path().join(".flk/profiles/rust.nix")).unwrap();
assert!(
!after.contains("ripgrep"),
"ripgrep still present:\n{after}"
);
assert!(after.contains("pkgs.fd"));
}
}