UNPKG

muspe-cli

Version:

MusPE Advanced Framework v2.1.3 - Mobile User-friendly Simple Progressive Engine with Enhanced CLI Tools, Specialized E-Commerce Templates, Material Design 3, Progressive Enhancement, Mobile Optimizations, Performance Analysis, and Enterprise-Grade Develo

791 lines (649 loc) • 21.6 kB
const fs = require('fs-extra'); const path = require('path'); const chalk = require('chalk'); const inquirer = require('inquirer'); const ora = require('ora'); const spawn = require('cross-spawn'); const semver = require('semver'); // Enhanced Deployment Command for MusPE Framework // Supports: Server-side, Android, iOS, PWA, and Docker deployments async function deployProject(options) { const projectRoot = findProjectRoot(); if (!projectRoot) { console.log(chalk.red('Not in a MusPE project directory')); return; } const config = await loadConfig(projectRoot); // Determine deployment target const deployTarget = options.target || await promptDeploymentTarget(); console.log(chalk.blue(`šŸš€ Starting deployment for: ${chalk.bold(deployTarget)}\n`)); switch (deployTarget) { case 'server': await deployToServer(projectRoot, config, options); break; case 'android': await deployToAndroid(projectRoot, config, options); break; case 'ios': await deployToIOS(projectRoot, config, options); break; case 'docker': await deployToDocker(projectRoot, config, options); break; case 'pwa': await deployToPWA(projectRoot, config, options); break; case 'all': await deployAll(projectRoot, config, options); break; default: console.log(chalk.red(`Unknown deployment target: ${deployTarget}`)); } } async function promptDeploymentTarget() { const { target } = await inquirer.prompt([ { type: 'list', name: 'target', message: 'Select deployment target:', choices: [ { name: '🌐 Server (Web deployment)', value: 'server' }, { name: 'šŸ¤– Android (APK/AAB)', value: 'android' }, { name: 'šŸŽ iOS (IPA)', value: 'ios' }, { name: '🐳 Docker Container', value: 'docker' }, { name: 'šŸ“± PWA (Progressive Web App)', value: 'pwa' }, { name: 'šŸŽÆ All Platforms', value: 'all' } ] } ]); return target; } // Server-side Deployment async function deployToServer(projectRoot, config, options) { const spinner = ora('Building for server deployment...').start(); try { // Build web assets with optimizations await buildWebAssets(projectRoot, { target: 'server', optimization: true, ssr: options.ssr || config.server?.ssr || false, minify: true, gzip: true }); // Generate server configuration files await generateServerConfig(projectRoot, config); // Create deployment package await createServerDeploymentPackage(projectRoot, config, options); spinner.succeed('Server deployment package created'); console.log(chalk.green('\n✨ Server deployment completed!')); console.log(chalk.cyan('\nšŸ“¦ Deployment artifacts:')); console.log(` ${chalk.gray('Build:')} dist/`); console.log(` ${chalk.gray('Server Config:')} server/`); console.log(` ${chalk.gray('Package:')} deploy/server/`); if (options.upload) { await uploadToServer(projectRoot, config, options); } } catch (error) { spinner.fail('Server deployment failed'); console.error(chalk.red(error.message)); } } // Android Deployment async function deployToAndroid(projectRoot, config, options) { const spinner = ora('Building for Android...').start(); try { // Check Android development environment await checkAndroidEnvironment(); // Initialize Cordova if not already done await ensureCordovaSetup(projectRoot); // Build web assets for mobile await buildWebAssets(projectRoot, { target: 'android', optimization: true, mobile: true, cordova: true }); // Configure Android-specific settings await configureAndroidBuild(projectRoot, config); // Build Android app const buildType = options.release ? 'release' : 'debug'; await buildAndroidApp(projectRoot, buildType, options); spinner.succeed('Android build completed'); console.log(chalk.green('\n✨ Android deployment completed!')); console.log(chalk.cyan('\nšŸ“± Build artifacts:')); console.log(` ${chalk.gray('APK:')} platforms/android/app/build/outputs/apk/`); console.log(` ${chalk.gray('AAB:')} platforms/android/app/build/outputs/bundle/`); if (options.install) { await installAndroidApp(projectRoot); } } catch (error) { spinner.fail('Android deployment failed'); console.error(chalk.red(error.message)); } } // iOS Deployment async function deployToIOS(projectRoot, config, options) { const spinner = ora('Building for iOS...').start(); try { // Check iOS development environment await checkIOSEnvironment(); // Initialize Cordova if not already done await ensureCordovaSetup(projectRoot); // Build web assets for mobile await buildWebAssets(projectRoot, { target: 'ios', optimization: true, mobile: true, cordova: true }); // Configure iOS-specific settings await configureIOSBuild(projectRoot, config); // Build iOS app const buildType = options.release ? 'release' : 'debug'; await buildIOSApp(projectRoot, buildType, options); spinner.succeed('iOS build completed'); console.log(chalk.green('\n✨ iOS deployment completed!')); console.log(chalk.cyan('\nšŸ“± Build artifacts:')); console.log(` ${chalk.gray('Archive:')} platforms/ios/build/`); console.log(` ${chalk.gray('IPA:')} platforms/ios/build/device/`); if (options.simulator) { await runIOSSimulator(projectRoot); } } catch (error) { spinner.fail('iOS deployment failed'); console.error(chalk.red(error.message)); } } // Docker Deployment async function deployToDocker(projectRoot, config, options) { const spinner = ora('Building Docker container...').start(); try { // Check Docker installation await checkDockerEnvironment(); // Generate Dockerfile and related files await generateDockerFiles(projectRoot, config); // Build web assets await buildWebAssets(projectRoot, { target: 'docker', optimization: true, production: true }); // Build Docker image await buildDockerImage(projectRoot, config, options); spinner.succeed('Docker container built'); console.log(chalk.green('\n✨ Docker deployment completed!')); console.log(chalk.cyan('\n🐳 Container info:')); console.log(` ${chalk.gray('Image:')} ${config.name}:${config.version}`); console.log(` ${chalk.gray('Size:')} ${await getDockerImageSize(config.name, config.version)}`); if (options.run) { await runDockerContainer(projectRoot, config); } } catch (error) { spinner.fail('Docker deployment failed'); console.error(chalk.red(error.message)); } } // PWA Deployment async function deployToPWA(projectRoot, config, options) { const spinner = ora('Building PWA...').start(); try { // Build web assets with PWA optimizations await buildWebAssets(projectRoot, { target: 'pwa', optimization: true, serviceWorker: true, manifest: true, offline: true }); // Generate PWA-specific files await generatePWAFiles(projectRoot, config); // Optimize for PWA features await optimizeForPWA(projectRoot, config); spinner.succeed('PWA build completed'); console.log(chalk.green('\n✨ PWA deployment completed!')); console.log(chalk.cyan('\nšŸ“± PWA features:')); console.log(` ${chalk.gray('Service Worker:')} āœ“`); console.log(` ${chalk.gray('Web Manifest:')} āœ“`); console.log(` ${chalk.gray('Offline Support:')} āœ“`); console.log(` ${chalk.gray('Push Notifications:')} āœ“`); } catch (error) { spinner.fail('PWA deployment failed'); console.error(chalk.red(error.message)); } } // Deploy All Platforms async function deployAll(projectRoot, config, options) { console.log(chalk.blue('šŸŽÆ Deploying to all platforms...\n')); try { await deployToServer(projectRoot, config, options); console.log(); await deployToPWA(projectRoot, config, options); console.log(); await deployToAndroid(projectRoot, config, options); console.log(); await deployToIOS(projectRoot, config, options); console.log(); await deployToDocker(projectRoot, config, options); console.log(chalk.green('\nšŸŽ‰ All platform deployments completed!')); } catch (error) { console.error(chalk.red(`Multi-platform deployment failed: ${error.message}`)); } } // Enhanced Build Web Assets async function buildWebAssets(projectRoot, buildOptions) { const { buildProject } = require('./build'); // Enhanced build configuration based on target const enhancedOptions = { ...buildOptions, minify: buildOptions.optimization, analyze: false, output: getBuildOutputDir(buildOptions.target) }; await buildProject(enhancedOptions); // Apply target-specific optimizations if (buildOptions.target === 'server') { await optimizeForServer(projectRoot, buildOptions); } else if (buildOptions.target === 'android' || buildOptions.target === 'ios') { await optimizeForMobile(projectRoot, buildOptions); } else if (buildOptions.target === 'docker') { await optimizeForDocker(projectRoot, buildOptions); } } function getBuildOutputDir(target) { const outputDirs = { server: 'dist', android: 'www', ios: 'www', docker: 'dist', pwa: 'dist' }; return outputDirs[target] || 'dist'; } // Server Configuration Generation async function generateServerConfig(projectRoot, config) { const serverDir = path.join(projectRoot, 'deploy', 'server'); await fs.ensureDir(serverDir); // Generate Express server configuration const serverConfig = generateExpressConfig(config); await fs.writeFile(path.join(serverDir, 'server.js'), serverConfig); // Generate nginx configuration const nginxConfig = generateNginxConfig(config); await fs.writeFile(path.join(serverDir, 'nginx.conf'), nginxConfig); // Generate PM2 configuration const pm2Config = generatePM2Config(config); await fs.writeFile(path.join(serverDir, 'ecosystem.config.js'), pm2Config); // Generate environment files await generateEnvironmentFiles(serverDir, config); } function generateExpressConfig(config) { return `// MusPE Express Server Configuration const express = require('express'); const path = require('path'); const compression = require('compression'); const helmet = require('helmet'); const morgan = require('morgan'); const cors = require('cors'); const app = express(); const PORT = process.env.PORT || 3000; const NODE_ENV = process.env.NODE_ENV || 'production'; // Security middleware app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], scriptSrc: ["'self'"], imgSrc: ["'self'", "data:", "https:"], connectSrc: ["'self'"], fontSrc: ["'self'"], objectSrc: ["'none'"], mediaSrc: ["'self'"], frameSrc: ["'none'"], }, }, })); // Enable compression app.use(compression()); // Enable CORS app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') || '*', credentials: true })); // Logging if (NODE_ENV !== 'test') { app.use(morgan('combined')); } // Static file serving with cache headers app.use(express.static(path.join(__dirname, '../../dist'), { maxAge: NODE_ENV === 'production' ? '1y' : '0', etag: true, lastModified: true, setHeaders: (res, filePath) => { if (filePath.endsWith('.html')) { res.setHeader('Cache-Control', 'no-cache'); } } })); // API routes (if any) app.use('/api', (req, res, next) => { // Add your API routes here res.status(404).json({ error: 'API endpoint not found' }); }); // SPA fallback - serve index.html for all non-API routes app.get('*', (req, res) => { res.sendFile(path.join(__dirname, '../../dist/index.html')); }); // Error handling middleware app.use((err, req, res, next) => { console.error(err.stack); res.status(500).json({ error: NODE_ENV === 'production' ? 'Internal Server Error' : err.message }); }); // Graceful shutdown process.on('SIGTERM', () => { console.log('SIGTERM signal received: closing HTTP server'); server.close(() => { console.log('HTTP server closed'); }); }); const server = app.listen(PORT, () => { console.log(\`šŸš€ MusPE app listening on port \${PORT} (environment: \${NODE_ENV})\`); }); module.exports = app; `; } function generateNginxConfig(config) { return `# MusPE Nginx Configuration server { listen 80; server_name ${config.domain || 'localhost'}; # Redirect HTTP to HTTPS (uncomment for production) # return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name ${config.domain || 'localhost'}; # SSL Configuration (update paths for production) # ssl_certificate /path/to/certificate.crt; # ssl_certificate_key /path/to/private.key; # SSL Security ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; # Security Headers add_header X-Frame-Options "SAMEORIGIN" always; add_header X-XSS-Protection "1; mode=block" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; # Gzip compression gzip on; gzip_vary on; gzip_min_length 1024; gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json; # Static assets with cache headers location ~* \\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { root /var/www/html; expires 1y; add_header Cache-Control "public, immutable"; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-XSS-Protection "1; mode=block" always; add_header X-Content-Type-Options "nosniff" always; } # HTML files with no cache location ~* \\.html$ { root /var/www/html; expires -1; add_header Cache-Control "no-cache, no-store, must-revalidate"; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-XSS-Protection "1; mode=block" always; add_header X-Content-Type-Options "nosniff" always; } # API proxy (if using separate API server) location /api/ { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; } # SPA fallback location / { root /var/www/html; try_files $uri $uri/ /index.html; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-XSS-Protection "1; mode=block" always; add_header X-Content-Type-Options "nosniff" always; } } `; } function generatePM2Config(config) { return `// PM2 Configuration for MusPE App module.exports = { apps: [{ name: '${config.name || 'muspe-app'}', script: './server.js', instances: 'max', exec_mode: 'cluster', env: { NODE_ENV: 'development', PORT: 3000 }, env_production: { NODE_ENV: 'production', PORT: 3000 }, error_file: './logs/err.log', out_file: './logs/out.log', log_file: './logs/combined.log', time: true, watch: false, max_memory_restart: '1G', node_args: '--max_old_space_size=4096' }] }; `; } // Docker Configuration Generation async function generateDockerFiles(projectRoot, config) { const dockerDir = path.join(projectRoot, 'deploy', 'docker'); await fs.ensureDir(dockerDir); // Generate Dockerfile const dockerfile = generateDockerfile(config); await fs.writeFile(path.join(projectRoot, 'Dockerfile'), dockerfile); // Generate docker-compose.yml const dockerCompose = generateDockerCompose(config); await fs.writeFile(path.join(projectRoot, 'docker-compose.yml'), dockerCompose); // Generate .dockerignore const dockerignore = generateDockerignore(); await fs.writeFile(path.join(projectRoot, '.dockerignore'), dockerignore); } function generateDockerfile(config) { return `# MusPE Docker Configuration # Multi-stage build for optimized production image # Build stage FROM node:18-alpine AS builder WORKDIR /app # Copy package files COPY package*.json ./ # Install dependencies RUN npm ci --only=production && npm cache clean --force # Copy source code COPY . . # Build the application RUN npm run build # Production stage FROM nginx:alpine AS production # Install Node.js for SSR support (optional) RUN apk add --no-cache nodejs npm # Copy built application COPY --from=builder /app/dist /usr/share/nginx/html # Copy nginx configuration COPY --from=builder /app/deploy/server/nginx.conf /etc/nginx/conf.d/default.conf # Copy server files for API support COPY --from=builder /app/deploy/server /app/server COPY --from=builder /app/node_modules /app/node_modules # Create logs directory RUN mkdir -p /var/log/nginx /app/logs # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\ CMD curl -f http://localhost/ || exit 1 # Expose ports EXPOSE 80 443 # Start services CMD ["sh", "-c", "nginx -g 'daemon off;' & node /app/server/server.js"] `; } function generateDockerCompose(config) { return `# MusPE Docker Compose Configuration version: '3.8' services: app: build: context: . dockerfile: Dockerfile container_name: ${config.name || 'muspe-app'} ports: - "80:80" - "443:443" - "3000:3000" environment: - NODE_ENV=production - PORT=3000 volumes: - ./logs:/app/logs # - ./ssl:/etc/nginx/ssl (uncomment for SSL) restart: unless-stopped networks: - muspe-network # Optional: Add database service # db: # image: postgres:15-alpine # container_name: ${config.name || 'muspe'}-db # environment: # POSTGRES_DB: ${config.name || 'muspe'} # POSTGRES_USER: postgres # POSTGRES_PASSWORD: password # volumes: # - postgres_data:/var/lib/postgresql/data # networks: # - muspe-network networks: muspe-network: driver: bridge # volumes: # postgres_data: `; } function generateDockerignore() { return `# MusPE Docker Ignore node_modules npm-debug.log* yarn-debug.log* yarn-error.log* # Build artifacts dist www platforms # Development files .git .gitignore .env.local .env.development .env.test # IDE files .vscode .idea *.swp *.swo # OS files .DS_Store Thumbs.db # Logs logs *.log # Testing coverage .jest # Documentation docs *.md !README.md `; } // Environment and Build Checks async function checkAndroidEnvironment() { try { await runCommand('java', ['-version']); await runCommand('android', ['list']); } catch (error) { throw new Error('Android SDK not found. Please install Android Studio and configure SDK.'); } } async function checkIOSEnvironment() { if (process.platform !== 'darwin') { throw new Error('iOS deployment is only available on macOS'); } try { await runCommand('xcodebuild', ['-version']); } catch (error) { throw new Error('Xcode not found. Please install Xcode from App Store.'); } } async function checkDockerEnvironment() { try { await runCommand('docker', ['--version']); } catch (error) { throw new Error('Docker not found. Please install Docker Desktop.'); } } // Utility Functions async function findProjectRoot() { let currentDir = process.cwd(); while (currentDir !== path.dirname(currentDir)) { const packageJsonPath = path.join(currentDir, 'package.json'); if (await fs.pathExists(packageJsonPath)) { const packageJson = await fs.readJSON(packageJsonPath); if (packageJson.dependencies && packageJson.dependencies['muspe-cli']) { return currentDir; } } currentDir = path.dirname(currentDir); } return null; } async function loadConfig(projectRoot) { const configPath = path.join(projectRoot, 'muspe.config.js'); if (await fs.pathExists(configPath)) { return require(configPath); } const packageJsonPath = path.join(projectRoot, 'package.json'); if (await fs.pathExists(packageJsonPath)) { const packageJson = await fs.readJSON(packageJsonPath); return packageJson.muspe || {}; } return {}; } async function runCommand(command, args, options = {}) { return new Promise((resolve, reject) => { const child = spawn(command, args, { stdio: 'pipe', ...options }); child.on('close', (code) => { if (code === 0) { resolve(); } else { reject(new Error(`Command ${command} failed with code ${code}`)); } }); child.on('error', reject); }); } module.exports = { deployProject };