grex_cli/cli/verbs/
run.rs1use crate::cli::args::{GlobalFlags, RunArgs};
10use anyhow::Result;
11use grex_core::execute::{ActionExecutor, ExecCtx, ExecStep, Platform};
12use grex_core::tree::{FsPackLoader, PackLoader};
13use grex_core::vars::VarEnv;
14use grex_core::{register_builtins, FsExecutor, PlanExecutor, Registry};
15use std::sync::Arc;
16use tokio_util::sync::CancellationToken;
17
18pub fn run(args: RunArgs, global: &GlobalFlags, _cancel: &CancellationToken) -> Result<()> {
19 let Some(pack_root) = super::resolve_pack_root_or_cwd(args.pack_root.as_deref()) else {
20 emit_error(
21 global.json,
22 "usage",
23 "`<pack_root>` required (directory with `.grex/pack.yaml`)",
24 );
25 std::process::exit(2);
26 };
27 let manifest = match FsPackLoader::new().load(&pack_root) {
28 Ok(m) => m,
29 Err(err) => {
30 emit_error(global.json, "load_manifest", &err.to_string());
31 std::process::exit(3);
32 }
33 };
34 let target = args.action.as_str();
35 let matched: Vec<(usize, &grex_core::Action)> =
36 manifest.actions.iter().enumerate().filter(|(_, a)| a.name() == target).collect();
37 if matched.is_empty() {
38 emit_no_match(global.json, target);
39 return Ok(());
40 }
41 let (steps, had_err) = dispatch_actions(&matched, &pack_root, global.dry_run, global.json);
42 emit_report(global.json, target, &pack_root, &steps, global.dry_run);
43 if had_err {
44 std::process::exit(2);
45 }
46 Ok(())
47}
48
49fn dispatch_actions(
55 matched: &[(usize, &grex_core::Action)],
56 pack_root: &std::path::Path,
57 dry_run: bool,
58 json: bool,
59) -> (Vec<(usize, ExecStep)>, bool) {
60 let mut registry = Registry::new();
61 register_builtins(&mut registry);
62 let registry = Arc::new(registry);
63 let vars = VarEnv::new();
64 let plan = PlanExecutor::with_registry(registry.clone());
65 let fs = FsExecutor::with_registry(registry);
66 let mut steps: Vec<(usize, ExecStep)> = Vec::new();
67 for (idx, action) in matched {
68 let ctx = ExecCtx::new(&vars, pack_root, pack_root).with_platform(Platform::current());
69 let result = if dry_run { plan.execute(action, &ctx) } else { fs.execute(action, &ctx) };
70 match result {
71 Ok(step) => steps.push((*idx, step)),
72 Err(err) => {
73 emit_error(json, "exec", &format!("action[{idx}] failed: {err}"));
74 return (steps, true);
75 }
76 }
77 }
78 (steps, false)
79}
80
81fn emit_no_match(json: bool, action: &str) {
82 if json {
83 let doc = serde_json::json!({
84 "verb": "run",
85 "action": action,
86 "matched_packs": 0,
87 "steps": [],
88 });
89 println!("{}", serde_json::to_string(&doc).unwrap_or_default());
90 } else {
91 println!("grex run: no packs declare action `{action}`");
92 }
93}
94
95fn emit_report(
96 json: bool,
97 action: &str,
98 pack_root: &std::path::Path,
99 steps: &[(usize, ExecStep)],
100 dry_run: bool,
101) {
102 if json {
103 let step_docs: Vec<serde_json::Value> = steps
104 .iter()
105 .map(|(idx, _s)| {
106 serde_json::json!({
107 "action_idx": idx,
108 "action": action,
109 })
110 })
111 .collect();
112 let doc = serde_json::json!({
113 "verb": "run",
114 "action": action,
115 "pack_root": pack_root.display().to_string(),
116 "dry_run": dry_run,
117 "matched_packs": if steps.is_empty() { 0 } else { 1 },
118 "steps": step_docs,
119 });
120 println!("{}", serde_json::to_string(&doc).unwrap_or_default());
121 } else {
122 let prefix = if dry_run { "DRY-RUN: would run" } else { "ran" };
123 for (idx, _s) in steps {
124 println!("{prefix} action[{idx}] `{action}` in {}", pack_root.display());
125 }
126 }
127}
128
129fn emit_error(json: bool, kind: &str, msg: &str) {
130 if json {
131 let doc = serde_json::json!({
132 "verb": "run",
133 "error": { "kind": kind, "message": msg },
134 });
135 println!("{}", serde_json::to_string(&doc).unwrap_or_default());
136 } else {
137 eprintln!("grex run: {msg}");
138 }
139}