lmrc_cli/generator/
mod.rs

1mod documentation;
2mod gitlab_ci;
3mod pipeline;
4mod workspace;
5
6use colored::Colorize;
7use lmrc_config_validator::LmrcConfig;
8use std::fs;
9use std::path::{Path, PathBuf};
10
11use crate::error::Result;
12
13pub struct ProjectGenerator {
14    config: LmrcConfig,
15    project_path: PathBuf,
16}
17
18impl ProjectGenerator {
19    pub fn new(config: LmrcConfig, project_path: PathBuf) -> Self {
20        Self {
21            config,
22            project_path,
23        }
24    }
25
26    pub fn project_path(&self) -> &Path {
27        &self.project_path
28    }
29
30    pub async fn generate(&self) -> Result<()> {
31        println!("{}", "Generating project structure...".yellow());
32
33        // Create root directory
34        fs::create_dir_all(&self.project_path)?;
35
36        // Create directory structure
37        self.create_directory_structure()?;
38
39        // Generate workspace Cargo.toml
40        workspace::generate_workspace_toml(&self.project_path, &self.config)?;
41
42        // Generate configuration file
43        self.generate_config_file()?;
44
45        // Generate applications
46        self.generate_applications()?;
47
48        // Generate libs directory with placeholders
49        self.generate_libs()?;
50
51        // Generate pipeline application
52        pipeline::generate_pipeline_app(&self.project_path, &self.config)?;
53
54        // Generate GitLab CI configuration
55        gitlab_ci::generate_gitlab_ci(&self.project_path, &self.config)?;
56
57        // Generate documentation
58        documentation::generate_docs(&self.project_path, &self.config)?;
59
60        // Generate Docker files
61        self.generate_docker_files()?;
62
63        // Generate .gitignore
64        self.generate_gitignore()?;
65
66        Ok(())
67    }
68
69    fn create_directory_structure(&self) -> Result<()> {
70        let dirs = vec!["apps", "libs", "docs", "docker", "infra"];
71
72        for dir in dirs {
73            let path = self.project_path.join(dir);
74            fs::create_dir_all(&path)?;
75            println!("  {} {}", "Created:".green(), path.display());
76        }
77
78        Ok(())
79    }
80
81    fn generate_config_file(&self) -> Result<()> {
82        let config_content = self.config.to_toml_string()?;
83        let config_path = self.project_path.join("lmrc.toml");
84        fs::write(&config_path, config_content)?;
85        println!("  {} {}", "Created:".green(), config_path.display());
86        Ok(())
87    }
88
89    fn generate_applications(&self) -> Result<()> {
90        use lmrc_toml_writer::PackageToml;
91
92        for app in &self.config.apps.applications {
93            let app_path = self.project_path.join("apps").join(&app.name);
94            fs::create_dir_all(&app_path)?;
95
96            // Create a basic Rust binary project using PackageToml builder
97            let cargo_toml = PackageToml::new(&app.name)
98                .version("0.1.0")
99                .edition("2021")
100                .dependency_inline("tokio", r#"{ version = "1.0", features = ["full"] }"#)
101                .build();
102
103            fs::write(app_path.join("Cargo.toml"), cargo_toml)?;
104
105            // Create src directory and main.rs
106            let src_dir = app_path.join("src");
107            fs::create_dir_all(&src_dir)?;
108
109            let main_rs = r#"#[tokio::main]
110async fn main() {
111    println!("Hello from {}!");
112}
113"#;
114
115            fs::write(src_dir.join("main.rs"), main_rs)?;
116            println!("  {} apps/{}", "Created:".green(), app.name);
117        }
118
119        Ok(())
120    }
121
122    fn generate_libs(&self) -> Result<()> {
123        let libs_path = self.project_path.join("libs");
124        let readme = r#"# Shared Libraries
125
126This directory contains shared libraries used across multiple applications.
127
128Place reusable code, utilities, and shared logic here.
129"#;
130        fs::write(libs_path.join("README.md"), readme)?;
131        println!("  {} libs/README.md", "Created:".green());
132        Ok(())
133    }
134
135    fn generate_docker_files(&self) -> Result<()> {
136        let docker_path = self.project_path.join("docker");
137
138        // Generate Dockerfile template for applications
139        let dockerfile = r#"# Build stage
140FROM rust:1.75-slim as builder
141
142WORKDIR /app
143COPY . .
144
145RUN cargo build --release
146
147# Runtime stage
148FROM debian:bookworm-slim
149
150RUN apt-get update && \
151    apt-get install -y ca-certificates && \
152    rm -rf /var/lib/apt/lists/*
153
154WORKDIR /app
155
156COPY --from=builder /app/target/release/app /app/app
157
158ENTRYPOINT ["/app/app"]
159"#;
160
161        fs::write(docker_path.join("Dockerfile"), dockerfile)?;
162        println!("  {} docker/Dockerfile", "Created:".green());
163
164        // Generate docker-compose.yml for local development
165        let docker_compose = format!(
166            r#"version: '3.8'
167
168services:
169  postgres:
170    image: postgres:{}
171    environment:
172      POSTGRES_DB: {}
173      POSTGRES_USER: postgres
174      POSTGRES_PASSWORD: postgres
175    ports:
176      - "5432:5432"
177    volumes:
178      - postgres_data:/var/lib/postgresql/data
179
180volumes:
181  postgres_data:
182"#,
183            self.config
184                .infrastructure
185                .postgres
186                .as_ref()
187                .map(|p| p.version.as_str())
188                .unwrap_or("16"),
189            self.config
190                .infrastructure
191                .postgres
192                .as_ref()
193                .map(|p| p.database_name.as_str())
194                .unwrap_or("myapp")
195        );
196
197        fs::write(docker_path.join("docker-compose.yml"), docker_compose)?;
198        println!("  {} docker/docker-compose.yml", "Created:".green());
199
200        Ok(())
201    }
202
203    fn generate_gitignore(&self) -> Result<()> {
204        let gitignore = r#"# Rust
205/target/
206**/*.rs.bk
207*.pdb
208Cargo.lock
209
210# IDE
211.idea/
212.vscode/
213*.swp
214*.swo
215*~
216
217# OS
218.DS_Store
219Thumbs.db
220
221# Environment
222.env
223.env.local
224
225# Secrets
226secrets/
227*.pem
228*.key
229"#;
230
231        let gitignore_path = self.project_path.join(".gitignore");
232        fs::write(&gitignore_path, gitignore)?;
233        println!("  {} .gitignore", "Created:".green());
234
235        Ok(())
236    }
237}