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