pub mod apps;
pub mod documentation;
pub mod gitlab_ci;
pub mod pipeline;
pub mod workspace;
use colored::Colorize;
use lmrc_config_validator::LmrcConfig;
use std::fs;
use std::path::{Path, PathBuf};
use crate::error::Result;
pub struct ProjectGenerator {
config: LmrcConfig,
project_path: PathBuf,
}
impl ProjectGenerator {
pub fn new(config: LmrcConfig, project_path: PathBuf) -> Self {
Self {
config,
project_path,
}
}
pub fn project_path(&self) -> &Path {
&self.project_path
}
pub async fn generate(&self) -> Result<()> {
println!("{}", "Generating project structure...".yellow());
fs::create_dir_all(&self.project_path)?;
self.create_directory_structure()?;
self.generate_config_file()?;
self.generate_applications()?;
self.generate_libs()?;
pipeline::generate_pipeline_app(&self.project_path, &self.config)?;
workspace::generate_workspace_toml(&self.project_path, &self.config)?;
gitlab_ci::generate_gitlab_ci(&self.project_path, &self.config)?;
documentation::generate_docs(&self.project_path, &self.config)?;
self.generate_docker_files()?;
self.generate_gitignore()?;
Ok(())
}
pub async fn generate_minimal(&self) -> Result<()> {
println!("{}", "Generating minimal project structure...".yellow());
fs::create_dir_all(&self.project_path)?;
self.create_directory_structure()?;
self.generate_config_file()?;
self.generate_libs()?;
self.generate_docker_files()?;
self.generate_gitignore()?;
Ok(())
}
fn create_directory_structure(&self) -> Result<()> {
let dirs = vec!["apps", "libs", "docs", "docker", "infra"];
for dir in dirs {
let path = self.project_path.join(dir);
fs::create_dir_all(&path)?;
println!(" {} {}", "Created:".green(), path.display());
}
Ok(())
}
fn generate_config_file(&self) -> Result<()> {
let config_content = self.config.to_toml_string()?;
let config_path = self.project_path.join("lmrc.toml");
fs::write(&config_path, config_content)?;
println!(" {} {}", "Created:".green(), config_path.display());
Ok(())
}
fn generate_applications(&self) -> Result<()> {
apps::generate_applications(&self.project_path, &self.config)?;
Ok(())
}
fn generate_libs(&self) -> Result<()> {
let libs_path = self.project_path.join("libs");
let readme = r#"# Shared Libraries
This directory contains shared libraries used across multiple applications.
Place reusable code, utilities, and shared logic here.
"#;
fs::write(libs_path.join("README.md"), readme)?;
println!(" {} libs/README.md", "Created:".green());
Ok(())
}
fn generate_docker_files(&self) -> Result<()> {
let docker_path = self.project_path.join("docker");
let dockerfile = r#"# Build stage
FROM rust:1.75-slim as builder
WORKDIR /app
COPY . .
RUN cargo build --release
# Runtime stage
FROM debian:bookworm-slim
RUN apt-get update && \
apt-get install -y ca-certificates && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /app/target/release/app /app/app
ENTRYPOINT ["/app/app"]
"#;
fs::write(docker_path.join("Dockerfile"), dockerfile)?;
println!(" {} docker/Dockerfile", "Created:".green());
let docker_compose = self.generate_docker_compose()?;
fs::write(docker_path.join("docker-compose.yml"), docker_compose)?;
println!(" {} docker/docker-compose.yml", "Created:".green());
Ok(())
}
fn generate_docker_compose(&self) -> Result<String> {
let mut services = String::new();
let mut volumes = String::new();
if self.config.infrastructure.postgres.is_some() {
let pg_version = self.config
.infrastructure
.postgres
.as_ref()
.map(|p| p.version.as_str())
.unwrap_or("16");
let pg_db = self.config
.infrastructure
.postgres
.as_ref()
.map(|p| p.database_name.as_str())
.unwrap_or("myapp");
services.push_str(&format!(
r#" postgres:
image: postgres:{}
container_name: {}-postgres
environment:
POSTGRES_DB: {}
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
networks:
- lmrc-network
"#,
pg_version, self.config.project.name, pg_db
));
volumes.push_str(" postgres_data:\n");
}
if self.config.infrastructure.rabbitmq.is_some() {
services.push_str(&format!(
r#" rabbitmq:
image: rabbitmq:3-management
container_name: {}-rabbitmq
environment:
RABBITMQ_DEFAULT_USER: guest
RABBITMQ_DEFAULT_PASS: guest
ports:
- "5672:5672" # AMQP port
- "15672:15672" # Management UI
volumes:
- rabbitmq_data:/var/lib/rabbitmq
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- lmrc-network
"#,
self.config.project.name
));
volumes.push_str(" rabbitmq_data:\n");
}
if self.config.infrastructure.vault.is_some() {
services.push_str(&format!(
r#" vault:
image: hashicorp/vault:latest
container_name: {}-vault
environment:
VAULT_DEV_ROOT_TOKEN_ID: root
VAULT_DEV_LISTEN_ADDRESS: 0.0.0.0:8200
ports:
- "8200:8200"
cap_add:
- IPC_LOCK
networks:
- lmrc-network
command: server -dev
"#,
self.config.project.name
));
}
services.push_str(&format!(
r#" redis:
image: redis:7-alpine
container_name: {}-redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- lmrc-network
"#,
self.config.project.name
));
volumes.push_str(" redis_data:\n");
let mut compose = String::from("version: '3.8'\n\nservices:\n");
compose.push_str(&services);
if !volumes.is_empty() {
compose.push_str("\nvolumes:\n");
compose.push_str(&volumes);
}
compose.push_str(
r#"
networks:
lmrc-network:
driver: bridge
"#,
);
Ok(compose)
}
fn generate_gitignore(&self) -> Result<()> {
let gitignore = r#"# Rust
/target/
**/*.rs.bk
*.pdb
Cargo.lock
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Environment
.env
.env.local
# Secrets
secrets/
.secrets/
*.pem
*.key
# SSH Keys (project-local)
.ssh/
*.pub
"#;
let gitignore_path = self.project_path.join(".gitignore");
fs::write(&gitignore_path, gitignore)?;
println!(" {} .gitignore", "Created:".green());
Ok(())
}
}