node-project-gen 0.0.4

⚡ Blazing fast, architecture-aware Node.js backend generator. Scaffold production-ready projects in under 1 second.
use crate::cli::*;
use crate::generator::ProjectConfig;
use std::path::PathBuf;

pub fn generate(
    project_path: &PathBuf,
    config: &ProjectConfig,
) -> Result<(), Box<dyn std::error::Error>> {
    let deps = get_dependencies(config);
    let dev_deps = get_dev_dependencies(config);
    let scripts = get_scripts(config);

    let package_json = format!(
        r#"{{
  "name": "{}",
  "version": "1.0.0",
  "description": "Generated by Acrocoder CLI ⚡",
  "main": "dist/server.js",
  "scripts": {{
{}
  }},
  "keywords": [],
  "author": "",
  "license": "MIT",
  "dependencies": {{
{}
  }},
  "devDependencies": {{
{}
  }}
}}
"#,
        config.name,
        scripts
            .iter()
            .map(|(k, v)| format!("    \"{}\": \"{}\"", k, v.replace("\"", "\\\"")))
            .collect::<Vec<_>>()
            .join(",\n"),
        deps.iter()
            .map(|(k, v)| format!("    \"{}\": \"{}\"", k, v))
            .collect::<Vec<_>>()
            .join(",\n"),
        dev_deps
            .iter()
            .map(|(k, v)| format!("    \"{}\": \"{}\"", k, v))
            .collect::<Vec<_>>()
            .join(",\n"),
    );

    std::fs::write(project_path.join("package.json"), package_json)?;
    Ok(())
}

fn get_scripts(config: &ProjectConfig) -> Vec<(&str, String)> {
    let mut scripts: Vec<(&str, String)> = vec![
        ("dev", "ts-node-dev --respawn --transpile-only src/server.ts".to_string()),
        ("build", "tsc".to_string()),
        ("start", "node dist/server.js".to_string()),
        ("lint", "eslint src/ --ext .ts".to_string()),
        ("lint:fix", "eslint src/ --ext .ts --fix".to_string()),
        ("format", "prettier --write \"src/**/*.ts\"".to_string()),
    ];

    match config.test {
        TestFramework::Jest => {
            scripts.push(("test", "jest --passWithNoTests".to_string()));
            scripts.push(("test:watch", "jest --watch".to_string()));
            scripts.push(("test:coverage", "jest --coverage".to_string()));
        }
        TestFramework::Vitest => {
            scripts.push(("test", "vitest run".to_string()));
            scripts.push(("test:watch", "vitest".to_string()));
            scripts.push(("test:coverage", "vitest --coverage".to_string()));
        }
    }

    if config.db.is_some() && config.resolve_orm() == Some(Orm::Prisma) {
        scripts.push(("prisma:generate", "prisma generate".to_string()));
        scripts.push(("prisma:migrate", "prisma migrate dev".to_string()));
        scripts.push(("prisma:studio", "prisma studio".to_string()));
    }

    scripts
}

