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'));
console.log(chalk.gray('Configure your project environment variables:\n')); # Authentication
JWT_SECRET="your-super-secret-jwt-key-change-this-in-production" # Secret key for JWT tokens
for (const envVar of envVars) { AUTH_ORIGIN="http://localhost:3000" # Allowed origin for authentication
let message = `${envVar.key}`;
if (envVar.description) { # Application
message += chalk.gray(` (${envVar.description})`); 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
let defaultValue = envVar.defaultValue;
# Email Configuration (Optional)
// Replace placeholder values MAIL_HOST="smtp.example.com" # SMTP server host
if (defaultValue.includes('${projectName}') || defaultValue.includes('your-project')) { MAIL_PORT="587" # SMTP server port
defaultValue = defaultValue.replace(/\${projectName}|your-project/g, projectName); MAIL_USERNAME="your-email@example.com" # SMTP username
} MAIL_PASSWORD="your-email-password" # SMTP password
MAIL_FROM_ADDRESS="noreply@yourapp.com" # From email address
const { value } = await inquirer.prompt([ MAIL_FROM_NAME="Your App Name" # From name
{
type: 'input', # Development
name: 'value', NODE_ENV="development" # Environment (development, production)
message: message + ':', NUXT_HOST="localhost" # Nuxt host
default: defaultValue, NUXT_PORT="3000" # Nuxt port
validate: function(input) { `;
if (envVar.key.includes('SECRET') || envVar.key.includes('PASSWORD')) {
if (input.trim().length < 8) { fs.writeFileSync('.env.example', defaultEnvContent);
return 'Security values should be at least 8 characters long'; return defaultEnvContent;
} }
}
return true; // Check if environment is properly configured
} function isEnvConfigured() {
} if (!fs.existsSync('.env')) {
]); return false;
envConfig[envVar.key] = value;
} }
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 // 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');
// 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) { } catch (error) {
envSpinner.fail('Failed to fetch company environment config'); envSpinner.fail('Failed to fetch environment configuration');
console.log(chalk.yellow('⚠️ Unable to access secure company environment.')); console.log(chalk.yellow(`⚠️ Unable to access URL: ${error.message}`));
console.log(chalk.gray('Please contact your system administrator for access.')); console.log(chalk.gray('Creating a basic .env file you can edit later...'));
process.exit(1);
// 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 { } else {
// Create environment file based on .env.example // Manual environment setup
const envSpinner = ora('Setting up environment configuration...').start(); 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);
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'));
} }
// 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) { } 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,43 +562,37 @@ 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',
' yarn dev # Start development server', ' # Edit your .env file with proper configuration',
' yarn build # Build for production', ' npx prisma generate # Generate Prisma client after configuring DATABASE_URL',
' yarn start # Start production server' ' yarn dev # Start development server',
' yarn build # Build for production',
' yarn start # Start production server'
] : [ ] : [
' yarn dev # Start development server', ' # Edit your .env file with proper configuration',
' yarn build # Build for production', ' npx prisma generate # Generate Prisma client after configuring DATABASE_URL',
' yarn start # Start production server' ' yarn dev # Start development server',
' yarn build # Build for production',
' yarn start # Start production server'
]; ];
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",
@ -36,4 +36,4 @@
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
} }
} }