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 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'),
{

View File

@ -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"
}
}
}