UNPKG

express-hale

Version:

🚀 Interactive Express.js scaffold CLI with comprehensive error handling, TypeScript/JavaScript, database integrations, Git Flow, and development tools

328 lines 15.4 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProjectGenerator = void 0; const fs_extra_1 = __importDefault(require("fs-extra")); const path_1 = __importDefault(require("path")); const template_renderer_1 = require("./template-renderer"); const child_process_1 = require("child_process"); class ProjectGenerator { constructor(projectName, projectPath, config, cliVersion) { this.projectName = projectName; this.projectPath = projectPath; this.config = config; this.templateRenderer = new template_renderer_1.TemplateRenderer(); this.cliVersion = cliVersion; } async generate() { // Create project directory await fs_extra_1.default.ensureDir(this.projectPath); // Generate template data const templateData = this.generateTemplateData(); // Create directory structure await this.createDirectoryStructure(); // Generate package.json await this.generatePackageJson(templateData); // Generate main application files await this.generateMainFiles(templateData); // Generate database configurations await this.generateDatabaseConfigs(); // Generate configuration files await this.generateConfigFiles(); // Generate Docker files if requested if (this.config.docker) { await this.generateDockerFiles(); } // Initialize git repository if requested if (this.config.gitInit) { await this.initializeGit(); // Initialize Git Flow if requested if (this.config.gitFlow) { await this.initializeGitFlow(); } } } generateTemplateData() { const dependencies = ['express', 'cors', 'helmet', 'morgan', 'dotenv']; const devDependencies = ['nodemon']; // Add language-specific dependencies if (this.config.language === 'typescript') { dependencies.push('@types/express', '@types/cors', '@types/morgan'); devDependencies.push('typescript', 'ts-node', '@types/node'); } // Add database dependencies this.config.databases.forEach((db) => { switch (db) { case 'mysql': dependencies.push('mysql2'); break; case 'mongodb': dependencies.push('mongoose'); if (this.config.language === 'typescript') { devDependencies.push('@types/mongoose'); } break; case 'redis': dependencies.push('redis'); break; } }); // Add linting and formatting dependencies if (this.config.eslint) { devDependencies.push('eslint'); if (this.config.language === 'typescript') { devDependencies.push('@typescript-eslint/parser', '@typescript-eslint/eslint-plugin'); } } if (this.config.prettier) { devDependencies.push('prettier'); if (this.config.eslint) { devDependencies.push('eslint-config-prettier', 'eslint-plugin-prettier'); } } // Add testing dependencies if (this.config.testing === 'jest') { devDependencies.push('jest', 'supertest'); if (this.config.language === 'typescript') { devDependencies.push('@types/jest', '@types/supertest', 'ts-jest'); } } else if (this.config.testing === 'mocha') { devDependencies.push('mocha', 'chai', 'supertest'); if (this.config.language === 'typescript') { devDependencies.push('@types/mocha', '@types/chai', '@types/supertest'); } } // Generate scripts const scripts = { start: this.config.language === 'typescript' ? 'node dist/index.js' : 'node src/index.js', dev: this.config.language === 'typescript' ? 'nodemon --exec ts-node src/index.ts' : 'nodemon src/index.js', }; if (this.config.language === 'typescript') { scripts.build = 'tsc'; } if (this.config.eslint) { scripts.lint = this.config.language === 'typescript' ? 'eslint src --ext .ts' : 'eslint src --ext .js'; scripts['lint:fix'] = scripts.lint + ' --fix'; } if (this.config.prettier) { scripts.format = this.config.language === 'typescript' ? 'prettier --write "src/**/*.ts"' : 'prettier --write "src/**/*.js"'; } if (this.config.testing !== 'none') { scripts.test = this.config.testing; scripts['test:watch'] = this.config.testing + ' --watch'; } // Add Git Flow scripts if enabled if (this.config.gitFlow) { const gitFlowScripts = this.templateRenderer.renderGitFlowPackageScripts(); Object.assign(scripts, gitFlowScripts); } return { projectName: this.projectName, config: this.config, dependencies, devDependencies, scripts, }; } async createDirectoryStructure() { const dirs = [ 'src', 'src/controllers', 'src/middleware', 'src/routes', 'src/services', 'src/models', 'src/utils', 'src/config', ]; if (this.config.testing !== 'none') { dirs.push('tests'); } for (const dir of dirs) { await fs_extra_1.default.ensureDir(path_1.default.join(this.projectPath, dir)); } } async generatePackageJson(templateData) { const packageJson = { name: templateData.projectName, version: this.cliVersion, description: 'Express.js application generated with Express Hale CLI', main: this.config.language === 'typescript' ? 'dist/index.js' : 'src/index.js', scripts: templateData.scripts, dependencies: this.arrayToObject(templateData.dependencies), devDependencies: this.arrayToObject(templateData.devDependencies), keywords: ['express', 'nodejs', 'api'], author: '', license: 'MIT', }; await fs_extra_1.default.writeJSON(path_1.default.join(this.projectPath, 'package.json'), packageJson, { spaces: 2 }); } async generateMainFiles(templateData) { const ext = this.config.language === 'typescript' ? 'ts' : 'js'; // Generate main index file const indexTemplate = this.templateRenderer.renderIndexFile(templateData); await fs_extra_1.default.writeFile(path_1.default.join(this.projectPath, `src/index.${ext}`), indexTemplate); // Generate route files const routeTemplate = this.templateRenderer.renderRouteFile(templateData); await fs_extra_1.default.writeFile(path_1.default.join(this.projectPath, `src/routes/index.${ext}`), routeTemplate); // Generate controllers directory await fs_extra_1.default.ensureDir(path_1.default.join(this.projectPath, 'src/controllers')); // Generate error handling middleware const errorHandlerTemplate = this.templateRenderer.renderErrorHandlerMiddleware(this.config.language); await fs_extra_1.default.writeFile(path_1.default.join(this.projectPath, `src/middleware/error-handler.${ext}`), errorHandlerTemplate); // Generate graceful shutdown utility const gracefulShutdownTemplate = this.templateRenderer.renderGracefulShutdownUtil(this.config.language); await fs_extra_1.default.writeFile(path_1.default.join(this.projectPath, `src/utils/graceful-shutdown.${ext}`), gracefulShutdownTemplate); // Generate error monitoring configuration const errorMonitoringTemplate = this.templateRenderer.renderErrorMonitoringConfig(this.config.language); await fs_extra_1.default.writeFile(path_1.default.join(this.projectPath, `src/utils/error-monitor.${ext}`), errorMonitoringTemplate); } async generateDatabaseConfigs() { const ext = this.config.language === 'typescript' ? 'ts' : 'js'; for (const db of this.config.databases) { const configTemplate = this.templateRenderer.renderDatabaseConfig(db, this.config.language); await fs_extra_1.default.writeFile(path_1.default.join(this.projectPath, `src/config/${db}.${ext}`), configTemplate); } } async generateConfigFiles() { // Generate .env file const envTemplate = this.templateRenderer.renderEnvFile(this.config); await fs_extra_1.default.writeFile(path_1.default.join(this.projectPath, '.env'), envTemplate); // Generate .env.example file const envExampleTemplate = this.templateRenderer.renderEnvExampleFile(this.config); await fs_extra_1.default.writeFile(path_1.default.join(this.projectPath, '.env.example'), envExampleTemplate); // Generate TypeScript config if needed if (this.config.language === 'typescript') { const tsConfigTemplate = this.templateRenderer.renderTsConfig(); await fs_extra_1.default.writeJSON(path_1.default.join(this.projectPath, 'tsconfig.json'), JSON.parse(tsConfigTemplate), { spaces: 2 }); } // Generate ESLint config if needed if (this.config.eslint) { const eslintTemplate = this.templateRenderer.renderEslintConfig(this.config); await fs_extra_1.default.writeFile(path_1.default.join(this.projectPath, 'eslint.config.js'), eslintTemplate); } // Generate Prettier config if needed if (this.config.prettier) { const prettierTemplate = this.templateRenderer.renderPrettierConfig(); await fs_extra_1.default.writeJSON(path_1.default.join(this.projectPath, '.prettierrc'), JSON.parse(prettierTemplate), { spaces: 2 }); } // Generate Jest config if needed if (this.config.testing === 'jest') { const jestTemplate = this.templateRenderer.renderJestConfig(this.config); await fs_extra_1.default.writeFile(path_1.default.join(this.projectPath, 'jest.config.js'), jestTemplate); } // Generate .gitignore const gitignoreTemplate = this.templateRenderer.renderGitignore(); await fs_extra_1.default.writeFile(path_1.default.join(this.projectPath, '.gitignore'), gitignoreTemplate); // Generate README.md const readmeTemplate = this.templateRenderer.renderReadme(this.projectName, this.config); await fs_extra_1.default.writeFile(path_1.default.join(this.projectPath, 'README.md'), readmeTemplate); // Generate Git Flow documentation if enabled if (this.config.gitFlow) { const gitFlowInfo = this.templateRenderer.renderGitFlowInfo(); await fs_extra_1.default.writeFile(path_1.default.join(this.projectPath, 'GIT_FLOW.md'), gitFlowInfo); } // Generate nodemon config for TypeScript projects if (this.config.language === 'typescript') { const nodemonTemplate = this.templateRenderer.renderNodemonConfig(); await fs_extra_1.default.writeJSON(path_1.default.join(this.projectPath, 'nodemon.json'), JSON.parse(nodemonTemplate), { spaces: 2 }); } // Generate test files if (this.config.testing === 'jest') { await this.generateTestFiles(); } } async generateDockerFiles() { const dockerfileTemplate = this.templateRenderer.renderDockerfile(this.config); await fs_extra_1.default.writeFile(path_1.default.join(this.projectPath, 'Dockerfile'), dockerfileTemplate); const dockerComposeTemplate = this.templateRenderer.renderDockerCompose(this.config); await fs_extra_1.default.writeFile(path_1.default.join(this.projectPath, 'docker-compose.yml'), dockerComposeTemplate); } async initializeGit() { return new Promise((resolve, reject) => { const git = (0, child_process_1.spawn)('git', ['init'], { cwd: this.projectPath }); git.on('close', (code) => { if (code === 0) { resolve(); } else { reject(new Error(`Git initialization failed with code ${code}`)); } }); }); } async initializeGitFlow() { // Check if git flow is available const isGitFlowAvailable = await this.checkGitFlowAvailable(); if (!isGitFlowAvailable) { console.warn('⚠️ Git Flow is not installed. Skipping Git Flow initialization.'); console.warn(' Install git-flow: brew install git-flow-avh (macOS) or apt-get install git-flow (Ubuntu)'); return; } // Initialize git flow with default branch names return new Promise((resolve, reject) => { const gitFlow = (0, child_process_1.spawn)('git', ['flow', 'init', '-d'], { cwd: this.projectPath, stdio: 'pipe', }); gitFlow.on('close', (code) => { if (code === 0) { console.log('✅ Git Flow initialized successfully'); resolve(); } else { reject(new Error(`Git Flow initialization failed with code ${code}`)); } }); gitFlow.on('error', (error) => { reject(new Error(`Git Flow initialization failed: ${error.message}`)); }); }); } async checkGitFlowAvailable() { return new Promise((resolve) => { const gitFlow = (0, child_process_1.spawn)('git', ['flow', 'version'], { cwd: this.projectPath, stdio: 'pipe', }); gitFlow.on('close', (code) => { resolve(code === 0); }); gitFlow.on('error', () => { resolve(false); }); }); } async generateTestFiles() { const ext = this.config.language === 'typescript' ? 'ts' : 'js'; // Generate basic test file const testTemplate = this.templateRenderer.renderTestFile(this.config); await fs_extra_1.default.writeFile(path_1.default.join(this.projectPath, `tests/app.test.${ext}`), testTemplate); // Generate API test file const apiTestTemplate = this.templateRenderer.renderApiTestFile(this.config); await fs_extra_1.default.writeFile(path_1.default.join(this.projectPath, `tests/api.test.${ext}`), apiTestTemplate); } arrayToObject(arr) { return arr.reduce((acc, item) => { acc[item] = 'latest'; return acc; }, {}); } } exports.ProjectGenerator = ProjectGenerator; //# sourceMappingURL=generator.js.map