1use crate::cli::args::{GlobalFlags, RmArgs};
9use anyhow::Result;
10use grex_core::sync::{self, SyncOptions};
11use grex_core::tree::{FsPackLoader, PackLoader};
12use grex_core::PackType;
13use std::path::PathBuf;
14use tokio_util::sync::CancellationToken;
15
16pub fn run(args: RmArgs, global: &GlobalFlags, cancel: &CancellationToken) -> Result<()> {
17 let pack_root = PathBuf::from(&args.path);
18 if !pack_root.exists() {
19 emit_error(global.json, "not_found", &format!("{} does not exist", pack_root.display()));
20 std::process::exit(2);
21 }
22 let manifest = match FsPackLoader::new().load(&pack_root) {
23 Ok(m) => m,
24 Err(err) => {
25 emit_error(global.json, "load_manifest", &err.to_string());
26 std::process::exit(3);
27 }
28 };
29 if matches!(manifest.r#type, PackType::Meta) && !manifest.children.is_empty() && !args.force {
30 let msg = format!(
31 "refusing to remove meta-pack with {} children; pass --force to override",
32 manifest.children.len()
33 );
34 emit_error(global.json, "has_children", &msg);
35 std::process::exit(1);
36 }
37 if !args.force {
38 run_teardown(&pack_root, global, cancel);
39 }
40 if global.dry_run {
41 emit_ok(global.json, &pack_root, true);
42 return Ok(());
43 }
44 if let Err(err) = std::fs::remove_dir_all(&pack_root) {
45 emit_error(global.json, "rmtree", &format!("remove {}: {err}", pack_root.display()));
46 std::process::exit(2);
47 }
48 emit_ok(global.json, &pack_root, false);
49 Ok(())
50}
51
52fn run_teardown(pack_root: &std::path::Path, global: &GlobalFlags, cancel: &CancellationToken) {
57 let opts = SyncOptions::new().with_dry_run(global.dry_run).with_validate(true);
58 if let Err(err) = sync::teardown(pack_root, &opts, cancel) {
59 match super::sync::classify_sync_err(err, global.json, "rm") {
60 super::sync::RunOutcome::Validation => std::process::exit(1),
61 super::sync::RunOutcome::Exec => std::process::exit(2),
62 super::sync::RunOutcome::Tree | super::sync::RunOutcome::UsageError => {
63 std::process::exit(3)
64 }
65 super::sync::RunOutcome::Ok => {}
66 }
67 }
68}
69
70fn emit_ok(json: bool, path: &std::path::Path, dry_run: bool) {
71 if json {
72 let doc = serde_json::json!({
73 "verb": "rm",
74 "status": "ok",
75 "path": path.display().to_string(),
76 "dry_run": dry_run,
77 });
78 println!("{}", serde_json::to_string(&doc).unwrap_or_default());
79 } else {
80 let prefix = if dry_run { "DRY-RUN: would remove" } else { "removed" };
81 println!("{prefix} {}", path.display());
82 }
83}
84
85fn emit_error(json: bool, kind: &str, msg: &str) {
86 if json {
87 let doc = serde_json::json!({
88 "verb": "rm",
89 "error": { "kind": kind, "message": msg },
90 });
91 println!("{}", serde_json::to_string(&doc).unwrap_or_default());
92 } else {
93 eprintln!("grex rm: {msg}");
94 }
95}