grex_cli/cli/verbs/
status.rs1use crate::cli::args::{GlobalFlags, StatusArgs};
10use anyhow::Result;
11use grex_core::sync::{self, SyncOptions, SyncReport};
12use std::collections::BTreeMap;
13use tokio_util::sync::CancellationToken;
14
15pub fn run(args: StatusArgs, global: &GlobalFlags, cancel: &CancellationToken) -> Result<()> {
16 let Some(pack_root) = super::resolve_pack_root_or_cwd(args.pack_root.as_deref()) else {
17 emit_error(
18 global.json,
19 "usage",
20 "`<pack_root>` required (directory with `.grex/pack.yaml`)",
21 );
22 std::process::exit(2);
23 };
24 let opts = SyncOptions::new().with_dry_run(true).with_validate(true);
25 match sync::run(&pack_root, &opts, cancel) {
26 Ok(report) => {
27 render(&report, global.json);
28 if report.halted.is_some() {
29 std::process::exit(2);
30 }
31 Ok(())
32 }
33 Err(err) => {
34 let outcome = super::sync::classify_sync_err(err, global.json, "status");
35 match outcome {
36 super::sync::RunOutcome::Validation => std::process::exit(1),
37 super::sync::RunOutcome::Tree | super::sync::RunOutcome::UsageError => {
38 std::process::exit(2)
39 }
40 super::sync::RunOutcome::Exec => std::process::exit(2),
41 super::sync::RunOutcome::Ok => Ok(()),
42 }
43 }
44 }
45}
46
47fn render(report: &SyncReport, json: bool) {
48 let mut per_pack: BTreeMap<String, usize> = BTreeMap::new();
49 for s in &report.steps {
50 *per_pack.entry(s.pack.clone()).or_insert(0) += 1;
51 }
52 if json {
53 let packs: Vec<serde_json::Value> = per_pack
54 .iter()
55 .map(|(pack, &count)| {
56 let state = if count == 0 { "clean" } else { "would_update" };
57 serde_json::json!({
58 "path": pack,
59 "state": state,
60 "drift_count": count,
61 })
62 })
63 .collect();
64 let clean = per_pack.values().all(|&n| n == 0);
65 let doc = serde_json::json!({
66 "verb": "status",
67 "clean": clean,
68 "packs": packs,
69 });
70 println!("{}", serde_json::to_string(&doc).unwrap_or_default());
71 } else if per_pack.is_empty() {
72 println!("clean");
73 } else {
74 for (pack, count) in &per_pack {
75 if *count == 0 {
76 println!("clean {pack}");
77 } else {
78 println!("would-update {count:>3} {pack}");
79 }
80 }
81 }
82}
83
84fn emit_error(json: bool, kind: &str, msg: &str) {
85 if json {
86 let doc = serde_json::json!({
87 "verb": "status",
88 "error": { "kind": kind, "message": msg },
89 });
90 println!("{}", serde_json::to_string(&doc).unwrap_or_default());
91 } else {
92 eprintln!("grex status: {msg}");
93 }
94}