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

386 lines (338 loc) • 11.6 kB
const fs = require('fs-extra'); const path = require('path'); const chalk = require('chalk'); const ora = require('ora'); const http = require('http'); const url = require('url'); const spawn = require('cross-spawn'); async function serveProject(options) { const projectRoot = findProjectRoot(); if (!projectRoot) { console.log(chalk.red('Not in a MusPE project directory')); return; } const config = await loadConfig(projectRoot); const port = options.port || config.server?.port || 3000; const host = options.host || config.server?.host || 'localhost'; const shouldOpen = options.open || config.server?.open || false; const spinner = ora('Starting development server...').start(); try { const server = await createDevServer(projectRoot, config, port, host); spinner.succeed(`Development server running at http://${host}:${port}`); console.log(chalk.cyan('\nšŸ“± MusPE Development Server')); console.log(chalk.gray(` Local: http://${host}:${port}`)); console.log(chalk.gray(` Network: http://${getNetworkIP()}:${port}`)); console.log(chalk.gray('\n Press Ctrl+C to stop\n')); // Open browser if requested if (shouldOpen) { await openBrowser(`http://${host}:${port}`); } // Keep the process alive process.on('SIGINT', () => { console.log(chalk.yellow('\nšŸ›‘ Shutting down development server...')); server.close(() => { console.log(chalk.green('āœ… Server stopped')); process.exit(0); }); }); } catch (error) { spinner.fail('Failed to start development server'); console.error(chalk.red(error.message)); } } function findProjectRoot() { let currentDir = process.cwd(); while (currentDir !== path.parse(currentDir).root) { const configPath = path.join(currentDir, 'muspe.config.js'); if (fs.existsSync(configPath)) { 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)) { try { delete require.cache[require.resolve(configPath)]; return require(configPath); } catch (error) { console.log(chalk.yellow('Warning: Failed to load muspe.config.js, using defaults')); return {}; } } return {}; } async function createDevServer(projectRoot, config, port, host) { const publicDir = path.join(projectRoot, 'public'); const srcDir = path.join(projectRoot, 'src'); const server = http.createServer(async (req, res) => { const parsedUrl = url.parse(req.url, true); let pathname = parsedUrl.pathname; // Handle root if (pathname === '/') { pathname = '/index.html'; } try { // Try to serve from public directory first let filePath = path.join(publicDir, pathname); // If not in public, try src directory if (!await fs.pathExists(filePath)) { filePath = path.join(srcDir, pathname); } // If still not found, serve index.html for SPA routing if (!await fs.pathExists(filePath) && !pathname.includes('.')) { filePath = path.join(publicDir, 'index.html'); } if (await fs.pathExists(filePath)) { const ext = path.extname(filePath).toLowerCase(); const mimeTypes = { '.html': 'text/html', '.js': 'application/javascript', '.css': 'text/css', '.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.svg': 'image/svg+xml', '.ico': 'image/x-icon', '.woff': 'font/woff', '.woff2': 'font/woff2', '.ttf': 'font/ttf', '.eot': 'application/vnd.ms-fontobject' }; const contentType = mimeTypes[ext] || 'application/octet-stream'; res.setHeader('Content-Type', contentType); res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); // Process CSS files for imports and Tailwind if (ext === '.css') { const content = await processCSSFile(filePath, config); res.writeHead(200); res.end(content); } else { const content = await fs.readFile(filePath); res.writeHead(200); res.end(content); } } else { // 404 page res.writeHead(404, { 'Content-Type': 'text/html' }); res.end(generate404Page()); } } catch (error) { console.error('Server error:', error); res.writeHead(500, { 'Content-Type': 'text/html' }); res.end(generate500Page(error)); } }); return new Promise((resolve, reject) => { server.listen(port, host, (error) => { if (error) { reject(error); } else { resolve(server); } }); }); } async function processCSSFile(filePath, config) { const content = await fs.readFile(filePath, 'utf8'); // If Tailwind is configured, process Tailwind directives if (config.framework === 'tailwind') { return await processTailwindCSS(content, filePath); } return content; } async function processTailwindCSS(content, filePath) { // Simple Tailwind processing for development // In a real implementation, you'd integrate with PostCSS and Tailwind CLI const tailwindBase = ` /* Tailwind CSS Base Styles */ *, ::before, ::after { box-sizing: border-box; border-width: 0; border-style: solid; border-color: #e5e7eb; } html { line-height: 1.5; -webkit-text-size-adjust: 100%; -moz-tab-size: 4; tab-size: 4; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif; } body { margin: 0; line-height: inherit; } /* Responsive Design */ .container { max-width: 100%; margin: 0 auto; padding: 0 1rem; } @media (min-width: 640px) { .container { max-width: 640px; } } @media (min-width: 768px) { .container { max-width: 768px; } } @media (min-width: 1024px) { .container { max-width: 1024px; } } /* Flexbox */ .flex { display: flex; } .flex-col { flex-direction: column; } .items-center { align-items: center; } .justify-center { justify-content: center; } .space-y-2 > * + * { margin-top: 0.5rem; } .space-y-4 > * + * { margin-top: 1rem; } /* Grid */ .grid { display: grid; } .grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } .gap-4 { gap: 1rem; } /* Spacing */ .p-4 { padding: 1rem; } .p-6 { padding: 1.5rem; } .py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; } .px-6 { padding-left: 1.5rem; padding-right: 1.5rem; } .m-0 { margin: 0; } .my-6 { margin-top: 1.5rem; margin-bottom: 1.5rem; } /* Colors */ .bg-primary-500 { background-color: #3b82f6; } .bg-primary-600 { background-color: #2563eb; } .bg-white { background-color: #ffffff; } .bg-gray-50 { background-color: #f9fafb; } .text-white { color: #ffffff; } .text-primary-500 { color: #3b82f6; } /* Typography */ .text-center { text-align: center; } .font-bold { font-weight: 700; } .text-2xl { font-size: 1.5rem; } /* Border */ .rounded-lg { border-radius: 0.5rem; } .rounded-xl { border-radius: 0.75rem; } .shadow-lg { box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); } /* Effects */ .transition-colors { transition-property: color, background-color, border-color; transition-duration: 150ms; } .hover\\:bg-primary-600:hover { background-color: #2563eb; } /* Responsive */ .max-w-md { max-width: 28rem; } .mx-auto { margin-left: auto; margin-right: auto; } .min-h-screen { min-height: 100vh; } `; // Replace Tailwind directives with base styles let processedContent = content .replace('@tailwind base;', tailwindBase) .replace('@tailwind components;', '/* Components will be processed here */') .replace('@tailwind utilities;', '/* Utilities will be processed here */'); return processedContent; } function generate404Page() { return ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>404 - Page Not Found</title> <style> body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 2rem; text-align: center; background: #f8f9fa; color: #333; } .container { max-width: 400px; margin: 2rem auto; background: white; padding: 2rem; border-radius: 1rem; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } h1 { color: #e74c3c; margin-bottom: 1rem; } .emoji { font-size: 4rem; margin-bottom: 1rem; } a { color: #3b82f6; text-decoration: none; } a:hover { text-decoration: underline; } </style> </head> <body> <div class="container"> <div class="emoji">šŸ“±</div> <h1>404 - Page Not Found</h1> <p>The page you're looking for doesn't exist.</p> <p><a href="/">← Back to Home</a></p> <p><small>MusPE Development Server</small></p> </div> </body> </html>`; } function generate500Page(error) { return ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>500 - Server Error</title> <style> body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 2rem; text-align: center; background: #f8f9fa; color: #333; } .container { max-width: 500px; margin: 2rem auto; background: white; padding: 2rem; border-radius: 1rem; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } h1 { color: #e74c3c; margin-bottom: 1rem; } .emoji { font-size: 4rem; margin-bottom: 1rem; } .error { background: #fee; padding: 1rem; border-radius: 0.5rem; margin: 1rem 0; text-align: left; font-family: monospace; color: #c53030; } a { color: #3b82f6; text-decoration: none; } a:hover { text-decoration: underline; } </style> </head> <body> <div class="container"> <div class="emoji">āš ļø</div> <h1>500 - Server Error</h1> <p>Something went wrong on the server.</p> <div class="error">${error.message}</div> <p><a href="/">← Back to Home</a></p> <p><small>MusPE Development Server</small></p> </div> </body> </html>`; } function getNetworkIP() { const interfaces = require('os').networkInterfaces(); for (const name of Object.keys(interfaces)) { for (const interface of interfaces[name]) { if (interface.family === 'IPv4' && !interface.internal) { return interface.address; } } } return 'localhost'; } async function openBrowser(url) { const open = (await import('open')).default; try { await open(url); } catch (error) { console.log(chalk.yellow(`Could not open browser automatically. Please visit: ${url}`)); } } module.exports = { serveProject };