diff --git a/cli-tool/index.js b/cli-tool/index.js index cbdd7f8..a5a30da 100644 --- a/cli-tool/index.js +++ b/cli-tool/index.js @@ -14,16 +14,8 @@ const gradient = require('gradient-string'); const https = require('https'); const http = require('http'); -// Configuration URLs - can be updated as needed -const ENV_CONFIG_URLS = { - internal: 'https://raw.githubusercontent.com/corradaf/env-configs/main/internal.env' -}; - -// Internal access token for secure env access -const INTERNAL_ACCESS_TOKEN = 'corrad_internal_2024_secure_token_af'; - -// Function to fetch content from URL with security -function fetchFromUrl(url, token = null) { +// Function to fetch content from URL +function fetchFromUrl(url) { return new Promise((resolve, reject) => { const client = url.startsWith('https') ? https : http; @@ -32,15 +24,9 @@ function fetchFromUrl(url, token = null) { hostname: urlObj.hostname, port: urlObj.port, path: urlObj.pathname + urlObj.search, - method: 'GET', - headers: {} + method: 'GET' }; - if (token) { - options.headers['Authorization'] = `Bearer ${token}`; - options.headers['X-Access-Token'] = token; - } - const req = client.request(options, (res) => { let data = ''; @@ -122,6 +108,30 @@ function createProgressBar(message, duration = 3000) { }); } +// Clear screen and display ASCII art +function displayHeader() { + clearScreen(); + + // Show ASCII "CORRAD AF" with gradient + const asciiText = figlet.textSync('CORRAD AF', { + font: 'Big', + horizontalLayout: 'default', + verticalLayout: 'default' + }); + + console.log(gradient.rainbow(asciiText)); + console.log(boxen( + chalk.white.bold('šŸš€ Welcome to CORRAD Application Framework CLI\n') + + chalk.gray('The fastest way to bootstrap your next project'), + { + padding: 1, + margin: 1, + borderStyle: 'round', + borderColor: 'cyan' + } + )); +} + // Clear screen and position cursor at top function clearScreen() { console.clear(); @@ -163,73 +173,59 @@ function extractComment(line) { return commentMatch ? commentMatch[1].trim() : ''; } -// Prompt for environment configurations -async function promptForEnvConfig(envVars, projectName) { - const envConfig = {}; - - console.log(chalk.cyan.bold('\nāš™ļø Environment Configuration')); - console.log(chalk.gray('Configure your project environment variables:\n')); - - for (const envVar of envVars) { - let message = `${envVar.key}`; - if (envVar.description) { - message += chalk.gray(` (${envVar.description})`); - } - - let defaultValue = envVar.defaultValue; - - // Replace placeholder values - if (defaultValue.includes('${projectName}') || defaultValue.includes('your-project')) { - defaultValue = defaultValue.replace(/\${projectName}|your-project/g, projectName); - } - - const { value } = await inquirer.prompt([ - { - type: 'input', - name: 'value', - message: message + ':', - default: defaultValue, - validate: function(input) { - if (envVar.key.includes('SECRET') || envVar.key.includes('PASSWORD')) { - if (input.trim().length < 8) { - return 'Security values should be at least 8 characters long'; - } - } - return true; - } - } - ]); - - envConfig[envVar.key] = value; +// Create default .env.example file if it doesn't exist +function createDefaultEnvExample() { + const defaultEnvContent = `# Database Configuration +DATABASE_URL="mysql://username:password@localhost:3306/database_name" # Your MySQL connection string + +# Authentication +JWT_SECRET="your-super-secret-jwt-key-change-this-in-production" # Secret key for JWT tokens +AUTH_ORIGIN="http://localhost:3000" # Allowed origin for authentication + +# Application +NUXT_SECRET_KEY="your-nuxt-secret-key-for-session-encryption" # Nuxt secret key +APP_NAME="Your Application Name" # Your application name +APP_URL="http://localhost:3000" # Your application URL + +# Email Configuration (Optional) +MAIL_HOST="smtp.example.com" # SMTP server host +MAIL_PORT="587" # SMTP server port +MAIL_USERNAME="your-email@example.com" # SMTP username +MAIL_PASSWORD="your-email-password" # SMTP password +MAIL_FROM_ADDRESS="noreply@yourapp.com" # From email address +MAIL_FROM_NAME="Your App Name" # From name + +# Development +NODE_ENV="development" # Environment (development, production) +NUXT_HOST="localhost" # Nuxt host +NUXT_PORT="3000" # Nuxt port +`; + + fs.writeFileSync('.env.example', defaultEnvContent); + return defaultEnvContent; +} + +// Check if environment is properly configured +function isEnvConfigured() { + if (!fs.existsSync('.env')) { + return false; } - return envConfig; + const envContent = fs.readFileSync('.env', 'utf-8'); + // Check if DATABASE_URL is properly set (not empty or default) + const dbUrlMatch = envContent.match(/DATABASE_URL\s*=\s*["'](.+?)["']/); + + if (!dbUrlMatch) return false; + + const dbUrl = dbUrlMatch[1]; + return dbUrl && !dbUrl.includes('username:password'); } // Main CLI function async function main() { try { - // Clear console and show welcome screen (only once) - clearScreen(); - - // Step 1: Show ASCII "CORRAD AF" with gradient (only once) - const asciiText = figlet.textSync('CORRAD AF', { - font: 'Big', - horizontalLayout: 'default', - verticalLayout: 'default' - }); - - console.log(gradient.rainbow(asciiText)); - console.log(boxen( - chalk.white.bold('šŸš€ Welcome to CORRAD Application Framework CLI\n') + - chalk.gray('The fastest way to bootstrap your next project'), - { - padding: 1, - margin: 1, - borderStyle: 'round', - borderColor: 'cyan' - } - )); + // Show the header once at the beginning + displayHeader(); // Check for existing project files const hasFiles = hasProjectFiles(); @@ -264,18 +260,8 @@ async function main() { process.exit(0); } - // Clear screen and show project name prompt - clearScreen(); - console.log(boxen( - chalk.white.bold('šŸš€ CORRAD Application Framework CLI\n') + - chalk.gray('The fastest way to bootstrap your next project'), - { - padding: 1, - margin: 1, - borderStyle: 'round', - borderColor: 'cyan' - } - )); + // Display header again for consistency + displayHeader(); // Get project name (only for new projects) let projectName = path.basename(process.cwd()); @@ -302,94 +288,32 @@ async function main() { projectName = inputProjectName; } - // Clear screen and show project type selection - clearScreen(); - console.log(boxen( - chalk.white.bold('šŸš€ CORRAD Application Framework CLI\n') + - chalk.gray('The fastest way to bootstrap your next project'), - { - padding: 1, - margin: 1, - borderStyle: 'round', - borderColor: 'cyan' - } - )); + // Display header again for consistency + displayHeader(); - // Step 3: Ask if public or internal project - console.log(chalk.cyan.bold('\nšŸ” Project Type')); - const { projectType } = await inquirer.prompt([ + // Step 3: Ask about environment setup + console.log(chalk.cyan.bold('\nšŸ” Environment Setup')); + const { envSetupType } = await inquirer.prompt([ { type: 'list', - name: 'projectType', - message: 'Is this a public or internal project?', + name: 'envSetupType', + message: 'How would you like to set up your environment?', choices: [ { - name: chalk.green('šŸŒ Public') + chalk.gray(' - Configure environment manually'), - value: 'public' + name: chalk.green('šŸ”§ Manual Setup') + chalk.gray(' - Configure environment later'), + value: 'manual' }, { - name: chalk.red('šŸ”’ Internal') + chalk.gray(' - Use company configuration'), - value: 'internal' + name: chalk.blue('šŸ”— Import from URL') + chalk.gray(' - Paste a configuration URL'), + value: 'url' } ], default: 0 } ]); - let isInternal = false; - let useCompanyEnv = false; - - if (projectType === 'internal') { - isInternal = true; - - // Clear screen and show password prompt - clearScreen(); - console.log(boxen( - chalk.white.bold('šŸš€ CORRAD Application Framework CLI\n') + - chalk.gray('The fastest way to bootstrap your next project'), - { - padding: 1, - margin: 1, - borderStyle: 'round', - borderColor: 'cyan' - } - )); - - // Password verification for internal projects - console.log(chalk.yellow('\nšŸ”’ Internal project detected - Password verification required')); - const { password } = await inquirer.prompt([ - { - type: 'password', - name: 'password', - message: 'Enter company password:', - mask: '*' - } - ]); - - if (password !== 'Rahsia@123456') { - console.log(chalk.red('āŒ Incorrect password. Access denied.')); - process.exit(1); - } - - console.log(chalk.green('āœ“ Password verified. Access granted.')); - useCompanyEnv = true; - - } else { - console.log(chalk.green('āœ“ Public project setup selected.')); - } - - // Clear screen and show development tools prompt - clearScreen(); - console.log(boxen( - chalk.white.bold('šŸš€ CORRAD Application Framework CLI\n') + - chalk.gray('The fastest way to bootstrap your next project'), - { - padding: 1, - margin: 1, - borderStyle: 'round', - borderColor: 'cyan' - } - )); + // Display header again for consistency + displayHeader(); // Step 4: Ask about Cursor rules console.log(chalk.cyan.bold('\nāš™ļø Development Tools')); @@ -406,18 +330,8 @@ async function main() { } ]); - // Clear screen and show setup progress - clearScreen(); - console.log(boxen( - chalk.white.bold('šŸš€ CORRAD Application Framework CLI\n') + - chalk.gray('The fastest way to bootstrap your next project'), - { - padding: 1, - margin: 1, - borderStyle: 'round', - borderColor: 'cyan' - } - )); + // Display header again for consistency + displayHeader(); // Step 5: Show loading screen and setup project console.log(chalk.cyan.bold('\nšŸš€ Setting up your project...')); @@ -467,75 +381,83 @@ async function main() { } // Setup environment file - if (useCompanyEnv && isInternal) { - const envSpinner = ora('Setting up company environment...').start(); + if (envSetupType === 'url') { + // Display header for consistency + displayHeader(); + + const { envUrl } = await inquirer.prompt([ + { + type: 'input', + name: 'envUrl', + message: 'Enter the URL for your environment configuration:', + validate: function(input) { + if (!input.trim() || !input.startsWith('http')) { + return 'Please enter a valid URL (starting with http:// or https://)'; + } + return true; + } + } + ]); + + const envSpinner = ora('Fetching environment configuration from URL...').start(); try { - // Fetch .env content from secure URL - const envContent = await fetchFromUrl(ENV_CONFIG_URLS.internal, INTERNAL_ACCESS_TOKEN); + // Fetch .env content from provided URL + const envContent = await fetchFromUrl(envUrl); const processedContent = envContent.replace(/\${projectName}/g, projectName); fs.writeFileSync('.env', processedContent); - envSpinner.succeed('Company environment configured'); + envSpinner.succeed('Environment configuration imported successfully'); + + // Run Prisma commands if the env is properly configured + if (isEnvConfigured()) { + const prismaSpinner = ora('Setting up Prisma database...').start(); + if (fs.existsSync('prisma/schema.prisma')) { + const prismaSuccess = runCommand('npx prisma generate', { stdio: 'pipe' }); + if (prismaSuccess) { + prismaSpinner.succeed('Prisma database configured'); + } else { + prismaSpinner.fail('Prisma setup failed - please check your DATABASE_URL'); + } + } else { + prismaSpinner.warn('No Prisma schema found - skipping database setup'); + } + } } catch (error) { - envSpinner.fail('Failed to fetch company environment config'); - console.log(chalk.yellow('āš ļø Unable to access secure company environment.')); - console.log(chalk.gray('Please contact your system administrator for access.')); - process.exit(1); + envSpinner.fail('Failed to fetch environment configuration'); + console.log(chalk.yellow(`āš ļø Unable to access URL: ${error.message}`)); + console.log(chalk.gray('Creating a basic .env file you can edit later...')); + + // Create basic .env file from .env.example + try { + let exampleContent; + if (fs.existsSync('.env.example')) { + exampleContent = fs.readFileSync('.env.example', 'utf8'); + } else { + exampleContent = createDefaultEnvExample(); + } + fs.copyFileSync('.env.example', '.env'); + console.log(chalk.green('āœ“ Basic environment file created')); + console.log(chalk.yellow('āš ļø Please edit the .env file and configure your DATABASE_URL before running Prisma commands')); + } catch (error) { + console.log(chalk.red(`Error creating .env file: ${error.message}`)); + } } } else { - // Create environment file based on .env.example - const envSpinner = ora('Setting up environment configuration...').start(); + // Manual environment setup + const envSpinner = ora('Setting up environment files...').start(); try { - if (fs.existsSync('.env.example')) { - const exampleContent = fs.readFileSync('.env.example', 'utf8'); - const envVars = parseEnvExample(exampleContent); - - envSpinner.succeed('Environment template loaded'); - - // Clear screen for env configuration - clearScreen(); - console.log(boxen( - chalk.white.bold('šŸš€ CORRAD Application Framework CLI\n') + - chalk.gray('The fastest way to bootstrap your next project'), - { - padding: 1, - margin: 1, - borderStyle: 'round', - borderColor: 'cyan' - } - )); - - // Prompt for each environment variable - const envConfig = await promptForEnvConfig(envVars, projectName); - - // Generate .env file - let envContent = ''; - for (const [key, value] of Object.entries(envConfig)) { - envContent += `${key}="${value}"\n`; - } - - fs.writeFileSync('.env', envContent); - console.log(chalk.green('āœ“ Environment file created')); - } else { - envSpinner.warn('No .env.example found - creating basic environment'); - - // Fallback to basic template - const fallbackEnv = `# Basic Environment Configuration -DATABASE_URL="postgresql://username:password@localhost:5432/database_name" -JWT_SECRET="your-super-secret-jwt-key" -AUTH_ORIGIN="http://localhost:3000" -NUXT_SECRET_KEY="your-nuxt-secret-key" -APP_NAME="${projectName}" -APP_URL="http://localhost:3000" -NODE_ENV="development" -NUXT_HOST="localhost" -NUXT_PORT="3000" -`; - fs.writeFileSync('.env', fallbackEnv); - console.log(chalk.green('āœ“ Basic environment created')); + // Create or ensure .env.example exists + if (!fs.existsSync('.env.example')) { + createDefaultEnvExample(); } + + // Copy .env.example to .env without filling in values + fs.copyFileSync('.env.example', '.env'); + envSpinner.succeed('Environment files created'); + console.log(chalk.green('āœ“ Created .env file from .env.example')); + console.log(chalk.yellow('āš ļø Please edit the .env file and configure your DATABASE_URL before running Prisma commands')); } catch (error) { envSpinner.fail('Failed to set up environment'); console.log(chalk.red(`Error: ${error.message}`)); @@ -640,43 +562,37 @@ Security: yarnSpinner.succeed('Dependencies installed with Yarn'); } - // Setup Prisma - const prismaSpinner = ora('Setting up Prisma database...').start(); - try { - // Check if Prisma is available and generate client - if (fs.existsSync('prisma/schema.prisma')) { - const prismaSuccess = runCommand('npx prisma generate', { stdio: 'pipe' }); - if (prismaSuccess) { - prismaSpinner.succeed('Prisma database configured'); - } else { - prismaSpinner.warn('Prisma setup incomplete - configure manually if needed'); - } - } else { - prismaSpinner.warn('No Prisma schema found - skipping database setup'); - } - } catch (error) { - prismaSpinner.warn('Prisma setup skipped'); - } - + // Display header again for consistency at the end + displayHeader(); + // Step 6: Success message and next steps + const prismaNotes = isEnvConfigured() ? + '' : + chalk.yellow('\nāš ļø Important: Edit your .env file and configure your DATABASE_URL before running:\n') + + chalk.gray(' npx prisma generate\n'); + const nextSteps = setupAction === 'new' ? [ ` cd ${projectName}`, - ' git init # Initialize git repository', - ' yarn dev # Start development server', - ' yarn build # Build for production', - ' yarn start # Start production server' + ' git init # Initialize git repository', + ' # Edit your .env file with proper configuration', + ' npx prisma generate # Generate Prisma client after configuring DATABASE_URL', + ' yarn dev # Start development server', + ' yarn build # Build for production', + ' yarn start # Start production server' ] : [ - ' yarn dev # Start development server', - ' yarn build # Build for production', - ' yarn start # Start production server' + ' # Edit your .env file with proper configuration', + ' npx prisma generate # Generate Prisma client after configuring DATABASE_URL', + ' yarn dev # Start development server', + ' yarn build # Build for production', + ' yarn start # Start production server' ]; console.log('\n' + boxen( chalk.green.bold('šŸŽ‰ Project setup completed successfully!\n\n') + chalk.white(`Project: ${chalk.cyan.bold(projectName)}\n`) + - chalk.white(`Type: ${chalk.yellow.bold(projectType)}\n`) + chalk.white(`Action: ${chalk.yellow.bold(setupAction === 'new' ? 'New Project' : 'Updated Existing')}\n`) + - chalk.white(`Location: ${chalk.gray(process.cwd())}\n\n`) + + chalk.white(`Location: ${chalk.gray(process.cwd())}\n`) + + prismaNotes + chalk.white.bold('Next steps:\n') + nextSteps.map(step => chalk.gray(step)).join('\n'), { diff --git a/cli-tool/package.json b/cli-tool/package.json index 480931d..4bf5e14 100644 --- a/cli-tool/package.json +++ b/cli-tool/package.json @@ -1,6 +1,6 @@ { "name": "corradaf", - "version": "1.0.0", + "version": "1.0.2", "description": "CLI tool to quickly set up CORRAD Application Framework projects", "main": "index.js", "bin": { @@ -19,12 +19,12 @@ "template" ], "dependencies": { - "figlet": "^1.7.0", - "ora": "^5.4.1", - "inquirer": "^8.2.6", - "chalk": "^4.1.2", "boxen": "^5.1.2", - "gradient-string": "^2.0.2" + "chalk": "^4.1.2", + "figlet": "^1.8.1", + "gradient-string": "^2.0.2", + "inquirer": "^8.2.6", + "ora": "^5.4.1" }, "author": "Corrad Team", "license": "MIT", @@ -36,4 +36,4 @@ "engines": { "node": ">=14.0.0" } -} \ No newline at end of file +}