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
JavaScript
"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