Skip to main content

algocline_app/service/pkg/
remove.rs

1//! `pkg_remove` — remove a package declaration from `alc.toml` and `alc.lock`.
2
3use super::super::alc_toml::{load_alc_toml_document, remove_package_entry, save_alc_toml};
4use super::super::lockfile::{load_lockfile, lockfile_path, save_lockfile};
5use super::super::project::resolve_project_root;
6use super::super::AppService;
7
8impl AppService {
9    /// Remove a package declaration from `alc.toml` and `alc.lock`.
10    ///
11    /// Requires an `alc.toml` to be found via `project_root` or ancestor walk.
12    /// Physical files in `~/.algocline/packages/` are **not** deleted.
13    ///
14    /// - `name`: package name to remove.
15    /// - `project_root`: optional explicit project root.
16    /// - `version`: optional version constraint (when specified, only removes
17    ///   the matching `alc.lock` entry; omit to remove any version).
18    pub async fn pkg_remove(
19        &self,
20        name: &str,
21        project_root: Option<String>,
22        version: Option<String>,
23    ) -> Result<String, String> {
24        // 1. Resolve project root via alc.toml ancestor walk.
25        let root = resolve_project_root(project_root.as_deref()).ok_or_else(|| {
26            format!(
27                "alc.toml not found: cannot remove '{name}' without a project root. \
28                 Provide project_root or run from a project directory."
29            )
30        })?;
31
32        // 2. Remove from alc.toml (best-effort: entry may not exist if manually removed).
33        match load_alc_toml_document(&root)? {
34            Some(mut doc) => {
35                remove_package_entry(&mut doc, name);
36                save_alc_toml(&root, &doc)?;
37            }
38            None => {
39                return Err(format!("alc.toml not found at {}", root.display()));
40            }
41        }
42
43        // 3. Remove from alc.lock.
44        let alc_lock_path = lockfile_path(&root);
45        match load_lockfile(&root)? {
46            Some(mut lock) => {
47                let before = lock.packages.len();
48                lock.packages.retain(|p| {
49                    if p.name != name {
50                        return true; // keep
51                    }
52                    // name matches: if version is specified, only remove matching version
53                    match &version {
54                        Some(v) => p.version.as_deref() != Some(v.as_str()),
55                        None => false, // remove all with this name
56                    }
57                });
58
59                if lock.packages.len() == before {
60                    return Err(format!(
61                        "Package '{name}' not found in alc.lock at {}",
62                        alc_lock_path.display()
63                    ));
64                }
65
66                save_lockfile(&root, &lock)?;
67            }
68            None => {
69                return Err(format!(
70                    "Package '{name}' not found in alc.lock at {}",
71                    alc_lock_path.display()
72                ));
73            }
74        }
75
76        Ok(serde_json::json!({
77            "removed": name,
78            "alc_toml": root.join("alc.toml").display().to_string(),
79            "alc_lock": alc_lock_path.display().to_string(),
80        })
81        .to_string())
82    }
83}