fn get_dependencies(config: &ProjectConfig) -> Vec<(&str, &str)> {
    let mut deps: Vec<(&str, &str)> = vec![
        ("dotenv", "^16.4.7"),
        ("cors", "^2.8.5"),
        ("helmet", "^8.0.0"),
        ("hpp", "^0.2.3"),
        ("compression", "^1.7.4"),
        ("express-rate-limit", "^7.5.0"),
        ("http-status-codes", "^2.3.0"),
    ];

    // Framework
    match config.framework {
        Framework::Express => {
            deps.push(("express", "^4.21.2"));
        }
        Framework::Fastify => {
            deps.push(("fastify", "^5.2.1"));
            deps.push(("@fastify/cors", "^10.0.2"));
            deps.push(("@fastify/helmet", "^13.0.1"));
            deps.push(("@fastify/rate-limit", "^10.2.1"));
            deps.push(("@fastify/swagger", "^9.4.2"));
            deps.push(("@fastify/swagger-ui", "^5.2.2"));
        }
        Framework::Nest => {
            deps.push(("@nestjs/core", "^10.4.15"));
            deps.push(("@nestjs/common", "^10.4.15"));
            deps.push(("@nestjs/platform-express", "^10.4.15"));
            deps.push(("reflect-metadata", "^0.2.2"));
            deps.push(("rxjs", "^7.8.1"));
        }
    }

    // Logger
    match config.logger {
        LoggerLib::Winston => {
            deps.push(("winston", "^3.17.0"));
        }
        LoggerLib::Pino => {
            deps.push(("pino", "^9.6.0"));
            deps.push(("pino-pretty", "^13.0.0"));
        }
    }

    // Validation
    match config.validation {
        ValidationLib::Zod => {
            deps.push(("zod", "^3.24.2"));
        }
        ValidationLib::Joi => {
            deps.push(("joi", "^17.13.3"));
        }
    }

    // Database & ORM
    if let Some(db) = config.db {
        match config.resolve_orm() {
            Some(Orm::Mongoose) => {
                deps.push(("mongoose", "^8.10.1"));
            }
            Some(Orm::Prisma) => {
                deps.push(("@prisma/client", "^6.4.1"));
            }
            Some(Orm::Sequelize) => {
                deps.push(("sequelize", "^6.37.5"));
                match db {
                    Database::Postgres => deps.push(("pg", "^8.13.1")),
                    Database::Mysql => deps.push(("mysql2", "^3.12.0")),
                    Database::Sqlite => deps.push(("sqlite3", "^5.1.7")),
                    _ => {}
                }
            }
            Some(Orm::Typeorm) => {
                deps.push(("typeorm", "^0.3.20"));
                match db {
                    Database::Postgres => deps.push(("pg", "^8.13.1")),
                    Database::Mysql => deps.push(("mysql2", "^3.12.0")),
                    Database::Sqlite => deps.push(("better-sqlite3", "^11.8.1")),
                    _ => {}
                }
            }
            None => {}
        }
    }

    // Auth
    if let Some(auth) = config.auth {
        match auth {
            AuthStrategy::Jwt => {
                deps.push(("jsonwebtoken", "^9.0.2"));
                deps.push(("bcryptjs", "^2.4.3"));
            }
            AuthStrategy::Session => {
                deps.push(("express-session", "^1.18.1"));
                deps.push(("connect-redis", "^8.0.1"));
                deps.push(("bcryptjs", "^2.4.3"));
            }
            AuthStrategy::Oauth => {
                deps.push(("passport", "^0.7.0"));
                deps.push(("passport-google-oauth20", "^2.0.0"));
                deps.push(("passport-github2", "^0.1.12"));
                deps.push(("jsonwebtoken", "^9.0.2"));
            }
            AuthStrategy::Firebase => {
                deps.push(("firebase-admin", "^13.0.2"));
            }
        }
    }

    // Swagger for Express
    if config.framework == Framework::Express {
        deps.push(("swagger-jsdoc", "^6.2.8"));
        deps.push(("swagger-ui-express", "^5.0.1"));
    }

    deps
}

fn get_dev_dependencies(config: &ProjectConfig) -> Vec<(&str, &str)> {
    let mut dev_deps: Vec<(&str, &str)> = vec![
        ("typescript", "^5.7.3"),
        ("ts-node-dev", "^2.0.0"),
        ("@types/node", "^22.13.4"),
        ("eslint", "^9.20.0"),
        ("@typescript-eslint/parser", "^8.24.1"),
        ("@typescript-eslint/eslint-plugin", "^8.24.1"),
        ("prettier", "^3.5.2"),
    ];

    match config.framework {
        Framework::Express => {
            dev_deps.push(("@types/express", "^5.0.0"));
            dev_deps.push(("@types/cors", "^2.8.17"));
            dev_deps.push(("@types/compression", "^1.7.5"));
            dev_deps.push(("@types/hpp", "^0.2.6"));
            dev_deps.push(("@types/swagger-jsdoc", "^6.0.4"));
            dev_deps.push(("@types/swagger-ui-express", "^4.1.7"));
        }
        _ => {}
    }

    match config.test {
        TestFramework::Jest => {
            dev_deps.push(("jest", "^29.7.0"));
            dev_deps.push(("ts-jest", "^29.2.5"));
            dev_deps.push(("@types/jest", "^29.5.14"));
        }
        TestFramework::Vitest => {
            dev_deps.push(("vitest", "^3.0.5"));
        }
    }

    if config.resolve_orm() == Some(Orm::Prisma) {
        dev_deps.push(("prisma", "^6.4.1"));
    }

    if let Some(auth) = config.auth {
        match auth {
            AuthStrategy::Jwt => {
                dev_deps.push(("@types/jsonwebtoken", "^9.0.7"));
                dev_deps.push(("@types/bcryptjs", "^2.4.6"));
            }
            AuthStrategy::Session => {
                dev_deps.push(("@types/express-session", "^1.18.1"));
                dev_deps.push(("@types/bcryptjs", "^2.4.6"));
            }
            AuthStrategy::Oauth => {
                dev_deps.push(("@types/passport", "^1.0.17"));
                dev_deps.push(("@types/passport-google-oauth20", "^2.0.16"));
            }
            _ => {}
        }
    }

    dev_deps
}