algocline_app/service/pkg/
remove.rs1use algocline_core::AppDir;
11
12use super::super::alc_toml::{load_alc_toml_document, remove_package_entry, save_alc_toml};
13use super::super::lockfile::{load_lockfile, lockfile_path, save_lockfile};
14use super::super::manifest::{load_manifest, record_remove};
15use super::super::project::resolve_project_root;
16use super::super::AppService;
17
18impl AppService {
19 pub async fn pkg_remove(
30 &self,
31 name: &str,
32 project_root: Option<String>,
33 version: Option<String>,
34 scope: Option<String>,
35 ) -> Result<String, String> {
36 let scope = scope.as_deref().unwrap_or("project");
37 let app_dir = self.log_config.app_dir();
38 match scope {
39 "project" => remove_from_project(name, project_root, version),
40 "global" => remove_from_global(&app_dir, name),
41 "all" => remove_from_all(&app_dir, name, project_root, version),
42 other => Err(format!(
43 "invalid scope '{other}': expected one of project, global, all"
44 )),
45 }
46 }
47}
48
49fn remove_from_project(
51 name: &str,
52 project_root: Option<String>,
53 version: Option<String>,
54) -> Result<String, String> {
55 let root = resolve_project_root(project_root.as_deref()).ok_or_else(|| {
56 format!(
57 "alc.toml not found: cannot remove '{name}' without a project root. \
58 Provide project_root or run from a project directory."
59 )
60 })?;
61
62 match load_alc_toml_document(&root)? {
64 Some(mut doc) => {
65 remove_package_entry(&mut doc, name);
66 save_alc_toml(&root, &doc)?;
67 }
68 None => {
69 return Err(format!("alc.toml not found at {}", root.display()));
70 }
71 }
72
73 let alc_lock_path = lockfile_path(&root);
76 match load_lockfile(&root)? {
77 Some(mut lock) => {
78 let before = lock.packages.len();
79 lock.packages.retain(|p| {
80 if p.name != name {
81 return true;
82 }
83 match &version {
84 Some(v) => p.version.as_deref() != Some(v.as_str()),
85 None => false,
86 }
87 });
88
89 if lock.packages.len() == before {
90 return Err(format!(
91 "Package '{name}' not found in alc.lock at {}",
92 alc_lock_path.display()
93 ));
94 }
95
96 save_lockfile(&root, &lock)?;
97 }
98 None => {
99 return Err(format!(
100 "Package '{name}' not found in alc.lock at {}",
101 alc_lock_path.display()
102 ));
103 }
104 }
105
106 Ok(serde_json::json!({
107 "removed": name,
108 "scope": "project",
109 "alc_toml": root.join("alc.toml").display().to_string(),
110 "alc_lock": alc_lock_path.display().to_string(),
111 })
112 .to_string())
113}
114
115fn remove_from_global(app_dir: &AppDir, name: &str) -> Result<String, String> {
118 let manifest = load_manifest(app_dir)?;
119 if !manifest.packages.contains_key(name) {
120 return Err(format!(
121 "Package '{name}' not found in global manifest ({})",
122 app_dir.installed_json().display()
123 ));
124 }
125
126 record_remove(app_dir, name)?;
127
128 Ok(serde_json::json!({
129 "removed": name,
130 "scope": "global",
131 "installed_json": manifest_path_display(app_dir),
132 })
133 .to_string())
134}
135
136fn remove_from_all(
139 app_dir: &AppDir,
140 name: &str,
141 project_root: Option<String>,
142 version: Option<String>,
143) -> Result<String, String> {
144 let project_res = remove_from_project(name, project_root, version);
145 let global_res = remove_from_global(app_dir, name);
146
147 let (project_ok, project_err) = match project_res {
148 Ok(_) => (true, None),
149 Err(e) => (false, Some(e)),
150 };
151 let (global_ok, global_err) = match global_res {
152 Ok(_) => (true, None),
153 Err(e) => (false, Some(e)),
154 };
155
156 if !project_ok && !global_ok {
157 return Err(format!(
158 "Package '{name}' not found in any scope:\n project: {}\n global: {}",
159 project_err.unwrap_or_default(),
160 global_err.unwrap_or_default()
161 ));
162 }
163
164 Ok(serde_json::json!({
165 "removed": name,
166 "scope": "all",
167 "project_removed": project_ok,
168 "global_removed": global_ok,
169 "project_note": project_err,
170 "global_note": global_err,
171 })
172 .to_string())
173}
174
175fn manifest_path_display(app_dir: &AppDir) -> String {
178 app_dir.installed_json().display().to_string()
179}