use algocline_core::AppDir;
use super::super::alc_toml::{load_alc_toml_document, remove_package_entry, save_alc_toml};
use super::super::lockfile::{load_lockfile, lockfile_path, save_lockfile};
use super::super::manifest::{load_manifest, record_remove};
use super::super::project::resolve_project_root;
use super::super::AppService;
impl AppService {
pub async fn pkg_remove(
&self,
name: &str,
project_root: Option<String>,
version: Option<String>,
scope: Option<String>,
) -> Result<String, String> {
let scope = scope.as_deref().unwrap_or("project");
let app_dir = self.log_config.app_dir();
match scope {
"project" => remove_from_project(name, project_root, version),
"global" => remove_from_global(&app_dir, name),
"all" => remove_from_all(&app_dir, name, project_root, version),
other => Err(format!(
"invalid scope '{other}': expected one of project, global, all"
)),
}
}
}
fn remove_from_project(
name: &str,
project_root: Option<String>,
version: Option<String>,
) -> Result<String, String> {
let root = resolve_project_root(project_root.as_deref()).ok_or_else(|| {
format!(
"alc.toml not found: cannot remove '{name}' without a project root. \
Provide project_root or run from a project directory."
)
})?;
match load_alc_toml_document(&root)? {
Some(mut doc) => {
remove_package_entry(&mut doc, name);
save_alc_toml(&root, &doc)?;
}
None => {
return Err(format!("alc.toml not found at {}", root.display()));
}
}
let alc_lock_path = lockfile_path(&root);
match load_lockfile(&root)? {
Some(mut lock) => {
let before = lock.packages.len();
lock.packages.retain(|p| {
if p.name != name {
return true;
}
match &version {
Some(v) => p.version.as_deref() != Some(v.as_str()),
None => false,
}
});
if lock.packages.len() == before {
return Err(format!(
"Package '{name}' not found in alc.lock at {}",
alc_lock_path.display()
));
}
save_lockfile(&root, &lock)?;
}
None => {
return Err(format!(
"Package '{name}' not found in alc.lock at {}",
alc_lock_path.display()
));
}
}
Ok(serde_json::json!({
"removed": name,
"scope": "project",
"alc_toml": root.join("alc.toml").display().to_string(),
"alc_lock": alc_lock_path.display().to_string(),
})
.to_string())
}
fn remove_from_global(app_dir: &AppDir, name: &str) -> Result<String, String> {
let manifest = load_manifest(app_dir)?;
if !manifest.packages.contains_key(name) {
return Err(format!(
"Package '{name}' not found in global manifest ({})",
app_dir.installed_json().display()
));
}
record_remove(app_dir, name)?;
Ok(serde_json::json!({
"removed": name,
"scope": "global",
"installed_json": manifest_path_display(app_dir),
})
.to_string())
}
fn remove_from_all(
app_dir: &AppDir,
name: &str,
project_root: Option<String>,
version: Option<String>,
) -> Result<String, String> {
let project_res = remove_from_project(name, project_root, version);
let global_res = remove_from_global(app_dir, name);
let (project_ok, project_err) = match project_res {
Ok(_) => (true, None),
Err(e) => (false, Some(e)),
};
let (global_ok, global_err) = match global_res {
Ok(_) => (true, None),
Err(e) => (false, Some(e)),
};
if !project_ok && !global_ok {
return Err(format!(
"Package '{name}' not found in any scope:\n project: {}\n global: {}",
project_err.unwrap_or_default(),
global_err.unwrap_or_default()
));
}
Ok(serde_json::json!({
"removed": name,
"scope": "all",
"project_removed": project_ok,
"global_removed": global_ok,
"project_note": project_err,
"global_note": global_err,
})
.to_string())
}
fn manifest_path_display(app_dir: &AppDir) -> String {
app_dir.installed_json().display().to_string()
}