Refactor CLI tool: Simplify environment configuration process by removing internal access token and enhancing error handling. Introduce displayHeader function for consistent ASCII art presentation. Create default .env.example file for easier project setup and improve user prompts for environment setup options.

This commit is contained in:
Zahirul Iman 2025-06-04 23:13:26 +08:00
parent 14606f0ada
commit 3883e5ef20
2 changed files with 184 additions and 268 deletions

View File

@ -14,16 +14,8 @@ const gradient = require('gradient-string');
const https = require('https'); const https = require('https');
const http = require('http'); const http = require('http');
// Configuration URLs - can be updated as needed // Function to fetch content from URL
const ENV_CONFIG_URLS = { function fetchFromUrl(url) {
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) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const client = url.startsWith('https') ? https : http; const client = url.startsWith('https') ? https : http;
@ -32,15 +24,9 @@ function fetchFromUrl(url, token = null) {
hostname: urlObj.hostname, hostname: urlObj.hostname,
port: urlObj.port, port: urlObj.port,
path: urlObj.pathname + urlObj.search, path: urlObj.pathname + urlObj.search,
method: 'GET', method: 'GET'
headers: {}
}; };
if (token) {
options.headers['Authorization'] = `Bearer ${token}`;
options.headers['X-Access-Token'] = token;
}
const req = client.request(options, (res) => { const req = client.request(options, (res) => {
let data = ''; 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 // Clear screen and position cursor at top
function clearScreen() { function clearScreen() {
console.clear(); console.clear();
@ -163,73 +173,59 @@ function extractComment(line) {
return commentMatch ? commentMatch[1].trim() : ''; return commentMatch ? commentMatch[1].trim() : '';
} }
// Prompt for environment configurations // Create default .env.example file if it doesn't exist
async function promptForEnvConfig(envVars, projectName) { function createDefaultEnvExample() {
const envConfig = {}; const defaultEnvContent = `# Database Configuration
DATABASE_URL="mysql://username:password@localhost:3306/database_name" # Your MySQL connection string
console.log(chalk.cyan.bold('\n⚙ Environment Configuration')); # Authentication
console.log(chalk.gray('Configure your project environment variables:\n')); 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
for (const envVar of envVars) { # Application
let message = `${envVar.key}`; NUXT_SECRET_KEY="your-nuxt-secret-key-for-session-encryption" # Nuxt secret key
if (envVar.description) { APP_NAME="Your Application Name" # Your application name
message += chalk.gray(` (${envVar.description})`); 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;
} }
let defaultValue = envVar.defaultValue; 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*["'](.+?)["']/);
// Replace placeholder values if (!dbUrlMatch) return false;
if (defaultValue.includes('${projectName}') || defaultValue.includes('your-project')) {
defaultValue = defaultValue.replace(/\${projectName}|your-project/g, projectName);
}
const { value } = await inquirer.prompt([ const dbUrl = dbUrlMatch[1];
{ return dbUrl && !dbUrl.includes('username:password');
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;
}
return envConfig;
} }
// Main CLI function // Main CLI function
async function main() { async function main() {
try { try {
// Clear console and show welcome screen (only once) // Show the header once at the beginning
clearScreen(); displayHeader();
// 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'
}
));
// Check for existing project files // Check for existing project files
const hasFiles = hasProjectFiles(); const hasFiles = hasProjectFiles();
@ -264,18 +260,8 @@ async function main() {
process.exit(0); process.exit(0);
} }
// Clear screen and show project name prompt // Display header again for consistency
clearScreen(); displayHeader();
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'
}
));
// Get project name (only for new projects) // Get project name (only for new projects)
let projectName = path.basename(process.cwd()); let projectName = path.basename(process.cwd());
@ -302,94 +288,32 @@ async function main() {
projectName = inputProjectName; projectName = inputProjectName;
} }
// Clear screen and show project type selection // Display header again for consistency
clearScreen(); displayHeader();
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'
}
));
// Step 3: Ask if public or internal project // Step 3: Ask about environment setup
console.log(chalk.cyan.bold('\n🔐 Project Type')); console.log(chalk.cyan.bold('\n🔐 Environment Setup'));
const { projectType } = await inquirer.prompt([ const { envSetupType } = await inquirer.prompt([
{ {
type: 'list', type: 'list',
name: 'projectType', name: 'envSetupType',
message: 'Is this a public or internal project?', message: 'How would you like to set up your environment?',
choices: [ choices: [
{ {
name: chalk.green('🌍 Public') + chalk.gray(' - Configure environment manually'), name: chalk.green('🔧 Manual Setup') + chalk.gray(' - Configure environment later'),
value: 'public' value: 'manual'
}, },
{ {
name: chalk.red('🔒 Internal') + chalk.gray(' - Use company configuration'), name: chalk.blue('🔗 Import from URL') + chalk.gray(' - Paste a configuration URL'),
value: 'internal' value: 'url'
} }
], ],
default: 0 default: 0
} }
]); ]);
let isInternal = false; // Display header again for consistency
let useCompanyEnv = false; displayHeader();
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'
}
));
// Step 4: Ask about Cursor rules // Step 4: Ask about Cursor rules
console.log(chalk.cyan.bold('\n⚙ Development Tools')); console.log(chalk.cyan.bold('\n⚙ Development Tools'));
@ -406,18 +330,8 @@ async function main() {
} }
]); ]);
// Clear screen and show setup progress // Display header again for consistency
clearScreen(); displayHeader();
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'
}
));
// Step 5: Show loading screen and setup project // Step 5: Show loading screen and setup project
console.log(chalk.cyan.bold('\n🚀 Setting up your project...')); console.log(chalk.cyan.bold('\n🚀 Setting up your project...'));
@ -467,75 +381,83 @@ async function main() {
} }
// Setup environment file // Setup environment file
if (useCompanyEnv && isInternal) { if (envSetupType === 'url') {
const envSpinner = ora('Setting up company environment...').start(); // 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 { try {
// Fetch .env content from secure URL // Fetch .env content from provided URL
const envContent = await fetchFromUrl(ENV_CONFIG_URLS.internal, INTERNAL_ACCESS_TOKEN); const envContent = await fetchFromUrl(envUrl);
const processedContent = envContent.replace(/\${projectName}/g, projectName); const processedContent = envContent.replace(/\${projectName}/g, projectName);
fs.writeFileSync('.env', processedContent); fs.writeFileSync('.env', processedContent);
envSpinner.succeed('Company environment configured'); envSpinner.succeed('Environment configuration imported successfully');
} catch (error) {
envSpinner.fail('Failed to fetch company environment config'); // Run Prisma commands if the env is properly configured
console.log(chalk.yellow('⚠️ Unable to access secure company environment.')); if (isEnvConfigured()) {
console.log(chalk.gray('Please contact your system administrator for access.')); const prismaSpinner = ora('Setting up Prisma database...').start();
process.exit(1); 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 { } else {
// Create environment file based on .env.example prismaSpinner.warn('No Prisma schema found - skipping database setup');
const envSpinner = ora('Setting up environment configuration...').start(); }
}
} catch (error) {
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 {
// Manual environment setup
const envSpinner = ora('Setting up environment files...').start();
try { try {
if (fs.existsSync('.env.example')) { // Create or ensure .env.example exists
const exampleContent = fs.readFileSync('.env.example', 'utf8'); if (!fs.existsSync('.env.example')) {
const envVars = parseEnvExample(exampleContent); createDefaultEnvExample();
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); // Copy .env.example to .env without filling in values
console.log(chalk.green('✓ Environment file created')); fs.copyFileSync('.env.example', '.env');
} else { envSpinner.succeed('Environment files created');
envSpinner.warn('No .env.example found - creating basic environment'); 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'));
// 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'));
}
} catch (error) { } catch (error) {
envSpinner.fail('Failed to set up environment'); envSpinner.fail('Failed to set up environment');
console.log(chalk.red(`Error: ${error.message}`)); console.log(chalk.red(`Error: ${error.message}`));
@ -640,32 +562,26 @@ Security:
yarnSpinner.succeed('Dependencies installed with Yarn'); yarnSpinner.succeed('Dependencies installed with Yarn');
} }
// Setup Prisma // Display header again for consistency at the end
const prismaSpinner = ora('Setting up Prisma database...').start(); displayHeader();
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');
}
// Step 6: Success message and next steps // 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' ? [ const nextSteps = setupAction === 'new' ? [
` cd ${projectName}`, ` cd ${projectName}`,
' git init # Initialize git repository', ' 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 dev # Start development server',
' yarn build # Build for production', ' yarn build # Build for production',
' yarn start # Start production server' ' 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 dev # Start development server',
' yarn build # Build for production', ' yarn build # Build for production',
' yarn start # Start production server' ' yarn start # Start production server'
@ -674,9 +590,9 @@ Security:
console.log('\n' + boxen( console.log('\n' + boxen(
chalk.green.bold('🎉 Project setup completed successfully!\n\n') + chalk.green.bold('🎉 Project setup completed successfully!\n\n') +
chalk.white(`Project: ${chalk.cyan.bold(projectName)}\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(`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') + chalk.white.bold('Next steps:\n') +
nextSteps.map(step => chalk.gray(step)).join('\n'), nextSteps.map(step => chalk.gray(step)).join('\n'),
{ {

View File

@ -1,6 +1,6 @@
{ {
"name": "corradaf", "name": "corradaf",
"version": "1.0.0", "version": "1.0.2",
"description": "CLI tool to quickly set up CORRAD Application Framework projects", "description": "CLI tool to quickly set up CORRAD Application Framework projects",
"main": "index.js", "main": "index.js",
"bin": { "bin": {
@ -19,12 +19,12 @@
"template" "template"
], ],
"dependencies": { "dependencies": {
"figlet": "^1.7.0",
"ora": "^5.4.1",
"inquirer": "^8.2.6",
"chalk": "^4.1.2",
"boxen": "^5.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", "author": "Corrad Team",
"license": "MIT", "license": "MIT",