ggen-domain 3.2.0

Domain logic layer for ggen - pure business logic without CLI dependencies
Documentation
//! Project creation domain logic
//!
//! Chicago TDD: Pure business logic with REAL project creation

use ggen_core::project_generator::{create_new_project, ProjectConfig, ProjectType};
use ggen_utils::error::Result;

/// New project command input (pure domain type)
#[derive(Debug, Clone, Default)]
pub struct NewInput {
    pub name: String,
    pub project_type: String,
    pub framework: Option<String>,
    pub output: Option<String>,
    pub skip_install: bool,
}

/// Project creation result
#[derive(Debug, Clone)]
pub struct CreationResult {
    pub project_path: String,
    pub next_steps: String,
}

/// Create a new project (Chicago TDD: REAL implementation)
pub fn create_project(args: &NewInput) -> Result<CreationResult> {
    // Validate project name
    ggen_core::project_generator::common::validate_project_name(&args.name)?;

    // Parse project type
    let project_type: ProjectType = args.project_type.parse()?;

    // Convert output path from Option<String> to PathBuf
    let output_path = args
        .output
        .as_deref()
        .map(std::path::PathBuf::from)
        .unwrap_or_else(|| std::path::PathBuf::from("."));

    // Create project config
    let config = ProjectConfig {
        name: args.name.clone(),
        project_type: project_type.clone(),
        framework: args.framework.clone(),
        path: output_path,
    };

    // Create project synchronously
    let runtime = tokio::runtime::Runtime::new()
        .map_err(|e| ggen_utils::error::Error::new_fmt(format_args!("Runtime error: {}", e)))?;

    runtime.block_on(async { create_new_project(&config).await })?;

    // Generate next steps message
    let next_steps = match project_type {
        ProjectType::RustWeb | ProjectType::RustCli | ProjectType::RustLib => {
            "  cargo run".to_string()
        }
        ProjectType::NextJs | ProjectType::Nuxt => {
            if !args.skip_install {
                "  npm run dev".to_string()
            } else {
                "  npm install\n  npm run dev".to_string()
            }
        }
    };

    // Construct project path from output directory (or current dir if None)
    let output_dir = args.output.as_deref().unwrap_or(".");
    let project_path = std::path::Path::new(output_dir).join(&args.name);

    Ok(CreationResult {
        project_path: project_path.display().to_string(),
        next_steps,
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_validate_project_name() {
        // Valid names
        assert!(ggen_core::project_generator::common::validate_project_name("my-project").is_ok());
        assert!(ggen_core::project_generator::common::validate_project_name("my_project").is_ok());

        // Invalid names
        assert!(ggen_core::project_generator::common::validate_project_name("my project").is_err());
        assert!(ggen_core::project_generator::common::validate_project_name("").is_err());
    }

    #[test]
    fn test_next_steps_rust() {
        let args = NewInput {
            name: "test-project".to_string(),
            project_type: "rust-cli".to_string(),
            framework: None,
            output: Some("/tmp".to_string()),
            skip_install: false,
        };

        let project_type: ProjectType = args.project_type.parse().unwrap();

        let next_steps = match project_type {
            ProjectType::RustWeb | ProjectType::RustCli | ProjectType::RustLib => {
                "  cargo run".to_string()
            }
            _ => String::new(),
        };

        assert_eq!(next_steps, "  cargo run");
    }
}