use crate::cli::args::{AddArgs, GlobalFlags};
use anyhow::{Context, Result};
use grex_core::add::{add_pack, infer_path_from_url, AddOpts, AddReport, AddRequest};
use grex_core::import::classify;
use grex_core::manifest::{
append::read_all, ensure_event_log_migrated, find_workspace_root, fold::fold,
};
use grex_core::refspec::parse_ref;
use tokio_util::sync::CancellationToken;
pub fn run(args: AddArgs, global: &GlobalFlags, _cancel: &CancellationToken) -> Result<()> {
let path = args.path.unwrap_or_else(|| infer_path_from_url(&args.url));
let pack_type = classify(&args.url).as_str().to_string();
let parsed_ref = match args.git_ref.as_deref() {
Some(token) => Some(parse_ref(token).with_context(|| format!("parse --ref `{token}`"))?),
None => None,
};
let cwd = std::env::current_dir().context("resolve cwd for workspace root")?;
let workspace = find_workspace_root(&cwd);
let manifest = ensure_event_log_migrated(&workspace).context("migrate v1.x event log")?;
check_path_collision(&manifest, &path, global.json)?;
let mut request = AddRequest::new(args.url, path, pack_type);
if let Some(r) = parsed_ref {
request = request.with_ref(r);
}
let report =
add_pack(&manifest, request, AddOpts::new(global.dry_run)).context("grex add failed")?;
if global.json {
emit_json(&report)?;
} else {
emit_human(&report);
}
Ok(())
}
fn check_path_collision(manifest: &std::path::Path, path: &str, json: bool) -> Result<()> {
if !manifest.exists() {
return Ok(());
}
let events = read_all(manifest).context("read event log for collision check")?;
let state = fold(events);
let Some(existing) = state.values().find(|s| s.path == path) else {
return Ok(());
};
let msg = format!("path '{path}' already registered to {}", existing.url);
if json {
let doc = serde_json::json!({
"verb": "add",
"error": {
"kind": "path_collision",
"message": msg,
"path": path,
"existing_url": existing.url,
},
});
println!("{}", serde_json::to_string(&doc)?);
} else {
eprintln!("grex add: {msg}; not adding");
}
std::process::exit(1);
}
fn emit_human(report: &AddReport) {
let prefix = if report.dry_run { "DRY-RUN: would add" } else { "added" };
println!(
"{prefix} {path:<32} {kind:<12} {url}",
path = report.path,
kind = report.pack_type,
url = if report.url.is_empty() { "-" } else { &report.url },
);
if let (Some(dir), Some(action)) = (report.refdir.as_deref(), report.ref_action) {
println!(" ref: refdir={dir} action={action:?}");
}
}
fn emit_json(report: &AddReport) -> Result<()> {
let out = serde_json::json!({
"dry_run": report.dry_run,
"id": report.id,
"url": report.url,
"path": report.path,
"type": report.pack_type,
"appended": report.appended,
"refdir": report.refdir,
"ref_action": report.ref_action.map(|a| format!("{a:?}")),
});
println!("{}", serde_json::to_string(&out)?);
Ok(())
}