grex_cli/cli/verbs/
teardown.rs1use crate::cli::args::{GlobalFlags, TeardownArgs};
13use anyhow::Result;
14use grex_core::sync::{self, HaltedContext, SyncError, SyncOptions, SyncReport, SyncStep};
15use tokio_util::sync::CancellationToken;
16
17pub fn run(args: TeardownArgs, global: &GlobalFlags, cancel: &CancellationToken) -> Result<()> {
25 let Some(pack_root) = args.pack_root.clone() else {
26 if global.json {
29 super::sync::emit_json_error(
30 "usage",
31 "`<pack_root>` is required (directory with `.grex/pack.yaml` or the YAML file)",
32 "teardown",
33 );
34 } else {
35 eprintln!(
36 "grex teardown: <pack_root> required (directory with `.grex/pack.yaml` or the YAML file)"
37 );
38 }
39 std::process::exit(2);
40 };
41 let opts = SyncOptions::new()
42 .with_dry_run(global.dry_run)
43 .with_validate(!args.no_validate)
44 .with_workspace(args.workspace.clone());
45 match run_impl(&pack_root, &opts, args.quiet, global.json, cancel) {
46 RunOutcome::Ok => Ok(()),
47 RunOutcome::Validation => std::process::exit(1),
48 RunOutcome::Exec => std::process::exit(2),
49 RunOutcome::Tree => std::process::exit(3),
50 }
51}
52
53enum RunOutcome {
54 Ok,
55 Validation,
56 Exec,
57 Tree,
58}
59
60fn run_impl(
61 pack_root: &std::path::Path,
62 opts: &SyncOptions,
63 quiet: bool,
64 json: bool,
65 cancel: &CancellationToken,
66) -> RunOutcome {
67 match sync::teardown(pack_root, opts, cancel) {
68 Ok(report) => {
69 if json {
70 super::sync::emit_json_report(&report, opts.dry_run, "teardown");
71 } else {
72 render_report(&report, quiet);
73 }
74 if report.halted.is_some() {
75 return RunOutcome::Exec;
76 }
77 RunOutcome::Ok
78 }
79 Err(err) => map_sync_outcome(super::sync::classify_sync_err(err, json, "teardown")),
80 }
81}
82
83fn map_sync_outcome(o: super::sync::RunOutcome) -> RunOutcome {
88 match o {
89 super::sync::RunOutcome::Ok => RunOutcome::Ok,
90 super::sync::RunOutcome::Validation => RunOutcome::Validation,
91 super::sync::RunOutcome::Exec => RunOutcome::Exec,
92 super::sync::RunOutcome::Tree | super::sync::RunOutcome::UsageError => RunOutcome::Tree,
93 }
94}
95
96fn print_halted_context(ctx: &HaltedContext) {
97 eprintln!(
98 "teardown halted at pack `{}` action #{} ({}):",
99 ctx.pack, ctx.action_idx, ctx.action_name
100 );
101 eprintln!(" error: {}", ctx.error);
102 if let Some(hint) = &ctx.recovery_hint {
103 eprintln!(" hint: {hint}");
104 }
105}
106
107fn render_report(report: &SyncReport, quiet: bool) {
108 if !quiet {
109 for s in &report.steps {
110 print_step(s);
111 }
112 }
113 for w in &report.event_log_warnings {
114 eprintln!("warning: {w}");
115 }
116 if let Some(err) = &report.halted {
117 match err {
118 SyncError::Halted(ctx) => print_halted_context(ctx),
119 other => eprintln!("halted: {other}"),
120 }
121 }
122}
123
124fn print_step(s: &SyncStep) {
125 use grex_core::ExecResult;
126 let tag = match &s.exec_step.result {
127 ExecResult::PerformedChange => "ok",
128 ExecResult::WouldPerformChange => "would",
129 ExecResult::AlreadySatisfied => "skipped",
130 ExecResult::NoOp => "noop",
131 _ => "other",
132 };
133 println!(
134 "[{tag}] pack={pack} action={kind} idx={idx}",
135 pack = s.pack,
136 kind = s.exec_step.action_name,
137 idx = s.action_idx,
138 );
139}