1pub mod commands;
7pub mod types;
8pub mod utils;
9
10use crate::error::Result;
11use clap::Parser;
12use std::path::PathBuf;
13use tracing::error;
14
15use crate::cli::utils::setup_logging;
17
18pub async fn run_cli() -> Result<()> {
22 let cli = Cli::parse();
23
24 setup_logging(cli.verbose)?;
26
27 let result = match cli.command {
28 Commands::Run {
29 paths,
30 parallel,
31 jobs,
32 fail_fast,
33 watch,
34 interactive,
35 force,
36 shard,
37 } => {
38 let config = crate::cli::types::CliConfig {
39 parallel,
40 jobs,
41 format: cli.format.clone(),
42 fail_fast,
43 watch,
44 interactive,
45 verbose: cli.verbose,
46 force,
47 };
48
49 let paths_to_run = if let Some(paths) = paths {
51 paths
52 } else {
53 vec![PathBuf::from(".")]
55 };
56
57 run_tests_with_shard(&paths_to_run, &config, shard).await
58 }
59
60 Commands::Validate { files } => {
61 for file in files {
62 validate_config(&file)?;
63 }
64 Ok(())
65 }
66
67 Commands::Init { force, config } => {
68 init_project(force, config)?;
69 Ok(())
70 }
71
72 Commands::Template { template, name, output } => {
73 let template_result = match template.as_str() {
75 "otel" => Some((generate_otel_template()?, "OTEL validation template")),
76 "matrix" => Some((generate_matrix_template()?, "Matrix testing template")),
77 "macros" | "macro-library" => Some((generate_macro_library()?, "Tera macro library")),
78 "full-validation" | "validation" => Some((generate_full_validation_template()?, "Full validation template")),
79 "deterministic" => Some((generate_deterministic_template()?, "Deterministic testing template")),
80 "lifecycle-matcher" => Some((generate_lifecycle_matcher()?, "Lifecycle matcher template")),
81 _ => None,
82 };
83
84 if let Some((content, description)) = template_result {
85 if let Some(output_path) = output {
87 std::fs::write(&output_path, &content).map_err(|e| {
88 crate::error::CleanroomError::io_error(format!(
89 "Failed to write template to {}: {}",
90 output_path.display(),
91 e
92 ))
93 })?;
94 println!("✓ {} generated: {}", description, output_path.display());
95 } else {
96 println!("{}", content);
97 }
98 Ok(())
99 } else {
100 generate_from_template(&template, name.as_deref())?;
102 Ok(())
103 }
104 }
105
106 Commands::Plugins => {
107 list_plugins()?;
108 Ok(())
109 }
110
111 Commands::Services { command } => match command {
112 ServiceCommands::Status => {
113 show_service_status().await?;
114 Ok(())
115 }
116 ServiceCommands::Logs { service, lines } => {
117 show_service_logs(&service, lines).await?;
118 Ok(())
119 }
120 ServiceCommands::Restart { service } => {
121 restart_service(&service).await?;
122 Ok(())
123 }
124 ServiceCommands::AiManage {
125 auto_scale: _,
126 predict_load: _,
127 optimize_resources: _,
128 horizon_minutes: _,
129 service: _,
130 } => {
131 Err(crate::error::CleanroomError::validation_error(
132 "AI service management is an experimental feature in the clnrm-ai crate.\n\
133 To use this feature, enable the 'ai' feature flag or use the clnrm-ai crate directly."
134 ))
135 }
136 },
137
138 Commands::Report {
139 input,
140 output,
141 format,
142 } => {
143 let format_str = match format {
144 ReportFormat::Html => "html",
145 ReportFormat::Markdown => "markdown",
146 ReportFormat::Json => "json",
147 ReportFormat::Pdf => "pdf",
148 };
149 generate_report(input.as_ref(), output.as_ref(), format_str).await?;
150 Ok(())
151 }
152
153 Commands::SelfTest { suite, report, otel_exporter, otel_endpoint } => {
154 run_self_tests(suite, report, otel_exporter, otel_endpoint).await?;
155 Ok(())
156 }
157
158 Commands::AiOrchestrate {
159 paths: _,
160 predict_failures: _,
161 auto_optimize: _,
162 confidence_threshold: _,
163 max_workers: _,
164 } => {
165 Err(crate::error::CleanroomError::validation_error(
166 "AI orchestration is an experimental feature in the clnrm-ai crate.\n\
167 To use this feature, enable the 'ai' feature flag or use the clnrm-ai crate directly."
168 ))
169 }
170
171 Commands::AiPredict {
172 analyze_history: _,
173 predict_failures: _,
174 recommendations: _,
175 format: _,
176 } => {
177 Err(crate::error::CleanroomError::validation_error(
178 "AI predictive analytics is an experimental feature in the clnrm-ai crate.\n\
179 To use this feature, enable the 'ai' feature flag or use the clnrm-ai crate directly."
180 ))
181 }
182
183 Commands::AiOptimize {
184 execution_order: _,
185 resource_allocation: _,
186 parallel_execution: _,
187 auto_apply: _,
188 } => {
189 Err(crate::error::CleanroomError::validation_error(
190 "AI test optimization is an experimental feature in the clnrm-ai crate.\n\
191 To use this feature, enable the 'ai' feature flag or use the clnrm-ai crate directly."
192 ))
193 }
194
195 Commands::AiReal { analyze: _ } => {
196 Err(crate::error::CleanroomError::validation_error(
197 "AI real-time analysis is an experimental feature in the clnrm-ai crate.\n\
198 To use this feature, enable the 'ai' feature flag or use the clnrm-ai crate directly."
199 ))
200 }
201
202 Commands::Health { verbose } => system_health_check(verbose).await,
203
204 Commands::Fmt { files, check, verify } => {
205 format_files(&files, check, verify)?;
206 Ok(())
207 }
208
209 Commands::DryRun { files, verbose } => {
210 use crate::CleanroomError;
211 let file_refs: Vec<_> = files.iter().map(|p| p.as_path()).collect();
212 let results = dry_run_validate(file_refs, verbose)?;
213
214 let failed_count = results.iter().filter(|r| !r.valid).count();
216
217 if failed_count > 0 {
219 return Err(CleanroomError::validation_error(format!(
220 "{} file(s) failed validation",
221 failed_count
222 )));
223 }
224
225 Ok(())
226 }
227
228 Commands::Dev { paths, debounce_ms, clear, only, timebox } => {
229 let config = crate::cli::types::CliConfig {
230 format: cli.format.clone(),
231 verbose: cli.verbose,
232 ..Default::default()
233 };
234
235 run_dev_mode_with_filters(paths, debounce_ms, clear, only, timebox, config).await
236 }
237
238 Commands::Lint { files, format, deny_warnings } => {
239 let file_refs: Vec<_> = files.iter().map(|p| p.as_path()).collect();
240
241 let format_str = match format {
243 crate::cli::types::LintFormat::Human => "human",
244 crate::cli::types::LintFormat::Json => "json",
245 crate::cli::types::LintFormat::Github => "github",
246 };
247
248 lint_files(file_refs, format_str, deny_warnings)?;
250
251 Ok(())
252 }
253
254 Commands::Diff { baseline, current, format, only_changes } => {
255 let format_str = match format {
257 crate::cli::types::DiffFormat::Tree => "tree",
258 crate::cli::types::DiffFormat::Json => "json",
259 crate::cli::types::DiffFormat::SideBySide => "side-by-side",
260 };
261
262 let result = diff_traces(&baseline, ¤t, format_str, only_changes)?;
263
264 if result.added_count > 0 || result.removed_count > 0 || result.modified_count > 0 {
266 std::process::exit(1);
267 }
268
269 Ok(())
270 }
271
272 Commands::Record { paths, output } => {
273 run_record(paths, output).await
274 }
275
276 Commands::AiMonitor {
277 interval: _,
278 anomaly_threshold: _,
279 ai_alerts: _,
280 anomaly_detection: _,
281 proactive_healing: _,
282 webhook_url: _,
283 } => {
284 Err(crate::error::CleanroomError::validation_error(
285 "AI monitoring is an experimental feature in the clnrm-ai crate.\n\
286 To use this feature, enable the 'ai' feature flag or use the clnrm-ai crate directly."
287 ))
288 }
289
290 Commands::Marketplace { command } => {
291 let marketplace = crate::marketplace::Marketplace::default().await?;
292 crate::marketplace::commands::execute_marketplace_command(&marketplace, command).await
293 }
294
295 Commands::Pull { paths, parallel, jobs } => {
297 pull_images(paths, parallel, jobs).await
298 }
299
300 Commands::Graph { trace, format, highlight_missing, filter } => {
301 visualize_graph(&trace, &format, highlight_missing, filter.as_deref())
302 }
303
304 Commands::Repro { baseline, verify_digest, output } => {
305 reproduce_baseline(&baseline, verify_digest, output.as_ref()).await
306 }
307
308 Commands::RedGreen { paths, expect, verify_red, verify_green } => {
309 let (should_verify_red, should_verify_green) = match expect {
311 Some(crate::cli::types::TddState::Red) => (true, false),
312 Some(crate::cli::types::TddState::Green) => (false, true),
313 None => (verify_red, verify_green),
314 };
315 run_red_green_validation(&paths, should_verify_red, should_verify_green).await
316 }
317
318 Commands::Render { template, map, output, show_vars } => {
319 render_template_with_vars(&template, &map, output.as_ref(), show_vars)
320 }
321
322 Commands::Spans { trace, grep, format, show_attrs, show_events } => {
323 filter_spans(&trace, grep.as_deref(), &format, show_attrs, show_events)
324 }
325
326 Commands::Collector { command } => {
327 match command {
328 crate::cli::types::CollectorCommands::Up { image, http_port, grpc_port, detach } => {
329 start_collector(&image, http_port, grpc_port, detach).await
330 }
331 crate::cli::types::CollectorCommands::Down { volumes } => {
332 stop_collector(volumes).await
333 }
334 crate::cli::types::CollectorCommands::Status => {
335 show_collector_status().await
336 }
337 crate::cli::types::CollectorCommands::Logs { lines, follow } => {
338 show_collector_logs(lines, follow).await
339 }
340 }
341 }
342
343 Commands::Analyze { test_file, traces } => {
344 use crate::cli::commands::v0_7_0::analyze::analyze_traces;
345
346 match analyze_traces(&test_file, traces.as_deref()) {
347 Ok(report) => {
348 println!("{}", report.format_report());
349
350 if !report.is_success() {
352 std::process::exit(1);
353 }
354 Ok(())
355 }
356 Err(e) => {
357 eprintln!("Error analyzing traces: {}", e);
358 std::process::exit(1);
359 }
360 }
361 }
362 };
363
364 if let Err(e) = result {
365 error!("Command failed: {}", e);
366 std::process::exit(1);
367 }
368
369 Ok(())
370}
371
372pub use commands::*;
374pub use types::*;