ggen_cli_lib/
lib.rs

1use std::io::{Read, Write};
2
3// Command modules - clap-noun-verb v3.4.0 auto-discovery
4pub mod cmds;            // clap-noun-verb v3 entry points with #[verb] functions
5pub mod conventions;     // File-based routing conventions
6// pub mod domain;          // Business logic layer - MOVED TO ggen-domain crate
7pub mod runtime;         // Async/sync bridge utilities
8pub mod runtime_helper;  // Sync CLI wrapper utilities for async operations
9pub mod prelude;         // Common imports for commands
10
11// Re-export clap-noun-verb for auto-discovery
12pub use clap_noun_verb::{run, CommandRouter, Result as ClapNounVerbResult};
13
14/// Main entry point using clap-noun-verb v3.4.0 auto-discovery
15///
16/// This function delegates to clap-noun-verb::run() which automatically discovers
17/// all #[verb] functions in the cmds module and its submodules.
18pub async fn cli_match() -> ggen_utils::error::Result<()> {
19    // Use clap-noun-verb auto-discovery
20    clap_noun_verb::run()
21        .map_err(|e| anyhow::anyhow!("CLI execution failed: {}", e))?;
22    Ok(())
23}
24
25/// Structured result for programmatic CLI execution (used by Node addon)
26#[derive(Debug, Clone)]
27pub struct RunResult {
28    pub code: i32,
29    pub stdout: String,
30    pub stderr: String,
31}
32
33/// Programmatic entrypoint to execute the CLI with provided arguments and capture output.
34/// This avoids spawning a new process and preserves deterministic behavior.
35pub async fn run_for_node(args: Vec<String>) -> ggen_utils::error::Result<RunResult> {
36    use std::sync::Arc;
37    use std::sync::Mutex;
38
39    // Prefix with a binary name to satisfy clap-noun-verb semantics
40    let _argv: Vec<String> = std::iter::once("ggen".to_string()).chain(args.into_iter()).collect();
41
42    // Create thread-safe buffers for capturing output
43    let stdout_buffer = Arc::new(Mutex::new(Vec::new()));
44    let stderr_buffer = Arc::new(Mutex::new(Vec::new()));
45
46    let stdout_clone = Arc::clone(&stdout_buffer);
47    let stderr_clone = Arc::clone(&stderr_buffer);
48
49    // Execute in a blocking task to avoid Send issues with gag
50    let result = tokio::task::spawn_blocking(move || {
51        // Capture stdout/stderr using gag buffers
52        let mut captured_stdout = Vec::new();
53        let mut captured_stderr = Vec::new();
54
55        let code = match (gag::BufferRedirect::stdout(), gag::BufferRedirect::stderr()) {
56            (Ok(mut so), Ok(mut se)) => {
57                // Execute using cmds router
58                let code_val = match cmds::run_cli() {
59                    Ok(()) => 0,
60                    Err(err) => {
61                        let _ = writeln!(std::io::stderr(), "{}", err);
62                        1
63                    }
64                };
65
66                let _ = so.read_to_end(&mut captured_stdout);
67                let _ = se.read_to_end(&mut captured_stderr);
68
69                *stdout_clone.lock().unwrap() = captured_stdout;
70                *stderr_clone.lock().unwrap() = captured_stderr;
71
72                code_val
73            }
74            _ => {
75                // Fallback: execute without capture
76                match cmds::run_cli() {
77                    Ok(()) => 0,
78                    Err(err) => {
79                        eprintln!("{}", err);
80                        1
81                    }
82                }
83            }
84        };
85
86        code
87    })
88    .await
89    .map_err(|e| anyhow::anyhow!("Failed to execute CLI: {}", e))?;
90
91    let stdout = String::from_utf8_lossy(&stdout_buffer.lock().unwrap()).to_string();
92    let stderr = String::from_utf8_lossy(&stderr_buffer.lock().unwrap()).to_string();
93
94    Ok(RunResult {
95        code: result,
96        stdout,
97        stderr,
98    })
99}