ggen_cli_lib/lib.rs
1//! # ggen-cli - Command-line interface for ggen code generation
2//!
3//! This crate provides the command-line interface for ggen, using clap-noun-verb
4//! for automatic command discovery and routing. It bridges between user commands
5//! and the domain logic layer (ggen-domain).
6//!
7//! ## Architecture
8//!
9//! - **Command Discovery**: Uses clap-noun-verb v3.4.0 auto-discovery to find
10//! all `\[verb\]` functions in the `cmds` module
11//! - **Async/Sync Bridge**: Provides runtime utilities to bridge async domain
12//! functions with synchronous CLI execution
13//! - **Conventions**: File-based routing conventions for template-based command
14//! generation
15//! - **Node Integration**: Programmatic entry point for Node.js addon integration
16//!
17//! ## Features
18//!
19//! - **Auto-discovery**: Commands are automatically discovered via clap-noun-verb
20//! - **Version handling**: Built-in `--version` flag support
21//! - **Output capture**: Programmatic execution with stdout/stderr capture
22//! - **Async support**: Full async/await support for non-blocking operations
23//!
24//! ## Examples
25//!
26//! ### Basic CLI Execution
27//!
28//! ```rust,no_run
29//! use ggen_cli::cli_match;
30//!
31//! # async fn example() -> ggen_core::utils::error::Result<()> {
32//! // Execute CLI with auto-discovered commands
33//! cli_match().await?;
34//! # Ok(())
35//! # }
36//! ```
37//!
38//! ### Programmatic Execution
39//!
40//! ```rust,ignore
41//! use ggen_cli::run_for_node;
42//!
43//! # async fn example() -> ggen_core::utils::error::Result<()> {
44//! let args = vec!["template".to_string(), "generate".to_string()];
45//! let result = run_for_node(args).await?;
46//! println!("Exit code: {}", result.code);
47//! println!("Output: {}", result.stdout);
48//! # Ok(())
49//! # }
50//! ```
51#![deny(warnings)]
52#![allow(unexpected_cfgs)]
53#![allow(unused_imports)]
54#![allow(dead_code)] // Poka-Yoke: Prevent warnings at compile time - compiler enforces correctness
55#![allow(non_upper_case_globals)] // Allow macro-generated static variables from clap-noun-verb
56#![allow(clippy::unused_unit)] // clap-noun-verb #[verb] macro generates unit expressions
57#![allow(
58 clippy::needless_borrows_for_generic_args,
59 clippy::needless_question_mark,
60 clippy::new_without_default,
61 clippy::question_mark,
62 clippy::too_many_arguments,
63 clippy::unnecessary_lazy_evaluations,
64 clippy::unnecessary_map_or,
65 clippy::useless_conversion
66)]
67pub mod config_clap;
68pub mod error;
69pub mod pack_install;
70pub mod prelude;
71pub mod progress;
72pub mod validation_lib;
73
74// Note: std::io::Write was used for output capture with gag crate (now disabled)
75
76// Command modules - clap-noun-verb v26.5.19 auto-discovery
77pub mod cmds; // clap-noun-verb v26 entry points with #[verb] functions
78pub mod conventions; // File-based routing conventions
79pub mod receipt_manager; // Cryptographic receipt generation for CLI operations
80pub mod runtime; // Async/sync bridge utilities
81pub mod runtime_helper; // Sync CLI wrapper utilities for async operations // Common imports for commands
82
83// Re-export clap-noun-verb for auto-discovery
84pub use clap_noun_verb::{run, Result as ClapNounVerbResult};
85
86// Re-export Result type for use in cmds
87pub use ggen_core::utils::error::Result;
88
89/// Main entry point using clap-noun-verb v26.5.19 auto-discovery
90///
91/// This function delegates to clap-noun-verb::run() which automatically discovers
92/// all `\[verb\]` functions in the cmds module and its submodules.
93/// The version flag is handled automatically by clap-noun-verb.
94pub async fn cli_match() -> ggen_core::utils::error::Result<()> {
95 // Initialize OTLP telemetry (non-fatal — CLI continues if collector is unreachable)
96 let _telemetry_guard =
97 ggen_core::telemetry::init_telemetry(ggen_core::telemetry::TelemetryConfig::default());
98
99 // Root span so every CLI invocation produces at least one exportable trace
100 let args: Vec<String> = std::env::args().skip(1).collect();
101 let span = tracing::info_span!("ggen.cli", command = %args.join(" "), version = env!("CARGO_PKG_VERSION"));
102 let _enter = span.enter();
103
104 // Handle --version flag before delegating to clap-noun-verb
105 let args: Vec<String> = std::env::args().collect();
106 if args.iter().any(|arg| arg == "--version" || arg == "-V") {
107 println!("ggen {}", env!("CARGO_PKG_VERSION"));
108 return Ok(());
109 }
110
111 // Use clap-noun-verb auto-discovery (handles --version automatically, but we preempted it)
112 clap_noun_verb::run().map_err(|e| {
113 ggen_core::utils::error::Error::new(&format!("CLI execution failed: {}", e))
114 })?;
115 Ok(())
116}
117
118/// Structured result for programmatic CLI execution (used by Node addon)
119#[derive(Debug, Clone)]
120pub struct RunResult {
121 pub code: i32,
122 pub stdout: String,
123 pub stderr: String,
124}
125
126/// Programmatic entrypoint to execute the CLI with provided arguments and capture output.
127/// This avoids spawning a new process and preserves deterministic behavior.
128pub async fn run_for_node(args: Vec<String>) -> ggen_core::utils::error::Result<RunResult> {
129 use std::sync::Arc;
130 use std::sync::Mutex;
131
132 // Prefix with a binary name to satisfy clap-noun-verb semantics
133 let _argv: Vec<String> = std::iter::once("ggen".to_string())
134 .chain(args.into_iter())
135 .collect();
136
137 // Create thread-safe buffers for capturing output
138 let stdout_buffer = Arc::new(Mutex::new(Vec::new()));
139 let stderr_buffer = Arc::new(Mutex::new(Vec::new()));
140
141 let stdout_clone = Arc::clone(&stdout_buffer);
142 let stderr_clone = Arc::clone(&stderr_buffer);
143
144 // Execute in a blocking task
145 // NOTE: Output capture with gag crate is disabled for now
146 let result = tokio::task::spawn_blocking(move || {
147 // Execute without capture (gag crate not available)
148 let code = match cmds::run_cli() {
149 Ok(()) => 0,
150 Err(err) => {
151 log::error!("{}", err);
152 1
153 }
154 };
155
156 // Suppress unused variable warnings
157 let _ = (stdout_clone, stderr_clone);
158
159 code
160 })
161 .await
162 .map_err(|e| ggen_core::utils::error::Error::new(&format!("Failed to execute CLI: {}", e)))?;
163
164 // Retrieve captured output, handle mutex poisoning gracefully
165 let stdout = match stdout_buffer.lock() {
166 Ok(guard) => String::from_utf8_lossy(&guard).to_string(),
167 Err(_poisoned) => {
168 log::warn!("Stdout buffer mutex was poisoned when reading, using empty string");
169 String::new()
170 }
171 };
172
173 let stderr = match stderr_buffer.lock() {
174 Ok(guard) => String::from_utf8_lossy(&guard).to_string(),
175 Err(_poisoned) => {
176 log::warn!("Stderr buffer mutex was poisoned when reading, using empty string");
177 String::new()
178 }
179 };
180
181 Ok(RunResult {
182 code: result,
183 stdout,
184 stderr,
185 })
186}