ggen_node/lib.rs
1#![deny(warnings)] // Poka-Yoke: Prevent warnings at compile time - compiler enforces correctness
2#![allow(clippy::unwrap_used)] // Safe unwrap in JSON parsing where structure is controlled
3#![allow(clippy::expect_used)] // NAPI functions use expect for error conversion
4//! # ggen-node - Production-grade Node.js N-API bindings for ggen CLI
5//!
6//! This module provides high-performance, type-safe bindings to the ggen CLI
7//! for use in Node.js applications. All functions use proper error handling
8//! without .expect() or .unwrap() to ensure production readiness.
9
10use napi::bindgen_prelude::*;
11use napi_derive::napi;
12
13/// Result of a CLI command execution
14#[napi(object)]
15pub struct RunResult {
16 /// Exit code (0 = success, non-zero = error)
17 pub code: i32,
18 /// Standard output captured from the command
19 pub stdout: String,
20 /// Standard error captured from the command
21 pub stderr: String,
22}
23
24/// Get the ggen version
25///
26/// # Returns
27/// Version string matching the Cargo package version
28///
29/// # Example
30/// ```typescript
31/// import { version } from '@ggen/node';
32/// console.log('ggen version:', version());
33/// ```
34#[napi]
35pub fn version() -> String {
36 env!("CARGO_PKG_VERSION").to_string()
37}
38
39/// Execute ggen CLI with provided arguments
40///
41/// This is the low-level API that all other functions use internally.
42/// Prefer using the high-level wrapper functions for better ergonomics.
43///
44/// # Arguments
45/// * `args` - Command-line arguments (without the 'ggen' binary name)
46///
47/// # Returns
48/// RunResult containing exit code and captured stdout/stderr
49///
50/// # Example
51/// ```typescript
52/// const result = await run(['--version']);
53/// console.log(result.stdout);
54/// ```
55#[napi]
56pub async fn run(args: Vec<String>) -> Result<RunResult> {
57 let result = ggen_cli_lib::run_for_node(args)
58 .await
59 .map_err(|e| Error::from_reason(format!("CLI execution failed: {}", e)))?;
60 Ok(RunResult {
61 code: result.code,
62 stdout: result.stdout,
63 stderr: result.stderr,
64 })
65}
66
67// ═══════════════════════════════════════════════════════════════════════════════
68// Marketplace Commands
69// ═══════════════════════════════════════════════════════════════════════════════
70
71/// Search marketplace packages by query
72///
73/// # Arguments
74/// * `query` - Search terms (e.g., "rust web service")
75///
76/// # Example
77/// ```typescript
78/// const result = await marketSearch('rust web');
79/// console.log(result.stdout);
80/// ```
81#[napi]
82pub async fn market_search(query: String) -> Result<RunResult> {
83 let args = vec!["market".to_string(), "search".to_string(), query];
84 run(args).await
85}
86
87/// Add a marketplace package to the current project
88///
89/// # Arguments
90/// * `package_id` - Package identifier (e.g., "io.ggen.rust.axum-service")
91///
92/// # Example
93/// ```typescript
94/// await marketAdd('io.ggen.rust.axum-service');
95/// ```
96#[napi]
97pub async fn market_add(package_id: String) -> Result<RunResult> {
98 let args = vec!["market".to_string(), "add".to_string(), package_id];
99 run(args).await
100}
101
102/// List all installed marketplace packages
103///
104/// # Example
105/// ```typescript
106/// const result = await marketList();
107/// console.log(result.stdout);
108/// ```
109#[napi]
110pub async fn market_list() -> Result<RunResult> {
111 let args = vec!["market".to_string(), "list".to_string()];
112 run(args).await
113}
114
115/// List available marketplace categories
116///
117/// # Example
118/// ```typescript
119/// const result = await marketCategories();
120/// console.log(result.stdout);
121/// ```
122#[napi]
123pub async fn market_categories() -> Result<RunResult> {
124 let args = vec!["market".to_string(), "categories".to_string()];
125 run(args).await
126}
127
128/// Remove a marketplace package from the current project
129///
130/// # Arguments
131/// * `package_id` - Package identifier to remove
132///
133/// # Example
134/// ```typescript
135/// await marketRemove('io.ggen.rust.axum-service');
136/// ```
137#[napi]
138pub async fn market_remove(package_id: String) -> Result<RunResult> {
139 let args = vec!["market".to_string(), "remove".to_string(), package_id];
140 run(args).await
141}
142
143// ═══════════════════════════════════════════════════════════════════════════════
144// Lifecycle Commands
145// ═══════════════════════════════════════════════════════════════════════════════
146
147/// Initialize a new ggen project
148///
149/// # Example
150/// ```typescript
151/// await lifecycleInit();
152/// ```
153#[napi]
154pub async fn lifecycle_init() -> Result<RunResult> {
155 let args = vec![
156 "lifecycle".to_string(),
157 "run".to_string(),
158 "init".to_string(),
159 ];
160 run(args).await
161}
162
163/// Run tests for the current project
164///
165/// # Example
166/// ```typescript
167/// const result = await lifecycleTest();
168/// if (result.code !== 0) {
169/// console.error('Tests failed:', result.stderr);
170/// }
171/// ```
172#[napi]
173pub async fn lifecycle_test() -> Result<RunResult> {
174 let args = vec![
175 "lifecycle".to_string(),
176 "run".to_string(),
177 "test".to_string(),
178 ];
179 run(args).await
180}
181
182/// Build the current project
183///
184/// # Example
185/// ```typescript
186/// await lifecycleBuild();
187/// ```
188#[napi]
189pub async fn lifecycle_build() -> Result<RunResult> {
190 let args = vec![
191 "lifecycle".to_string(),
192 "run".to_string(),
193 "build".to_string(),
194 ];
195 run(args).await
196}
197
198/// Deploy the project to a specified environment
199///
200/// # Arguments
201/// * `env` - Target environment (e.g., "staging", "production")
202///
203/// # Example
204/// ```typescript
205/// await lifecycleDeploy('production');
206/// ```
207#[napi]
208pub async fn lifecycle_deploy(env: Option<String>) -> Result<RunResult> {
209 let mut args = vec![
210 "lifecycle".to_string(),
211 "run".to_string(),
212 "deploy".to_string(),
213 ];
214 if let Some(environment) = env {
215 args.push("--env".to_string());
216 args.push(environment);
217 }
218 run(args).await
219}
220
221/// Validate deployment readiness for a specified environment
222///
223/// # Arguments
224/// * `env` - Target environment to validate (e.g., "production")
225///
226/// # Example
227/// ```typescript
228/// const result = await lifecycleValidate('production');
229/// if (result.code === 0) {
230/// console.log('Ready for production deployment');
231/// }
232/// ```
233#[napi]
234pub async fn lifecycle_validate(env: Option<String>) -> Result<RunResult> {
235 let mut args = vec!["lifecycle".to_string(), "validate".to_string()];
236 if let Some(environment) = env {
237 args.push("--env".to_string());
238 args.push(environment);
239 }
240 run(args).await
241}
242
243/// Check production readiness status
244///
245/// # Example
246/// ```typescript
247/// const result = await lifecycleReadiness();
248/// console.log(result.stdout);
249/// ```
250#[napi]
251pub async fn lifecycle_readiness() -> Result<RunResult> {
252 let args = vec!["lifecycle".to_string(), "readiness".to_string()];
253 run(args).await
254}
255
256/// Update readiness status for a specific requirement
257///
258/// # Arguments
259/// * `requirement_id` - Requirement identifier
260/// * `status` - New status value (e.g., "complete", "in-progress")
261///
262/// # Example
263/// ```typescript
264/// await lifecycleReadinessUpdate('auth-basic', 'complete');
265/// ```
266#[napi]
267pub async fn lifecycle_readiness_update(
268 requirement_id: String, status: String,
269) -> Result<RunResult> {
270 let args = vec![
271 "lifecycle".to_string(),
272 "readiness-update".to_string(),
273 requirement_id,
274 status,
275 ];
276 run(args).await
277}
278
279/// List available lifecycle phases
280///
281/// # Example
282/// ```typescript
283/// const result = await lifecycleList();
284/// console.log(result.stdout);
285/// ```
286#[napi]
287pub async fn lifecycle_list() -> Result<RunResult> {
288 let args = vec!["lifecycle".to_string(), "list".to_string()];
289 run(args).await
290}
291
292// ═══════════════════════════════════════════════════════════════════════════════
293// Template Generation Commands
294// ═══════════════════════════════════════════════════════════════════════════════
295
296/// Generate code from a template
297///
298/// # Arguments
299/// * `template_path` - Path to template file
300/// * `vars` - Optional variable map for template rendering (as JSON string)
301/// * `manifest_path` - Optional path to ggen.toml manifest
302///
303/// # Example
304/// ```typescript
305/// await templateGenerate('service.tmpl', JSON.stringify({ name: 'api' }));
306/// ```
307#[napi]
308pub async fn template_generate(
309 template_path: String, vars: Option<String>, manifest_path: Option<String>,
310) -> Result<RunResult> {
311 let mut args = vec!["gen".to_string(), template_path];
312
313 // Add variables if provided
314 if let Some(vars_str) = vars {
315 if let Ok(vars_obj) = serde_json::from_str::<serde_json::Value>(&vars_str) {
316 if let Some(obj) = vars_obj.as_object() {
317 args.push("--vars".to_string());
318 for (key, value) in obj {
319 if let Some(val_str) = value.as_str() {
320 args.push(format!("{}={}", key, val_str));
321 }
322 }
323 }
324 }
325 }
326
327 // Add manifest path if provided
328 if let Some(manifest) = manifest_path {
329 args.push("--manifest-path".to_string());
330 args.push(manifest);
331 }
332
333 run(args).await
334}
335
336/// List available templates
337///
338/// # Example
339/// ```typescript
340/// const result = await templateList();
341/// console.log(result.stdout);
342/// ```
343#[napi]
344pub async fn template_list() -> Result<RunResult> {
345 let args = vec!["list".to_string()];
346 run(args).await
347}
348
349// ═══════════════════════════════════════════════════════════════════════════════
350// AI Generation Commands
351// ═══════════════════════════════════════════════════════════════════════════════
352
353/// Generate a complete project using AI from a description
354///
355/// # Arguments
356/// * `description` - Natural language project description
357/// * `name` - Optional project name
358/// * `language` - Optional target language (rust, typescript, python, go)
359///
360/// # Example
361/// ```typescript
362/// await aiProject('REST API with authentication', 'my-api', 'rust');
363/// ```
364#[napi]
365pub async fn ai_project(
366 description: String, name: Option<String>, language: Option<String>,
367) -> Result<RunResult> {
368 let mut args = vec!["ai".to_string(), "project".to_string(), description];
369
370 if let Some(project_name) = name {
371 args.push("--name".to_string());
372 args.push(project_name);
373 }
374
375 if let Some(lang) = language {
376 args.push(format!("--{}", lang));
377 }
378
379 run(args).await
380}
381
382/// Generate a template file using AI from a description
383///
384/// # Arguments
385/// * `description` - What the template should do
386/// * `output_path` - Where to save the generated template
387///
388/// # Example
389/// ```typescript
390/// await aiGenerate('Database repository for users', 'user-repo.tmpl');
391/// ```
392#[napi]
393pub async fn ai_generate(description: String, output_path: String) -> Result<RunResult> {
394 let args = vec![
395 "ai".to_string(),
396 "generate".to_string(),
397 "-d".to_string(),
398 description,
399 "-o".to_string(),
400 output_path,
401 ];
402 run(args).await
403}
404
405/// Generate an RDF ontology using AI from a description
406///
407/// # Arguments
408/// * `description` - What the ontology should represent
409/// * `output_path` - Where to save the generated ontology (.ttl file)
410///
411/// # Example
412/// ```typescript
413/// await aiGraph('User management ontology', 'users.ttl');
414/// ```
415#[napi]
416pub async fn ai_graph(description: String, output_path: String) -> Result<RunResult> {
417 let args = vec![
418 "ai".to_string(),
419 "graph".to_string(),
420 "-d".to_string(),
421 description,
422 "-o".to_string(),
423 output_path,
424 ];
425 run(args).await
426}
427
428/// Generate a SPARQL query using AI from a description
429///
430/// # Arguments
431/// * `description` - What the query should find
432/// * `graph_path` - Optional path to RDF graph file for context
433///
434/// # Example
435/// ```typescript
436/// await aiSparql('Find all active users', 'users.ttl');
437/// ```
438#[napi]
439pub async fn ai_sparql(description: String, graph_path: Option<String>) -> Result<RunResult> {
440 let mut args = vec![
441 "ai".to_string(),
442 "sparql".to_string(),
443 "-d".to_string(),
444 description,
445 ];
446
447 if let Some(graph) = graph_path {
448 args.push("-g".to_string());
449 args.push(graph);
450 }
451
452 run(args).await
453}
454
455// ═══════════════════════════════════════════════════════════════════════════════
456// Utility Commands
457// ═══════════════════════════════════════════════════════════════════════════════
458
459/// Run environment diagnostics
460///
461/// # Example
462/// ```typescript
463/// const result = await doctor();
464/// console.log(result.stdout);
465/// ```
466#[napi]
467pub async fn doctor() -> Result<RunResult> {
468 let args = vec!["doctor".to_string()];
469 run(args).await
470}
471
472/// Get help text for a specific command
473///
474/// # Arguments
475/// * `command` - Optional command name (empty for general help)
476///
477/// # Example
478/// ```typescript
479/// const result = await help('market');
480/// console.log(result.stdout);
481/// ```
482#[napi]
483pub async fn help(command: Option<String>) -> Result<RunResult> {
484 let mut args = vec![];
485 if let Some(cmd) = command {
486 args.push(cmd);
487 }
488 args.push("--help".to_string());
489 run(args).await
490}