mammoth_cli/
project.rs

1use anyhow::{Context, Result};
2use colored::*;
3use dialoguer::{Input, Select};
4use indicatif::{ProgressBar, ProgressStyle};
5use std::fs;
6use std::path::Path;
7
8use crate::config::ProjectConfig;
9use crate::manager::TemplateManager;
10use crate::utils::{init_git_repository, update_package_json};
11
12pub async fn new_project(
13    manager: &mut TemplateManager,
14    template_id: Option<&str>,
15    name: Option<&str>,
16    output: &str,
17) -> Result<()> {
18    println!(
19        "{}",
20        "🚀 Welcome to mammoth-cli Frontend Scaffolding Tool!"
21            .bold()
22            .green()
23    );
24    println!();
25    
26    // Get project configuration through interactive prompts
27    let config = get_project_config(manager, template_id, name, output).await?;
28    
29    // Generate the project
30    generate_project(manager, &config).await?;
31    
32    println!();
33    println!("{}", "🎉 Project generated successfully!".bold().green());
34    println!(
35        "📁 Project location: {}",
36        Path::new(&config.output_dir).join(&config.name).display()
37    );
38    println!();
39    println!("Next steps:");
40    println!("  cd {}", config.name);
41    println!("  npm install  # or pnpm install");
42    println!("  npm run dev  # or pnpm dev");
43    
44    Ok(())
45}
46
47pub async fn get_project_config(
48    manager: &TemplateManager,
49    template_id: Option<&str>,
50    name: Option<&str>,
51    output: &str,
52) -> Result<ProjectConfig> {
53    // Template selection
54    let template = if let Some(id) = template_id {
55        manager
56            .get_template_by_id(id)
57            .ok_or_else(|| anyhow::anyhow!("Template '{}' not found", id))?
58    } else {
59        println!("{}", "🎨 Step 1: Select Template".bold().blue());
60        
61        if manager.config.templates.is_empty() {
62            anyhow::bail!("No templates available. Add templates first with 'template add'");
63        }
64        
65        let template_names: Vec<String> = manager
66            .config
67            .templates
68            .iter()
69            .map(|t| format!("{} - {}", t.id, t.description))
70            .collect();
71        
72        let template_selection = Select::new()
73            .with_prompt("Choose a template")
74            .items(&template_names)
75            .default(0)
76            .interact()?;
77        
78        &manager.config.templates[template_selection]
79    };
80    
81    println!("✨ Selected template: {}", template.id.green());
82    println!();
83    
84    // Project information
85    println!("{}", "📋 Step 2: Project Information".bold().blue());
86    
87    let project_name: String = if let Some(n) = name {
88        n.to_string()
89    } else {
90        Input::new()
91            .with_prompt("Project name")
92            .with_initial_text("my-awesome-project")
93            .interact_text()?
94    };
95    
96    let author: String = Input::new()
97        .with_prompt("Author name")
98        .with_initial_text("Your Name")
99        .interact_text()?;
100    
101    let description: String = Input::new()
102        .with_prompt("Project description")
103        .with_initial_text("A wonderful project")
104        .interact_text()?;
105    
106    let output_dir: String = if output != "." {
107        output.to_string()
108    } else {
109        Input::new()
110            .with_prompt("Output directory")
111            .with_initial_text(".")
112            .interact_text()?
113    };
114    
115    println!();
116    println!("{}", "📊 Project Summary".bold().yellow());
117    println!("Name: {}", project_name);
118    println!("Author: {}", author);
119    println!("Description: {}", description);
120    println!("Template: {}", template.id);
121    println!("Language: {}", template.language);
122    println!("Output Directory: {}", output_dir);
123    println!();
124    
125    // Confirmation
126    let confirm = dialoguer::Confirm::new()
127        .with_prompt("Do you want to proceed with project generation?")
128        .default(true)
129        .interact()?;
130    
131    if !confirm {
132        println!("{}", "❌ Project generation cancelled".red());
133        std::process::exit(0);
134    }
135    
136    Ok(ProjectConfig {
137        name: project_name,
138        author,
139        description,
140        output_dir,
141        template: template.clone(),
142    })
143}
144
145pub async fn generate_project(manager: &TemplateManager, config: &ProjectConfig) -> Result<()> {
146    println!("{}", "🔨 Generating project...".bold().blue());
147    
148    let project_path = Path::new(&config.output_dir).join(&config.name);
149    
150    // Create progress bar
151    let pb = ProgressBar::new(100);
152    pb.set_style(
153        ProgressStyle::default_bar()
154            .template(
155                "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} {msg}",
156            )
157            .unwrap()
158            .progress_chars("#>-"),
159    );
160    
161    pb.set_message("Creating project directory...");
162    pb.inc(10);
163    
164    // Create project directory
165    fs::create_dir_all(&project_path).with_context(|| {
166        format!(
167            "Failed to create project directory: {}",
168            project_path.display()
169        )
170    })?;
171    
172    pb.set_message("Getting template files...");
173    pb.inc(20);
174    
175    // Get template files (will download if not cached)
176    manager.download_template(&config.template, false).await?;
177    manager.copy_template_files(&config.template, &project_path)?;
178    
179    pb.set_message("Updating project configuration...");
180    pb.inc(30);
181    
182    // Update package.json with project information
183    update_package_json(&project_path, config)?;
184    
185    pb.set_message("Finalizing project...");
186    pb.inc(40);
187    
188    // Initialize git repository
189    init_git_repository(&project_path)?;
190    
191    pb.finish_with_message("Project generation completed!");
192    
193    Ok(())
194}