Complete guide to CLI tools, automation scripts, and development utilities that streamline your NextReady development workflow.
// scripts/setup.js - Interactive Setup CLI
const inquirer = require('inquirer');
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const chalk = require('chalk');
class NextReadySetup {
constructor() {
this.projectPath = process.cwd();
this.envPath = path.join(this.projectPath, '.env.local');
this.config = {};
}
async run() {
console.log(chalk.blue.bold('š NextReady Interactive Setup\n'));
try {
await this.welcomeMessage();
await this.collectProjectInfo();
await this.setupEnvironment();
await this.setupDatabase();
await this.setupServices();
await this.runFinalSteps();
console.log(chalk.green.bold('\nā
Setup completed successfully!'));
console.log(chalk.cyan('Run npm run dev to start your application'));
} catch (error) {
console.error(chalk.red('Setup failed:'), error.message);
process.exit(1);
}
}
async welcomeMessage() {
const { proceed } = await inquirer.prompt([
{
type: 'confirm',
name: 'proceed',
message: 'Welcome to NextReady! This will guide you through the setup process. Continue?',
default: true
}
]);
if (!proceed) {
console.log('Setup cancelled.');
process.exit(0);
}
}
async collectProjectInfo() {
console.log(chalk.yellow('\nš Project Information'));
const answers = await inquirer.prompt([
{
type: 'input',
name: 'projectName',
message: 'What is your project name?',
default: path.basename(this.projectPath),
validate: (input) => input.trim() !== '' || 'Project name cannot be empty'
},
{
type: 'input',
name: 'description',
message: 'Project description (optional):',
default: 'A NextReady application'
},
{
type: 'input',
name: 'author',
message: 'Author name:',
default: 'Your Name'
}
]);
this.config.project = answers;
await this.updatePackageJson();
}
async setupEnvironment() {
console.log(chalk.yellow('\nš§ Environment Setup'));
const answers = await inquirer.prompt([
{
type: 'input',
name: 'appUrl',
message: 'Application URL (for production):',
default: 'https://your-domain.com',
validate: (input) => {
try {
new URL(input);
return true;
} catch {
return 'Please enter a valid URL';
}
}
},
{
type: 'password',
name: 'nextAuthSecret',
message: 'NextAuth secret (press enter to generate):',
mask: '*'
}
]);
// Generate secret if not provided
if (!answers.nextAuthSecret) {
answers.nextAuthSecret = this.generateSecret();
console.log(chalk.green('Generated NextAuth secret automatically'));
}
this.config.env = answers;
}
async setupDatabase() {
console.log(chalk.yellow('\nšļø Database Configuration'));
const { setupDb } = await inquirer.prompt([
{
type: 'confirm',
name: 'setupDb',
message: 'Would you like to set up the database now?',
default: true
}
]);
if (!setupDb) {
console.log(chalk.gray('Skipping database setup'));
return;
}
const dbAnswers = await inquirer.prompt([
{
type: 'input',
name: 'databaseUrl',
message: 'Database URL (Neon PostgreSQL):',
validate: (input) => {
if (!input.trim()) return 'Database URL is required';
if (!input.includes('postgresql://')) return 'Please enter a valid PostgreSQL URL';
return true;
}
},
{
type: 'confirm',
name: 'runMigrations',
message: 'Run database migrations?',
default: true
},
{
type: 'confirm',
name: 'seedData',
message: 'Seed with sample data?',
default: true
}
]);
this.config.database = dbAnswers;
if (dbAnswers.runMigrations) {
await this.runDatabaseMigrations();
}
if (dbAnswers.seedData) {
await this.seedDatabase();
}
}
async setupServices() {
console.log(chalk.yellow('\nāļø External Services'));
const services = await inquirer.prompt([
{
type: 'checkbox',
name: 'selectedServices',
message: 'Which services would you like to configure?',
choices: [
{ name: 'File Storage (Vercel Blob)', value: 'blob' },
{ name: 'Email Service (Resend)', value: 'email' },
{ name: 'OAuth Providers', value: 'oauth' }
]
}
]);
for (const service of services.selectedServices) {
await this.setupService(service);
}
}
async setupService(service) {
switch (service) {
case 'blob':
await this.setupVercelBlob();
break;
case 'email':
await this.setupResend();
break;
case 'oauth':
await this.setupOAuth();
break;
}
}
async setupVercelBlob() {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'blobToken',
message: 'Vercel Blob read-write token:',
validate: (input) => input.trim() !== '' || 'Blob token is required'
}
]);
this.config.blob = answers;
console.log(chalk.green('ā
Vercel Blob configured'));
}
async setupResend() {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'resendApiKey',
message: 'Resend API key:',
validate: (input) => input.trim() !== '' || 'Resend API key is required'
},
{
type: 'input',
name: 'fromEmail',
message: 'From email address:',
default: 'NextReady <noreply@your-domain.com>',
validate: (input) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(input.split('<')[1]?.replace('>', '') || input) || 'Please enter a valid email';
}
}
]);
this.config.email = answers;
console.log(chalk.green('ā
Email service configured'));
}
async generateEnvFile() {
const envContent = [
'# Generated by NextReady Setup',
`# Project: ${this.config.project.projectName}`,
`# Generated: ${new Date().toISOString()}`,
'',
'# NextAuth Configuration',
`NEXTAUTH_SECRET="${this.config.env.nextAuthSecret}"`,
`NEXTAUTH_URL="${this.config.env.appUrl}"`,
'',
'# Database Configuration',
`DATABASE_URL="${this.config.database?.databaseUrl || ''}"`,
`DIRECT_URL="${this.config.database?.databaseUrl?.replace('?pgbouncer=true', '') || ''}"`,
''
];
if (this.config.blob) {
envContent.push('# File Storage');
envContent.push(`BLOB_READ_WRITE_TOKEN="${this.config.blob.blobToken}"`);
envContent.push('');
}
if (this.config.email) {
envContent.push('# Email Service');
envContent.push(`RESEND_API_KEY="${this.config.email.resendApiKey}"`);
envContent.push(`EMAIL_FROM="${this.config.email.fromEmail}"`);
envContent.push('');
}
fs.writeFileSync(this.envPath, envContent.join('\n'));
console.log(chalk.green(`ā
Environment file created: .env.local`));
}
async runFinalSteps() {
console.log(chalk.yellow('\nš Final Steps'));
await this.generateEnvFile();
const { runTests } = await inquirer.prompt([
{
type: 'confirm',
name: 'runTests',
message: 'Run configuration tests?',
default: true
}
]);
if (runTests) {
await this.validateConfiguration();
}
}
generateSecret() {
return require('crypto').randomBytes(32).toString('hex');
}
async runDatabaseMigrations() {
try {
console.log(chalk.blue('Running database migrations...'));
execSync('npm run db:push', { stdio: 'inherit' });
console.log(chalk.green('ā
Database migrations completed'));
} catch (error) {
console.log(chalk.red('ā Migration failed'), error.message);
}
}
async validateConfiguration() {
console.log(chalk.blue('\nš Validating configuration...'));
// Test database connection
try {
execSync('npm run db:test', { stdio: 'pipe' });
console.log(chalk.green('ā
Database connection successful'));
} catch (error) {
console.log(chalk.yellow('ā ļø Database connection could not be verified'));
}
// Validate environment file
if (fs.existsSync(this.envPath)) {
console.log(chalk.green('ā
Environment file created'));
}
console.log(chalk.green('ā
Configuration validation completed'));
}
}
if (require.main === module) {
new NextReadySetup().run().catch(console.error);
}
module.exports = NextReadySetup;npm run setupInteractive project setup and configuration
Complete project initialization
npm run brandBrand customization tool
Update colors, fonts, and assets
npm run db:setupDatabase setup with migrations and seeding
Initialize database from scratch
npm run validateValidate project configuration
Check setup completeness and connectivity
npm run db:backupCreate database backup
Backup production data
npm run optimizeOptimize assets and performance
Pre-deployment optimization
npm run setupInteractive configurationnpm run validateVerify setupnpm run devStart developmentnpm run validateCheck configurationnpm run buildBuild applicationnpm run optimizeOptimize assetsYour automation toolkit is configured and ready to streamline your development workflow with powerful CLI tools.