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
JavaScript
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 };