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