#!/usr/bin/env node /** * Build script for production deployment * Handles CSS compilation, asset optimization, and production setup */ const fs = require('fs'); const path = require('path'); const { exec } = require('child_process'); const { promisify } = require('util'); const execAsync = promisify(exec); // Configuration const BUILD_DIR = path.join(__dirname, '..', 'dist'); const PUBLIC_DIR = path.join(__dirname, '..', 'public'); const VIEWS_DIR = path.join(__dirname, '..', 'views'); async function buildForProduction() { try { console.log('🏗️ Starting production build...'); // Create build directory await createBuildDirectory(); // Install production dependencies await installProductionDependencies(); // Optimize assets await optimizeAssets(); // Generate service worker with workbox await generateServiceWorker(); // Create production environment file await createProductionEnv(); // Copy necessary files await copyProductionFiles(); console.log('✅ Production build completed successfully!'); console.log('📦 Build output available in:', BUILD_DIR); console.log('🚀 Ready for deployment!'); } catch (error) { console.error('❌ Production build failed:', error); process.exit(1); } } async function createBuildDirectory() { try { if (fs.existsSync(BUILD_DIR)) { console.log('🗑️ Cleaning existing build directory...'); await execAsync(`rm -rf ${BUILD_DIR}`); } fs.mkdirSync(BUILD_DIR, { recursive: true }); console.log('📁 Created build directory'); } catch (error) { console.error('❌ Error creating build directory:', error); throw error; } } async function installProductionDependencies() { try { console.log('📦 Installing production dependencies...'); await execAsync('npm ci --only=production'); console.log('✅ Production dependencies installed'); } catch (error) { console.error('❌ Error installing dependencies:', error); throw error; } } async function optimizeAssets() { try { console.log('🎨 Optimizing CSS and JavaScript...'); // Create optimized CSS const cssContent = fs.readFileSync(path.join(PUBLIC_DIR, 'css', 'main.css'), 'utf8'); const optimizedCSS = await optimizeCSS(cssContent); const buildCSSDir = path.join(BUILD_DIR, 'public', 'css'); fs.mkdirSync(buildCSSDir, { recursive: true }); fs.writeFileSync(path.join(buildCSSDir, 'main.min.css'), optimizedCSS); // Create optimized JavaScript const jsContent = fs.readFileSync(path.join(PUBLIC_DIR, 'js', 'main.js'), 'utf8'); const optimizedJS = await optimizeJS(jsContent); const buildJSDir = path.join(BUILD_DIR, 'public', 'js'); fs.mkdirSync(buildJSDir, { recursive: true }); fs.writeFileSync(path.join(buildJSDir, 'main.min.js'), optimizedJS); // Copy other assets await copyDirectory(path.join(PUBLIC_DIR, 'images'), path.join(BUILD_DIR, 'public', 'images')); console.log('✅ Assets optimized'); } catch (error) { console.error('❌ Error optimizing assets:', error); throw error; } } async function optimizeCSS(css) { // Simple CSS minification (remove comments, extra whitespace) return css .replace(/\/\*[\s\S]*?\*\//g, '') // Remove comments .replace(/\s{2,}/g, ' ') // Replace multiple spaces with single space .replace(/;\s*}/g, '}') // Remove semicolon before closing brace .replace(/\s*{\s*/g, '{') // Remove spaces around opening brace .replace(/;\s*/g, ';') // Remove spaces after semicolons .replace(/,\s*/g, ',') // Remove spaces after commas .trim(); } async function optimizeJS(js) { // Simple JS minification (remove comments, extra whitespace) return js .replace(/\/\/.*$/gm, '') // Remove single-line comments .replace(/\/\*[\s\S]*?\*\//g, '') // Remove multi-line comments .replace(/\s{2,}/g, ' ') // Replace multiple spaces with single space .replace(/\n\s*/g, '') // Remove newlines and indentation .trim(); } async function generateServiceWorker() { try { console.log('⚙️ Generating optimized service worker...'); const swContent = fs.readFileSync(path.join(PUBLIC_DIR, 'sw.js'), 'utf8'); const optimizedSW = await optimizeJS(swContent); fs.writeFileSync(path.join(BUILD_DIR, 'public', 'sw.js'), optimizedSW); // Copy manifest fs.copyFileSync( path.join(PUBLIC_DIR, 'manifest.json'), path.join(BUILD_DIR, 'public', 'manifest.json') ); console.log('✅ Service worker generated'); } catch (error) { console.error('❌ Error generating service worker:', error); throw error; } } async function createProductionEnv() { try { console.log('🔧 Creating production environment configuration...'); const prodEnv = ` # Production Environment Configuration NODE_ENV=production PORT=3000 # Database MONGODB_URI=mongodb://localhost:27017/smartsoltech # Security SESSION_SECRET=your-super-secret-session-key-change-this-in-production JWT_SECRET=your-super-secret-jwt-key-change-this-in-production # File Upload UPLOAD_PATH=./uploads MAX_FILE_SIZE=10485760 # Email Configuration SMTP_HOST=smtp.gmail.com SMTP_PORT=587 SMTP_USER=your-email@gmail.com SMTP_PASS=your-app-password # Telegram Bot (Optional) TELEGRAM_BOT_TOKEN=your-telegram-bot-token # Admin Account ADMIN_EMAIL=admin@smartsoltech.kr ADMIN_PASSWORD=change-this-password # Security Headers RATE_LIMIT_WINDOW_MS=900000 RATE_LIMIT_MAX_REQUESTS=100 `.trim(); fs.writeFileSync(path.join(BUILD_DIR, '.env.production'), prodEnv); console.log('✅ Production environment file created'); } catch (error) { console.error('❌ Error creating production environment:', error); throw error; } } async function copyProductionFiles() { try { console.log('📋 Copying production files...'); // Copy main application files const filesToCopy = [ 'server.js', 'package.json', 'package-lock.json' ]; for (const file of filesToCopy) { fs.copyFileSync( path.join(__dirname, '..', file), path.join(BUILD_DIR, file) ); } // Copy directories await copyDirectory( path.join(__dirname, '..', 'models'), path.join(BUILD_DIR, 'models') ); await copyDirectory( path.join(__dirname, '..', 'routes'), path.join(BUILD_DIR, 'routes') ); await copyDirectory( path.join(__dirname, '..', 'views'), path.join(BUILD_DIR, 'views') ); await copyDirectory( path.join(__dirname, '..', 'middleware'), path.join(BUILD_DIR, 'middleware') ); // Create uploads directory fs.mkdirSync(path.join(BUILD_DIR, 'uploads'), { recursive: true }); console.log('✅ Production files copied'); } catch (error) { console.error('❌ Error copying production files:', error); throw error; } } async function copyDirectory(src, dest) { try { if (!fs.existsSync(src)) { return; } fs.mkdirSync(dest, { recursive: true }); const items = fs.readdirSync(src); for (const item of items) { const srcPath = path.join(src, item); const destPath = path.join(dest, item); const stat = fs.statSync(srcPath); if (stat.isDirectory()) { await copyDirectory(srcPath, destPath); } else { fs.copyFileSync(srcPath, destPath); } } } catch (error) { console.error(`❌ Error copying directory ${src}:`, error); throw error; } } if (require.main === module) { buildForProduction(); } module.exports = { buildForProduction };