UNPKG

heyjarvis

Version:

J.A.R.V.I.S. - Advanced Node.js MVC Framework with ORM, built-in validation, soft delete, and query builder

537 lines (453 loc) 18.6 kB
const fs = require('fs'); const path = require('path'); const inquirer = require('inquirer'); const chalk = require('chalk'); const { execSync } = require('child_process'); const setupDatabase = async () => { try { // Check if we're in a J.A.R.V.I.S. project if (!fs.existsSync('package.json')) { throw new Error('Not in a Node.js project directory'); } const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8')); console.log(chalk.cyan('🔧 Database Configuration Setup\n')); // Interactive database configuration const answers = await inquirer.prompt([ { type: 'list', name: 'database', message: 'Select database type:', choices: [ { name: 'MySQL', value: 'mysql' }, { name: 'PostgreSQL', value: 'postgres' }, { name: 'SQLite', value: 'sqlite' } ] }, { type: 'input', name: 'host', message: 'Database host:', default: 'localhost', when: (answers) => answers.database !== 'sqlite' }, { type: 'input', name: 'port', message: 'Database port:', default: (answers) => answers.database === 'postgres' ? '5432' : '3306', when: (answers) => answers.database !== 'sqlite' }, { type: 'input', name: 'name', message: 'Database name:', default: `${packageJson.name}_db`, when: (answers) => answers.database !== 'sqlite' }, { type: 'input', name: 'username', message: 'Database username:', default: (answers) => answers.database === 'postgres' ? 'postgres' : 'root', when: (answers) => answers.database !== 'sqlite' }, { type: 'password', name: 'password', message: 'Database password:', when: (answers) => answers.database !== 'sqlite' }, { type: 'confirm', name: 'testConnection', message: 'Test database connection?', default: true } ]); console.log(chalk.cyan('\n📦 Installing database driver...')); // Install database driver await installDatabaseDriver(answers.database); // Update .env file await updateEnvFile(answers); // Update database config await updateDatabaseConfig(answers.database); // Test connection if requested if (answers.testConnection && answers.database !== 'sqlite') { await testDatabaseConnection(answers); } console.log(chalk.green('\n✅ Database configuration completed!')); } catch (error) { console.log(chalk.red('\n❌ Database setup failed:')); console.log(chalk.red(error.message)); throw error; } }; const installDatabaseDriver = async (database) => { const drivers = { mysql: 'mysql2', postgres: 'pg pg-hstore', sqlite: 'sqlite3' }; const driver = drivers[database]; if (!driver) { throw new Error(`Unknown database type: ${database}`); } console.log(chalk.cyan(`Installing ${database} driver...`)); try { execSync(`npm install ${driver}`, { stdio: 'pipe' }); console.log(chalk.green(`✅ ${database} driver installed`)); } catch (error) { console.log(chalk.red(`❌ Failed to install ${database} driver`)); throw new Error(`Failed to install ${database} driver: ${error.message}`); } }; const updateEnvFile = async (config) => { const envPath = '.env'; let envContent = ''; if (fs.existsSync(envPath)) { envContent = fs.readFileSync(envPath, 'utf8'); } // Remove existing database config envContent = envContent.replace(/# Database Configuration[\s\S]*?(?=\n\n|\n#|$)/g, ''); // Add new database config const dbConfig = ` # Database Configuration DB_HOST=${config.host || 'localhost'} DB_NAME=${config.name || 'jarvis_db'} DB_USER=${config.username || 'root'} DB_PASS=${config.password || ''} DB_PORT=${config.port || (config.database === 'postgres' ? '5432' : '3306')} DB_DIALECT=${config.database} `; envContent += dbConfig; fs.writeFileSync(envPath, envContent); console.log(chalk.green('✅ .env file updated')); }; const updateDatabaseConfig = async (database) => { const configPath = 'src/config/database.js'; if (!fs.existsSync(configPath)) { const dir = path.dirname(configPath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } } const configs = { mysql: `const { Sequelize } = require('sequelize'); const sequelize = new Sequelize( process.env.DB_NAME, process.env.DB_USER, process.env.DB_PASS, { host: process.env.DB_HOST, port: process.env.DB_PORT, dialect: 'mysql', timezone: '+03:00', logging: process.env.NODE_ENV === 'development' ? console.log : false, pool: { max: 10, min: 0, acquire: 30000, idle: 10000 }, define: { charset: 'utf8mb4', collate: 'utf8mb4_unicode_ci' } } ); module.exports = sequelize;`, postgres: `const { Sequelize } = require('sequelize'); const sequelize = new Sequelize( process.env.DB_NAME, process.env.DB_USER, process.env.DB_PASS, { host: process.env.DB_HOST, port: process.env.DB_PORT, dialect: 'postgres', logging: process.env.NODE_ENV === 'development' ? console.log : false, pool: { max: 10, min: 0, acquire: 30000, idle: 10000 } } ); module.exports = sequelize;`, sqlite: `const { Sequelize } = require('sequelize'); const path = require('path'); const sequelize = new Sequelize({ dialect: 'sqlite', storage: path.join(__dirname, '../../database/database.sqlite'), logging: process.env.NODE_ENV === 'development' ? console.log : false }); module.exports = sequelize;` }; const config = configs[database] || configs.mysql; fs.writeFileSync(configPath, config); console.log(chalk.green('✅ Database config updated')); }; const testDatabaseConnection = async (config) => { console.log(chalk.cyan('Testing database connection...')); console.log(chalk.gray(`Host: ${config.host}:${config.port}`)); console.log(chalk.gray(`Database: ${config.name}`)); console.log(chalk.gray(`User: ${config.username}`)); try { // Dynamic import to avoid module not found error let Sequelize; try { Sequelize = require('sequelize').Sequelize; } catch (importError) { console.log(chalk.yellow('⚠️ Sequelize not found in framework. Installing...')); await installFrameworkDependencies(['sequelize']); Sequelize = require('sequelize').Sequelize; } // Database driver'ının framework'te kurulu olduğundan emin ol try { if (config.database === 'mysql') { require('mysql2'); } else if (config.database === 'postgres') { require('pg'); } } catch (driverError) { console.log(chalk.yellow(`⚠️ ${config.database} driver not found in framework. Installing...`)); const driverPackages = { mysql: ['mysql2'], postgres: ['pg', 'pg-hstore'], sqlite: ['sqlite3'] }; await installFrameworkDependencies(driverPackages[config.database]); } const sequelize = new Sequelize( config.name, config.username, config.password, { host: config.host, port: config.port, dialect: config.database, logging: false } ); await sequelize.authenticate(); await sequelize.close(); console.log(chalk.green('✅ Database connection successful')); } catch (error) { console.log(chalk.red('❌ Database connection failed:')); console.log(chalk.red(`Error: ${error.message}`)); // Eğer database yok ise oluşturmayı teklif et if (error.message.includes('Unknown database') || error.message.includes('database') && error.message.includes('does not exist')) { console.log(chalk.yellow('\n💡 Database does not exist. Creating it...')); try { await createDatabaseFromConnection(config); console.log(chalk.green(`✅ Database "${config.name}" created successfully!`)); // Tekrar test et const Sequelize = require('sequelize').Sequelize; const sequelize2 = new Sequelize( config.name, config.username, config.password, { host: config.host, port: config.port, dialect: config.database, logging: false } ); await sequelize2.authenticate(); await sequelize2.close(); console.log(chalk.green('✅ Database connection verified!')); } catch (createError) { console.log(chalk.red('❌ Failed to create database:')); console.log(chalk.red(createError.message)); const retry = await inquirer.prompt([ { type: 'confirm', name: 'retry', message: 'Would you like to reconfigure the database?', default: true } ]); if (retry.retry) { await setupDatabase(); } } } else { const retry = await inquirer.prompt([ { type: 'confirm', name: 'retry', message: 'Would you like to reconfigure the database?', default: true } ]); if (retry.retry) { await setupDatabase(); } } } }; // Framework'e dependency kurma fonksiyonu const installFrameworkDependencies = async (packages) => { const { execSync } = require('child_process'); const frameworkPath = path.join(__dirname, '../../'); const currentDir = process.cwd(); try { process.chdir(frameworkPath); console.log(chalk.cyan(`📦 Installing framework dependencies: ${packages.join(', ')}`)); execSync(`npm install ${packages.join(' ')} --save`, { stdio: 'pipe' }); process.chdir(currentDir); console.log(chalk.green(`✅ Framework dependencies installed: ${packages.join(', ')}`)); // Cache'i temizle packages.forEach(pkg => { try { delete require.cache[require.resolve(pkg)]; } catch (e) { } }); } catch (installError) { process.chdir(currentDir); throw new Error(`Failed to install framework dependencies: ${installError.message}`); } }; const createDatabaseFromConnection = async (config) => { try { // Framework dependencies kurulu olduğundan emin ol try { require('sequelize'); if (config.database === 'mysql') { require('mysql2'); } } catch (depError) { console.log(chalk.yellow('⚠️ Installing required framework dependencies...')); const packages = ['sequelize']; if (config.database === 'mysql') packages.push('mysql2'); if (config.database === 'postgres') packages.push('pg', 'pg-hstore'); if (config.database === 'sqlite') packages.push('sqlite3'); await installFrameworkDependencies(packages); } const { Sequelize } = require('sequelize'); console.log(chalk.cyan('🔧 Creating database without specifying database name...')); // MySQL için default database, PostgreSQL için postgres kullan const defaultDb = config.database === 'postgres' ? 'postgres' : 'mysql'; const sequelize = new Sequelize( defaultDb, config.username, config.password, { host: config.host, port: config.port, dialect: config.database, logging: false } ); // Bağlantıyı test et await sequelize.authenticate(); console.log(chalk.green('✅ Connected to database server')); // Database oluştur if (config.database === 'mysql') { console.log(chalk.cyan(`🏗️ Creating MySQL database: ${config.name}`)); await sequelize.query(`CREATE DATABASE IF NOT EXISTS \`${config.name}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;`); } else if (config.database === 'postgres') { console.log(chalk.cyan(`🏗️ Creating PostgreSQL database: ${config.name}`)); // PostgreSQL için database var mı kontrol et const [results] = await sequelize.query(`SELECT 1 FROM pg_database WHERE datname = '${config.name}';`); if (results.length === 0) { await sequelize.query(`CREATE DATABASE "${config.name}";`); } } await sequelize.close(); console.log(chalk.green(`✅ Database "${config.name}" created successfully`)); } catch (error) { console.log(chalk.red('❌ Database creation failed:')); console.log(chalk.red(error.message)); // Alternatif yöntem: mysql cli kullan if (config.database === 'mysql' && error.message.includes('Access denied')) { console.log(chalk.yellow('\n💡 Trying alternative method with mysql CLI...')); await createDatabaseWithCLI(config); } else { throw error; } } }; const createDatabaseWithCLI = async (config) => { try { const { execSync } = require('child_process'); // MySQL CLI ile database oluştur const mysqlCommand = `mysql -h ${config.host} -P ${config.port} -u ${config.username} ${config.password ? `-p${config.password}` : ''} -e "CREATE DATABASE IF NOT EXISTS \`${config.name}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"`; console.log(chalk.cyan('🔧 Using mysql CLI to create database...')); execSync(mysqlCommand, { stdio: 'pipe' }); console.log(chalk.green(`✅ Database "${config.name}" created via CLI`)); } catch (cliError) { console.log(chalk.red('❌ CLI method also failed:')); console.log(chalk.red(cliError.message)); // Manuel talimat ver console.log(chalk.yellow('\n📋 Please create the database manually:')); console.log(chalk.white(`mysql -u ${config.username} -p`)); console.log(chalk.white(`CREATE DATABASE \`${config.name}\`;`)); console.log(chalk.white(`exit;`)); const proceed = await inquirer.prompt([ { type: 'confirm', name: 'created', message: 'Have you created the database manually?', default: false } ]); if (!proceed.created) { throw new Error('Database creation cancelled by user'); } } }; const createDatabase = async (config = null) => { try { let dbConfig = config; if (!dbConfig) { const envPath = '.env'; if (!fs.existsSync(envPath)) { throw new Error('.env file not found. Run "jarvis database:setup" first.'); } // Load environment variables require('dotenv').config(); dbConfig = { name: process.env.DB_NAME, host: process.env.DB_HOST, port: process.env.DB_PORT, username: process.env.DB_USER, password: process.env.DB_PASS, database: process.env.DB_DIALECT || 'mysql' }; } if (dbConfig.database === 'sqlite') { // Create database directory for SQLite const dbDir = path.join(process.cwd(), 'database'); if (!fs.existsSync(dbDir)) { fs.mkdirSync(dbDir, { recursive: true }); } console.log(chalk.green('✅ SQLite database directory created')); return; } await createDatabaseFromConnection(dbConfig); console.log(chalk.green(`✅ Database "${dbConfig.name}" created successfully`)); } catch (error) { throw new Error(`Database creation failed: ${error.message}`); } }; const migrateDatabase = async () => { try { console.log(chalk.cyan('Running database migrations...')); const sequelize = require(path.join(process.cwd(), 'src/config/database')); await sequelize.authenticate(); console.log(chalk.green('✅ Database connection established')); await sequelize.sync({ alter: true }); console.log(chalk.green('✅ Database synchronized successfully')); await sequelize.close(); } catch (error) { throw new Error(`Database migration failed: ${error.message}`); } }; module.exports = { setupDatabase, createDatabase, migrateDatabase };