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
JavaScript
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
};