lmrc_cli/generator/
mod.rs1pub mod apps;
2pub mod documentation;
3pub mod gitlab_ci;
4pub mod pipeline;
5pub mod workspace;
6
7use colored::Colorize;
8use lmrc_config_validator::LmrcConfig;
9use std::fs;
10use std::path::{Path, PathBuf};
11
12use crate::error::Result;
13
14pub struct ProjectGenerator {
15 config: LmrcConfig,
16 project_path: PathBuf,
17}
18
19impl ProjectGenerator {
20 pub fn new(config: LmrcConfig, project_path: PathBuf) -> Self {
21 Self {
22 config,
23 project_path,
24 }
25 }
26
27 pub fn project_path(&self) -> &Path {
28 &self.project_path
29 }
30
31 pub async fn generate(&self) -> Result<()> {
32 println!("{}", "Generating project structure...".yellow());
33
34 fs::create_dir_all(&self.project_path)?;
36
37 self.create_directory_structure()?;
39
40 self.generate_config_file()?;
42
43 self.generate_applications()?;
45
46 self.generate_libs()?;
48
49 pipeline::generate_pipeline_app(&self.project_path, &self.config)?;
51
52 workspace::generate_workspace_toml(&self.project_path, &self.config)?;
54
55 gitlab_ci::generate_gitlab_ci(&self.project_path, &self.config)?;
57
58 documentation::generate_docs(&self.project_path, &self.config)?;
60
61 self.generate_docker_files()?;
63
64 self.generate_gitignore()?;
66
67 Ok(())
68 }
69
70 pub async fn generate_minimal(&self) -> Result<()> {
71 println!("{}", "Generating minimal project structure...".yellow());
72
73 fs::create_dir_all(&self.project_path)?;
75
76 self.create_directory_structure()?;
78
79 self.generate_config_file()?;
83
84 self.generate_libs()?;
86
87 self.generate_docker_files()?;
89
90 self.generate_gitignore()?;
92
93 Ok(())
94 }
95
96 fn create_directory_structure(&self) -> Result<()> {
97 let dirs = vec!["apps", "libs", "docs", "docker", "infra"];
98
99 for dir in dirs {
100 let path = self.project_path.join(dir);
101 fs::create_dir_all(&path)?;
102 println!(" {} {}", "Created:".green(), path.display());
103 }
104
105 Ok(())
106 }
107
108 fn generate_config_file(&self) -> Result<()> {
109 let config_content = self.config.to_toml_string()?;
110 let config_path = self.project_path.join("lmrc.toml");
111 fs::write(&config_path, config_content)?;
112 println!(" {} {}", "Created:".green(), config_path.display());
113 Ok(())
114 }
115
116 fn generate_applications(&self) -> Result<()> {
117 apps::generate_applications(&self.project_path, &self.config)?;
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 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 let docker_compose = self.generate_docker_compose()?;
166 fs::write(docker_path.join("docker-compose.yml"), docker_compose)?;
167 println!(" {} docker/docker-compose.yml", "Created:".green());
168
169 Ok(())
170 }
171
172 fn generate_docker_compose(&self) -> Result<String> {
173 let mut services = String::new();
174 let mut volumes = String::new();
175
176 if self.config.infrastructure.postgres.is_some() {
178 let pg_version = self.config
179 .infrastructure
180 .postgres
181 .as_ref()
182 .map(|p| p.version.as_str())
183 .unwrap_or("16");
184 let pg_db = self.config
185 .infrastructure
186 .postgres
187 .as_ref()
188 .map(|p| p.database_name.as_str())
189 .unwrap_or("myapp");
190
191 services.push_str(&format!(
192 r#" postgres:
193 image: postgres:{}
194 container_name: {}-postgres
195 environment:
196 POSTGRES_DB: {}
197 POSTGRES_USER: postgres
198 POSTGRES_PASSWORD: postgres
199 ports:
200 - "5432:5432"
201 volumes:
202 - postgres_data:/var/lib/postgresql/data
203 healthcheck:
204 test: ["CMD-SHELL", "pg_isready -U postgres"]
205 interval: 10s
206 timeout: 5s
207 retries: 5
208 networks:
209 - lmrc-network
210
211"#,
212 pg_version, self.config.project.name, pg_db
213 ));
214 volumes.push_str(" postgres_data:\n");
215 }
216
217 if self.config.infrastructure.rabbitmq.is_some() {
219 services.push_str(&format!(
220 r#" rabbitmq:
221 image: rabbitmq:3-management
222 container_name: {}-rabbitmq
223 environment:
224 RABBITMQ_DEFAULT_USER: guest
225 RABBITMQ_DEFAULT_PASS: guest
226 ports:
227 - "5672:5672" # AMQP port
228 - "15672:15672" # Management UI
229 volumes:
230 - rabbitmq_data:/var/lib/rabbitmq
231 healthcheck:
232 test: ["CMD", "rabbitmq-diagnostics", "ping"]
233 interval: 10s
234 timeout: 5s
235 retries: 5
236 networks:
237 - lmrc-network
238
239"#,
240 self.config.project.name
241 ));
242 volumes.push_str(" rabbitmq_data:\n");
243 }
244
245 if self.config.infrastructure.vault.is_some() {
247 services.push_str(&format!(
248 r#" vault:
249 image: hashicorp/vault:latest
250 container_name: {}-vault
251 environment:
252 VAULT_DEV_ROOT_TOKEN_ID: root
253 VAULT_DEV_LISTEN_ADDRESS: 0.0.0.0:8200
254 ports:
255 - "8200:8200"
256 cap_add:
257 - IPC_LOCK
258 networks:
259 - lmrc-network
260 command: server -dev
261
262"#,
263 self.config.project.name
264 ));
265 }
266
267 services.push_str(&format!(
269 r#" redis:
270 image: redis:7-alpine
271 container_name: {}-redis
272 ports:
273 - "6379:6379"
274 volumes:
275 - redis_data:/data
276 healthcheck:
277 test: ["CMD", "redis-cli", "ping"]
278 interval: 10s
279 timeout: 5s
280 retries: 5
281 networks:
282 - lmrc-network
283
284"#,
285 self.config.project.name
286 ));
287 volumes.push_str(" redis_data:\n");
288
289 let mut compose = String::from("version: '3.8'\n\nservices:\n");
291 compose.push_str(&services);
292
293 if !volumes.is_empty() {
294 compose.push_str("\nvolumes:\n");
295 compose.push_str(&volumes);
296 }
297
298 compose.push_str(
299 r#"
300networks:
301 lmrc-network:
302 driver: bridge
303"#,
304 );
305
306 Ok(compose)
307 }
308
309 fn generate_gitignore(&self) -> Result<()> {
310 let gitignore = r#"# Rust
311/target/
312**/*.rs.bk
313*.pdb
314Cargo.lock
315
316# IDE
317.idea/
318.vscode/
319*.swp
320*.swo
321*~
322
323# OS
324.DS_Store
325Thumbs.db
326
327# Environment
328.env
329.env.local
330
331# Secrets
332secrets/
333.secrets/
334*.pem
335*.key
336
337# SSH Keys (project-local)
338.ssh/
339*.pub
340"#;
341
342 let gitignore_path = self.project_path.join(".gitignore");
343 fs::write(&gitignore_path, gitignore)?;
344 println!(" {} .gitignore", "Created:".green());
345
346 Ok(())
347 }
348}