@friggframework/frigg-cli
Version:
Frigg Framework CLI tool
194 lines (156 loc) ⢠6.91 kB
JavaScript
const path = require('path');
const chalk = require('chalk');
const dotenv = require('dotenv');
const {
validateDatabaseUrl,
getDatabaseType,
checkPrismaClientGenerated
} = require('../utils/database-validator');
const {
runPrismaGenerate,
checkDatabaseState,
runPrismaMigrate,
runPrismaDbPush,
getMigrationCommand
} = require('@friggframework/core/database/utils/prisma-runner');
const {
getDatabaseUrlMissingError,
getDatabaseTypeNotConfiguredError,
getPrismaCommandError,
getDatabaseSetupSuccess
} = require('../utils/error-messages');
/**
* Database Setup Command
* Sets up the database for a Frigg application:
* - Validates configuration
* - Generates Prisma client
* - Runs migrations (PostgreSQL) or db push (MongoDB)
*/
async function dbSetupCommand(options = {}) {
const verbose = options.verbose || false;
const stage = options.stage || process.env.STAGE || 'development';
console.log(chalk.blue('š§ Frigg Database Setup'));
console.log(chalk.gray(`Stage: ${stage}\n`));
// Load environment variables from .env file
const envPath = path.join(process.cwd(), '.env');
dotenv.config({ path: envPath });
try {
// Step 1: Validate DATABASE_URL
if (verbose) {
console.log(chalk.gray('Step 1: Validating DATABASE_URL...'));
}
const urlValidation = validateDatabaseUrl();
if (!urlValidation.valid) {
console.error(getDatabaseUrlMissingError());
process.exit(1);
}
if (verbose) {
console.log(chalk.green('ā DATABASE_URL found\n'));
}
// Step 2: Determine database type from app definition
if (verbose) {
console.log(chalk.gray('Step 2: Determining database type...'));
}
const dbTypeResult = getDatabaseType();
if (dbTypeResult.error) {
console.error(chalk.red('ā ' + dbTypeResult.error));
// Show stack trace in verbose mode for debugging
if (verbose && dbTypeResult.stack) {
console.error(chalk.gray('\nStack trace:'));
console.error(chalk.gray(dbTypeResult.stack));
}
console.error(getDatabaseTypeNotConfiguredError());
process.exit(1);
}
const dbType = dbTypeResult.dbType;
console.log(chalk.cyan(`Database type: ${dbType}`));
if (verbose) {
console.log(chalk.green(`ā Using ${dbType}\n`));
}
// Step 3: Check if Prisma client exists, generate if needed
if (verbose) {
console.log(chalk.gray('Step 3: Checking Prisma client...'));
}
const clientCheck = checkPrismaClientGenerated(dbType);
const forceRegenerate = options.force || false;
if (clientCheck.generated && !forceRegenerate) {
// Client already exists and --force not specified
console.log(chalk.green('ā Prisma client already exists (skipping generation)\n'));
if (verbose) {
console.log(chalk.gray(` Client location: ${clientCheck.path}\n`));
}
} else {
// Client doesn't exist OR --force specified - generate it
if (forceRegenerate && clientCheck.generated) {
console.log(chalk.yellow('ā ļø Forcing Prisma client regeneration...'));
} else {
console.log(chalk.cyan('Generating Prisma client...'));
}
const generateResult = await runPrismaGenerate(dbType, verbose);
if (!generateResult.success) {
console.error(getPrismaCommandError('generate', generateResult.error));
if (generateResult.output) {
console.error(chalk.gray(generateResult.output));
}
process.exit(1);
}
console.log(chalk.green('ā Prisma client generated\n'));
}
// Step 4: Check database state
// Note: We skip connection testing in db:setup because when using frigg:local,
// the CLI code runs from tmp/frigg but the client is in backend/node_modules,
// causing module resolution mismatches. Connection testing happens in frigg start.
if (verbose) {
console.log(chalk.gray('Step 4: Checking database state...'));
}
const stateCheck = await checkDatabaseState(dbType);
// Step 5: Run migrations or db push
if (dbType === 'postgresql') {
console.log(chalk.cyan('Running database migrations...'));
const migrationCommand = getMigrationCommand(stage);
if (verbose) {
console.log(chalk.gray(`Using migration command: ${migrationCommand}`));
}
if (stateCheck.upToDate && migrationCommand === 'deploy') {
console.log(chalk.yellow('Database is already up-to-date'));
} else {
const migrateResult = await runPrismaMigrate(migrationCommand, verbose);
if (!migrateResult.success) {
console.error(getPrismaCommandError('migrate', migrateResult.error));
if (migrateResult.output) {
console.error(chalk.gray(migrateResult.output));
}
process.exit(1);
}
console.log(chalk.green('ā Migrations applied\n'));
}
} else if (dbType === 'mongodb') {
console.log(chalk.cyan('Pushing schema to MongoDB...'));
const pushResult = await runPrismaDbPush(verbose);
if (!pushResult.success) {
console.error(getPrismaCommandError('db push', pushResult.error));
if (pushResult.output) {
console.error(chalk.gray(pushResult.output));
}
process.exit(1);
}
console.log(chalk.green('ā Schema pushed to database\n'));
}
// Success!
console.log(getDatabaseSetupSuccess(dbType, stage));
} catch (error) {
console.error(chalk.red('\nā Database setup failed'));
console.error(chalk.gray(error.message));
if (verbose && error.stack) {
console.error(chalk.gray('\nStack trace:'));
console.error(chalk.gray(error.stack));
}
console.error(chalk.yellow('\nTroubleshooting:'));
console.error(chalk.gray(' ⢠Verify DATABASE_URL in your .env file'));
console.error(chalk.gray(' ⢠Check database is running and accessible'));
console.error(chalk.gray(' ⢠Ensure app definition has database configuration'));
console.error(chalk.gray(' ⢠Run with --verbose flag for more details'));
process.exit(1);
}
}
module.exports = { dbSetupCommand };