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

1,720 lines (1,464 loc) • 61.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 validatePackageName = require('validate-npm-package-name'); async function createProject(projectName, options) { const spinner = ora('Creating MusPE project...').start(); try { // Validate project name const validation = validatePackageName(projectName); if (!validation.validForNewPackages) { spinner.fail('Invalid project name'); console.log(chalk.red('Project name contains invalid characters')); return; } const projectPath = path.join(process.cwd(), projectName); // Check if directory exists if (await fs.pathExists(projectPath)) { spinner.fail('Directory already exists'); console.log(chalk.red(`Directory ${projectName} already exists`)); return; } // Interactive template selection if not specified let template = options.template; let framework = options.framework; if (!options.template || !options.framework) { spinner.stop(); const answers = await inquirer.prompt([ { type: 'list', name: 'template', message: 'Choose your project template:', choices: [ { name: 'šŸ“± Mobile App - Optimized for mobile devices', value: 'mobile' }, { name: '🌐 Web App - Traditional web application', value: 'web' }, { name: '⚔ PWA - Progressive Web App', value: 'pwa' }, { name: 'šŸ”€ Hybrid - Mobile + Web hybrid app', value: 'hybrid' } ], default: 'mobile' }, { type: 'list', name: 'framework', message: 'Choose your CSS framework:', choices: [ { name: 'šŸŽØ Tailwind CSS - Utility-first CSS framework', value: 'tailwind' }, { name: 'šŸ…±ļø Bootstrap - Popular CSS framework', value: 'bootstrap' }, { name: 'šŸ“ Custom CSS - Pure CSS (no framework)', value: 'none' } ], default: 'tailwind' } ]); template = answers.template; framework = answers.framework; spinner.start('Creating project structure...'); } // Create project directory await fs.ensureDir(projectPath); // Generate project files based on template await generateProjectStructure(projectPath, projectName, template, framework); spinner.succeed('Project structure created'); // Install dependencies if (!options.skipInstall) { spinner.start('Installing dependencies...'); await installDependencies(projectPath); spinner.succeed('Dependencies installed'); } // Success message console.log(chalk.green(`\n✨ Successfully created ${projectName}!`)); console.log(chalk.cyan('\nNext steps:')); console.log(` ${chalk.gray('$')} cd ${projectName}`); console.log(` ${chalk.gray('$')} muspe serve`); console.log(` ${chalk.gray('$')} muspe serve --debug ${chalk.dim('(for debugging)')}`); console.log(chalk.gray('\nHappy coding! šŸš€\n')); } catch (error) { spinner.fail('Failed to create project'); console.error(chalk.red(error.message)); } } async function generateProjectStructure(projectPath, projectName, template, framework) { // Create comprehensive structure with centralized imports const dirs = [ 'src', 'src/components', 'src/pages', 'src/styles', 'src/styles/themes', 'src/scripts', 'src/config', 'src/routes', 'src/models', 'src/services', 'src/utils', 'src/core', 'src/assets', 'src/assets/images', 'src/assets/icons', 'public' ]; for (const dir of dirs) { await fs.ensureDir(path.join(projectPath, dir)); } // Generate package.json with debugging dependencies const packageJson = { name: projectName, version: '1.0.0', description: `A MusPE ${template} application`, main: 'src/app.js', scripts: { start: 'muspe serve', build: 'muspe build', dev: 'muspe serve --open --debug', debug: 'muspe serve --debug --port 3001', test: 'echo "No tests specified"' }, dependencies: {}, devDependencies: {}, muspe: { template, framework, version: '2.0.0', features: { debugging: true, centralized_imports: true, sample_pages: true } } }; // Add framework-specific dependencies if (framework === 'tailwind') { packageJson.devDependencies['tailwindcss'] = '^3.3.0'; packageJson.devDependencies['autoprefixer'] = '^10.4.14'; packageJson.devDependencies['postcss'] = '^8.4.24'; } else if (framework === 'bootstrap') { packageJson.dependencies['bootstrap'] = '^5.3.0'; } await fs.writeJSON(path.join(projectPath, 'package.json'), packageJson, { spaces: 2 }); // Generate configuration files await generateConfigFiles(projectPath, template, framework); // Generate template files await generateTemplateFiles(projectPath, projectName, template, framework); // Copy core utilities await copyUtilities(projectPath); } async function generateConfigFiles(projectPath, template, framework) { // MusPE config const config = `module.exports = { template: '${template}', framework: '${framework}', server: { port: 3000, host: 'localhost', open: true }, build: { outDir: 'dist', minify: true, sourcemap: true }, pwa: { enabled: ${template === 'pwa' || template === 'hybrid'}, manifest: './public/manifest.json', serviceWorker: './src/sw.js' }, mobile: { viewport: 'width=device-width, initial-scale=1.0, user-scalable=no', statusBar: 'default', orientation: 'portrait' } };`; await fs.writeFile(path.join(projectPath, 'muspe.config.js'), config); // Generate framework-specific configs if (framework === 'tailwind') { const tailwindConfig = `/** @type {import('tailwindcss').Config} */ module.exports = { content: [ "./src/**/*.{html,js}", "./public/**/*.html" ], theme: { extend: { screens: { 'xs': '475px', }, colors: { primary: { 50: '#eff6ff', 500: '#3b82f6', 600: '#2563eb', } } }, }, plugins: [], }`; await fs.writeFile(path.join(projectPath, 'tailwind.config.js'), tailwindConfig); const postcssConfig = `module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, }`; await fs.writeFile(path.join(projectPath, 'postcss.config.js'), postcssConfig); } } async function generateTemplateFiles(projectPath, projectName, template, framework) { // Generate index.html with centralized imports const htmlContent = generateHTMLTemplate(projectName, template, framework); await fs.writeFile(path.join(projectPath, 'public/index.html'), htmlContent); // Generate centralized configuration await generateAppConfig(projectPath, projectName, template, framework); // Generate centralized imports await generateCentralizedImports(projectPath, projectName, template, framework); // Generate theme system await generateThemeSystem(projectPath, framework); // Generate sample pages and components await generateSamplePages(projectPath, framework); await generateSampleComponents(projectPath, framework); // Generate models await generateModels(projectPath); // Generate routes configuration await generateRoutesConfig(projectPath); // Generate main application files await generateMainApp(projectPath, projectName, template, framework); // Generate debugging utilities await generateDebuggingUtils(projectPath); // Copy Cordova demo component and service for mobile/hybrid apps if (template === 'mobile' || template === 'hybrid') { await copyCordovaFiles(projectPath); } // Generate PWA files if needed if (template === 'pwa' || template === 'hybrid') { await generatePWAFiles(projectPath, projectName); } // Generate README const readmeContent = generateREADME(projectName, template, framework); await fs.writeFile(path.join(projectPath, 'README.md'), readmeContent); } function generateHTMLTemplate(projectName, template, framework) { const isFramework7Like = template === 'mobile' || template === 'hybrid'; return `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0${isFramework7Like ? ', user-scalable=no' : ''}"> <meta name="description" content="${projectName} - Built with MusPE"> <title>${projectName}</title> <!-- Centralized CSS Imports --> <link href="./src/styles/main.css" rel="stylesheet"> <link href="./src/styles/themes/default.css" rel="stylesheet"> ${framework === 'tailwind' ? '<link href="./src/styles/tailwind.css" rel="stylesheet">' : ''} ${framework === 'bootstrap' ? '<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">' : ''} ${(template === 'pwa' || template === 'hybrid') ? '<link rel="manifest" href="./manifest.json">' : ''} <!-- Mobile optimizations --> <meta name="theme-color" content="var(--primary-color)"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="default"> <!-- Prevent zoom on input focus (mobile) --> ${isFramework7Like ? '<meta name="format-detection" content="telephone=no">' : ''} <!-- Debugging styles --> <style id="debug-styles" style="display: none;"> .debug-info { position: fixed; top: 10px; right: 10px; background: rgba(0, 0, 0, 0.8); color: white; padding: 10px; border-radius: 5px; font-family: monospace; font-size: 12px; z-index: 10000; } .error-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255, 0, 0, 0.1); backdrop-filter: blur(2px); z-index: 9999; display: flex; align-items: center; justify-content: center; } .error-content { background: white; border: 2px solid #ef4444; border-radius: 10px; padding: 20px; max-width: 90%; max-height: 80%; overflow: auto; } </style> </head> <body> <!-- Debug Info Panel --> <div id="debug-info" class="debug-info" style="display: none;"></div> <!-- Error Overlay --> <div id="error-overlay" class="error-overlay" style="display: none;"> <div class="error-content"> <h3>🚨 Application Error</h3> <pre id="error-details"></pre> <button onclick="MusPEDebug.clearError()">Close</button> </div> </div> <div id="app"> <!-- Loading indicator --> <div id="app-loading" class="loading-screen"> <div class="loader"></div> <p>Loading ${projectName}...</p> </div> <!-- App will be rendered here --> </div> <!-- Centralized JavaScript Imports --> <!-- Core and utilities first --> <script src="./src/core/muspe.js"></script> <script src="./src/utils/dom.js"></script> <script src="./src/utils/fetch.js"></script> <script src="./src/utils/debug.js"></script> <!-- Configuration --> <script src="./src/config/app.config.js"></script> <script src="./src/config/theme.config.js"></script> <!-- Models --> <script src="./src/models/index.js"></script> <!-- Services --> <script src="./src/services/index.js"></script> <!-- Components (loaded via centralized imports) --> <script src="./src/components/index.js"></script> <!-- Pages (loaded via centralized imports) --> <script src="./src/pages/index.js"></script> <!-- Routes --> <script src="./src/routes/index.js"></script> <!-- Main application --> <script src="./src/app.js"></script> ${framework === 'bootstrap' ? '<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>' : ''} <!-- Initialize app --> <script> // Initialize debugging if enabled if (window.location.search.includes('debug=true') || localStorage.getItem('muspe-debug') === 'true') { MusPEDebug.enable(); } // Initialize application document.addEventListener('DOMContentLoaded', () => { window.app = new MusPEApp(); window.app.init(); }); </script> </body> </html>`; } // Generate centralized app configuration async function generateAppConfig(projectPath, projectName, template, framework) { const config = `// MusPE App Configuration - Central configuration for the entire application class MusPEAppConfig { constructor() { this.app = { name: '${projectName}', shortName: '${projectName}', description: 'A ${template} application built with MusPE framework', version: '1.0.0', author: 'MusPE Developer', language: 'en', debug: window.location.search.includes('debug=true') || localStorage.getItem('muspe-debug') === 'true' }; this.theme = { primary: '#3b82f6', secondary: '#10b981', accent: '#f59e0b', background: '#f9fafb', surface: '#ffffff', error: '#ef4444', warning: '#f59e0b', success: '#10b981', text: { primary: '#111827', secondary: '#6b7280', disabled: '#d1d5db' }, sizes: { xs: '0.75rem', sm: '0.875rem', base: '1rem', lg: '1.125rem', xl: '1.25rem', '2xl': '1.5rem', '3xl': '1.875rem', '4xl': '2.25rem' }, spacing: { xs: '0.25rem', sm: '0.5rem', md: '1rem', lg: '1.5rem', xl: '2rem', '2xl': '3rem' } }; this.layout = { container: { maxWidth: '1200px', padding: '1rem' }, header: { height: '64px', sticky: true }, footer: { show: true, height: '60px' } }; this.routes = [ { path: '', name: 'home', component: 'HomePage', title: 'Home - ${projectName}', meta: { description: 'Welcome to ${projectName}', keywords: ['home', '${template}'] } }, { path: 'documentation', name: 'documentation', component: 'DocumentationPage', title: 'Documentation - ${projectName}', meta: { description: 'Learn how to use ${projectName}', keywords: ['docs', 'documentation', 'guide'] } }, { path: 'components', name: 'components', component: 'ComponentsPage', title: 'Components - ${projectName}', meta: { description: 'View available components', keywords: ['components', 'ui', 'examples'] } } ]; this.api = { baseURL: window.location.origin, timeout: 5000, retries: 3 }; this.debugging = { enabled: this.app.debug, showPerformance: true, showErrors: true, showWarnings: true, logLevel: 'info' }; } // Get theme CSS variables getThemeCSS() { const vars = []; Object.entries(this.theme).forEach(([key, value]) => { if (typeof value === 'object') { Object.entries(value).forEach(([subKey, subValue]) => { vars.push(\`--\${key}-\${subKey}: \${subValue};\`); }); } else { vars.push(\`--\${key}: \${value};\`); } }); return vars.join('\\n '); } // Get route by name getRoute(name) { return this.routes.find(route => route.name === name); } // Get route by path getRouteByPath(path) { return this.routes.find(route => route.path === path); } // Debug mode methods enableDebug() { this.app.debug = true; this.debugging.enabled = true; localStorage.setItem('muspe-debug', 'true'); } disableDebug() { this.app.debug = false; this.debugging.enabled = false; localStorage.removeItem('muspe-debug'); } } // Create global config instance window.AppConfig = new MusPEAppConfig();`; await fs.writeFile(path.join(projectPath, 'src/config/app.config.js'), config); } // Generate theme configuration async function generateThemeConfig(projectPath) { const themeConfig = `// Theme Configuration - Centralized theme management class ThemeConfig { constructor() { this.themes = { light: { name: 'Light Theme', primary: '#3b82f6', secondary: '#10b981', background: '#ffffff', surface: '#f8fafc', text: '#1f2937' }, dark: { name: 'Dark Theme', primary: '#60a5fa', secondary: '#34d399', background: '#1f2937', surface: '#374151', text: '#f9fafb' } }; this.currentTheme = 'light'; } // Apply theme applyTheme(themeName) { const theme = this.themes[themeName]; if (!theme) return; const root = document.documentElement; Object.entries(theme).forEach(([key, value]) => { if (key !== 'name') { root.style.setProperty(\`--theme-\${key}\`, value); } }); this.currentTheme = themeName; localStorage.setItem('muspe-theme', themeName); } // Get current theme getCurrentTheme() { return this.themes[this.currentTheme]; } // Toggle between light and dark toggleTheme() { const newTheme = this.currentTheme === 'light' ? 'dark' : 'light'; this.applyTheme(newTheme); } } window.ThemeConfig = new ThemeConfig();`; await fs.writeFile(path.join(projectPath, 'src/config/theme.config.js'), themeConfig); } // Generate centralized imports async function generateCentralizedImports(projectPath, projectName, template, framework) { await generateThemeConfig(projectPath); // Generate main CSS with centralized imports const mainCSS = generateCSSTemplate(framework); await fs.writeFile(path.join(projectPath, 'src/styles/main.css'), mainCSS); // Generate Tailwind CSS if needed if (framework === 'tailwind') { const tailwindCSS = `@tailwind base; @tailwind components; @tailwind utilities; /* Import theme variables */ @import './themes/default.css'; /* Import component styles */ @import './components.css'; /* Custom utilities */ @layer utilities { .debug-border { border: 1px solid red !important; } .debug-bg { background-color: rgba(255, 0, 0, 0.1) !important; } }`; await fs.writeFile(path.join(projectPath, 'src/styles/tailwind.css'), tailwindCSS); } // Generate component styles const componentCSS = `/* Component Styles - Centralized component styling */ .loading-screen { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; background: var(--background); } .loader { width: 40px; height: 40px; border: 4px solid var(--surface); border-left: 4px solid var(--primary); border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* App Header Component */ .app-header { background: var(--primary); color: white; padding: var(--spacing-lg); text-align: center; position: sticky; top: 0; z-index: 100; } /* Navigation Component */ .app-nav { background: var(--surface); padding: var(--spacing-md); border-bottom: 1px solid var(--border); } .nav-list { display: flex; list-style: none; gap: var(--spacing-md); margin: 0; padding: 0; } .nav-item a { text-decoration: none; color: var(--text-primary); padding: var(--spacing-sm) var(--spacing-md); border-radius: 4px; transition: background-color 0.2s; } .nav-item a:hover, .nav-item a.active { background: var(--primary); color: white; } /* Card Component */ .card { background: var(--surface); border-radius: 8px; padding: var(--spacing-lg); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); margin-bottom: var(--spacing-md); } /* Button Components */ .btn { display: inline-block; padding: var(--spacing-sm) var(--spacing-lg); border: none; border-radius: 4px; cursor: pointer; text-decoration: none; font-weight: 500; transition: all 0.2s; } .btn-primary { background: var(--primary); color: white; } .btn-primary:hover { background: var(--primary-dark, #2563eb); } .btn-secondary { background: var(--secondary); color: white; } .btn-outline { background: transparent; border: 2px solid var(--primary); color: var(--primary); }`; await fs.writeFile(path.join(projectPath, 'src/styles/components.css'), componentCSS); } // Generate theme system async function generateThemeSystem(projectPath, framework) { const defaultTheme = `/* Default Theme - CSS Custom Properties */ :root { /* Colors */ --primary: #3b82f6; --primary-dark: #2563eb; --primary-light: #dbeafe; --secondary: #10b981; --secondary-dark: #059669; --secondary-light: #d1fae5; --accent: #f59e0b; --background: #ffffff; --surface: #f8fafc; --border: #e5e7eb; --error: #ef4444; --warning: #f59e0b; --success: #10b981; /* Text Colors */ --text-primary: #111827; --text-secondary: #6b7280; --text-disabled: #d1d5db; /* Spacing */ --spacing-xs: 0.25rem; --spacing-sm: 0.5rem; --spacing-md: 1rem; --spacing-lg: 1.5rem; --spacing-xl: 2rem; --spacing-2xl: 3rem; /* Font Sizes */ --text-xs: 0.75rem; --text-sm: 0.875rem; --text-base: 1rem; --text-lg: 1.125rem; --text-xl: 1.25rem; --text-2xl: 1.5rem; --text-3xl: 1.875rem; --text-4xl: 2.25rem; /* Shadows */ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1); --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1); /* Border Radius */ --radius-sm: 0.25rem; --radius-md: 0.375rem; --radius-lg: 0.5rem; --radius-xl: 0.75rem; /* Transitions */ --transition-fast: 0.15s ease-in-out; --transition-normal: 0.3s ease-in-out; --transition-slow: 0.5s ease-in-out; } /* Dark theme */ [data-theme="dark"] { --primary: #60a5fa; --primary-dark: #3b82f6; --primary-light: #1e3a8a; --background: #111827; --surface: #1f2937; --border: #374151; --text-primary: #f9fafb; --text-secondary: #d1d5db; --text-disabled: #6b7280; } /* High contrast theme */ [data-theme="high-contrast"] { --primary: #000000; --background: #ffffff; --surface: #f0f0f0; --text-primary: #000000; --border: #000000; }`; await fs.writeFile(path.join(projectPath, 'src/styles/themes/default.css'), defaultTheme); } function generateCSSTemplate(framework) { if (framework === 'tailwind') { return `/* MusPE Framework - Tailwind Base Styles */ @import './themes/default.css'; @import './components.css'; /* Base reset and utilities */ * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: var(--text-primary); background-color: var(--background); } /* Page structure */ .page { min-height: 100vh; display: flex; flex-direction: column; } .page-header { background: var(--primary); color: white; padding: var(--spacing-xl); text-align: center; } .page-content { flex: 1; padding: var(--spacing-lg); } .container { max-width: 1200px; margin: 0 auto; padding: 0 var(--spacing-md); }`; } return `/* MusPE Framework - Mobile-First CSS */ /* Import theme variables */ @import './themes/default.css'; @import './components.css'; /* Base reset */ * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: var(--text-primary); background-color: var(--background); } /* Page structure */ .page { min-height: 100vh; display: flex; flex-direction: column; } .page-header { background: linear-gradient(135deg, var(--primary), var(--primary-dark)); color: white; padding: var(--spacing-xl); text-align: center; } .page-header h1 { font-size: var(--text-3xl); margin-bottom: var(--spacing-sm); } .page-content { flex: 1; padding: var(--spacing-lg); } .container { max-width: 1200px; margin: 0 auto; padding: 0 var(--spacing-md); } /* App footer */ .app-footer { background: var(--surface); padding: var(--spacing-md); text-align: center; border-top: 1px solid var(--border); color: var(--text-secondary); font-size: var(--text-sm); } /* Feature grid */ .features-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: var(--spacing-lg); margin: var(--spacing-xl) 0; } .feature-card { background: var(--surface); border-radius: var(--radius-lg); padding: var(--spacing-lg); text-align: center; box-shadow: var(--shadow-md); transition: var(--transition-normal); cursor: pointer; } .feature-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); } .feature-card .icon { font-size: var(--text-4xl); margin-bottom: var(--spacing-md); display: block; } /* Action buttons */ .action-buttons { display: flex; gap: var(--spacing-md); justify-content: center; flex-wrap: wrap; margin-top: var(--spacing-xl); } /* Mobile optimizations */ @media (max-width: 768px) { .container { padding: 0 var(--spacing-sm); } .page-header { padding: var(--spacing-lg); } .features-grid { grid-template-columns: 1fr; gap: var(--spacing-md); } .action-buttons { flex-direction: column; align-items: stretch; } }`; } // Continue with the rest of the functions... async function copyUtilities(projectPath) { const utilsDir = path.join(projectPath, 'src/utils'); await fs.ensureDir(utilsDir); // Copy DOM utilities const domUtilsSource = path.join(__dirname, '../utils/dom.js'); const domUtilsTarget = path.join(utilsDir, 'dom.js'); if (await fs.pathExists(domUtilsSource)) { await fs.copy(domUtilsSource, domUtilsTarget); } // Copy Fetch utilities const fetchUtilsSource = path.join(__dirname, '../utils/fetch.js'); const fetchUtilsTarget = path.join(utilsDir, 'fetch.js'); if (await fs.pathExists(fetchUtilsSource)) { await fs.copy(fetchUtilsSource, fetchUtilsTarget); } // Copy core framework const coreDir = path.join(projectPath, 'src/core'); await fs.ensureDir(coreDir); const coreSource = path.join(__dirname, '../core/muspe.js'); const coreTarget = path.join(coreDir, 'muspe.js'); if (await fs.pathExists(coreSource)) { await fs.copy(coreSource, coreTarget); } } async function copyCordovaFiles(projectPath) { // Copy Cordova demo component const componentDir = path.join(projectPath, 'src/components'); const componentSource = path.join(__dirname, '../components/CordovaDemo.js'); const componentTarget = path.join(componentDir, 'CordovaDemo.js'); if (await fs.pathExists(componentSource)) { await fs.copy(componentSource, componentTarget); } // Copy Cordova service const serviceDir = path.join(projectPath, 'src/services'); await fs.ensureDir(serviceDir); const serviceSource = path.join(__dirname, '../services/CordovaService.js'); const serviceTarget = path.join(serviceDir, 'CordovaService.js'); if (await fs.pathExists(serviceSource)) { await fs.copy(serviceSource, serviceTarget); } } async function generatePWAFiles(projectPath, projectName) { // Generate manifest.json const manifest = { name: projectName, short_name: projectName, description: `${projectName} - Built with MusPE`, start_url: '/', display: 'standalone', background_color: '#ffffff', theme_color: '#3b82f6', icons: [ { src: './assets/icons/icon-192x192.png', sizes: '192x192', type: 'image/png' }, { src: './assets/icons/icon-512x512.png', sizes: '512x512', type: 'image/png' } ] }; await fs.writeJSON(path.join(projectPath, 'public/manifest.json'), manifest, { spaces: 2 }); // Generate service worker const serviceWorker = `// MusPE Service Worker const CACHE_NAME = '${projectName}-v1'; const urlsToCache = [ '/', '/src/styles/main.css', '/src/scripts/main.js' ]; self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME) .then((cache) => cache.addAll(urlsToCache)) ); }); self.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request) .then((response) => { return response || fetch(event.request); }) ); });`; await fs.writeFile(path.join(projectPath, 'public/sw.js'), serviceWorker); } async function installDependencies(projectPath) { return new Promise((resolve, reject) => { const child = spawn('npm', ['install'], { cwd: projectPath, stdio: 'pipe' }); child.on('close', (code) => { if (code !== 0) { reject(new Error(`npm install failed with code ${code}`)); } else { resolve(); } }); child.on('error', reject); }); } function generateREADME(projectName, template, framework) { return `# ${projectName} A ${template} application built with **MusPE Framework** - Mobile User-friendly Simple Progressive Engine. ## šŸš€ Features - šŸ“± Mobile-first design with responsive layout - ⚔ Fast and lightweight architecture - šŸŽØ ${framework === 'tailwind' ? 'Tailwind CSS with custom design system' : framework === 'bootstrap' ? 'Bootstrap 5 styling' : 'Custom CSS with CSS variables theming'} - šŸ—ļø **Centralized imports** for pages, components, models, and services - šŸŽÆ **Sample pages**: Documentation and Components showcase - šŸ› **Built-in debugging** with error overlay and performance monitoring - šŸŽØ **Theme system** with light/dark mode support - šŸ”§ Easy component and page generation - šŸ“¦ Built-in development server with hot reload ${template === 'pwa' || template === 'hybrid' ? '- šŸ”„ Progressive Web App features' : ''} ## šŸ“‹ Getting Started \`\`\`bash # Start development server npm start # Start in debug mode npm run debug # Build for production npm run build \`\`\` ## šŸ› Debugging Features ### Enable Debug Mode - Add \`?debug=true\` to URL - Use \`npm run debug\` - Call \`MusPEDebug.enable()\` in console ### Debug Features - **Error Overlay**: Visual error display with stack traces - **Debug Panel**: Shows logs, errors, and app stats - **Performance Monitoring**: Memory usage and timing info ### Debug Shortcuts - \`Ctrl/Cmd + Shift + D\` - Toggle debug mode - \`Ctrl/Cmd + Shift + T\` - Toggle theme Happy coding with MusPE! šŸš€ `; } // Generate sample pages async function generateSamplePages(projectPath, framework) { // Home Page const homePage = `// Home Page - Welcome page with getting started guide class HomePage { constructor() { this.name = 'HomePage'; this.title = 'Home'; } render() { return \` <div class="page home-page"> <header class="page-header"> <h1>šŸš€ Welcome to your MusPE App!</h1> <p>Your application is ready to go with centralized architecture.</p> </header> <main class="page-content"> <div class="container"> <div class="welcome-section"> <div class="card"> <h2>Getting Started</h2> <p>Your application is set up with a centralized architecture for easy development:</p> <div class="features-grid"> <div class="feature-card"> <span class="icon">šŸ“</span> <h3>Organized Structure</h3> <p>Components, pages, models, and services are centrally managed</p> </div> <div class="feature-card"> <span class="icon">šŸŽØ</span> <h3>Theme System</h3> <p>CSS custom properties for consistent theming</p> </div> <div class="feature-card"> <span class="icon">šŸ”§</span> <h3>Debug Mode</h3> <p>Built-in debugging tools for development</p> </div> <div class="feature-card"> <span class="icon">šŸ“±</span> <h3>Mobile First</h3> <p>Responsive design optimized for mobile devices</p> </div> </div> <div class="action-buttons"> <button class="btn btn-primary" onclick="MusPERouter.navigate('/documentation')"> šŸ“– View Documentation </button> <button class="btn btn-secondary" onclick="MusPERouter.navigate('/components')"> 🧩 See Components </button> <button class="btn btn-outline" onclick="MusPEDebug.toggle()"> šŸ› Toggle Debug </button> </div> </div> </div> </div> </main> </div> \`; } afterRender() { console.log('Home page rendered'); this.setupInteractions(); } setupInteractions() { // Add hover effects to feature cards const featureCards = document.querySelectorAll('.feature-card'); featureCards.forEach(card => { card.addEventListener('mouseenter', () => { card.style.transform = 'translateY(-2px)'; card.style.boxShadow = 'var(--shadow-lg)'; }); card.addEventListener('mouseleave', () => { card.style.transform = 'translateY(0)'; card.style.boxShadow = 'var(--shadow-md)'; }); }); } beforeDestroy() { console.log('Home page being destroyed'); } } // Register page window.HomePage = HomePage;`; await fs.writeFile(path.join(projectPath, 'src/pages/HomePage.js'), homePage); // Documentation Page const documentationPage = `// Documentation Page - Application guide and API reference class DocumentationPage { constructor() { this.name = 'DocumentationPage'; this.title = 'Documentation'; } render() { return \` <div class="page documentation-page"> <header class="page-header"> <h1>šŸ“– Documentation</h1> <p>Learn how to build with MusPE framework</p> </header> <main class="page-content"> <div class="container"> <div class="card"> <h2>šŸš€ Getting Started</h2> <p>Your MusPE application is ready! Here's what you can do:</p> <h3>šŸ“ Project Structure</h3> <ul> <li><strong>src/components/</strong> - Reusable UI components</li> <li><strong>src/pages/</strong> - Application pages/views</li> <li><strong>src/models/</strong> - Data models</li> <li><strong>src/config/</strong> - App configuration</li> <li><strong>src/styles/</strong> - CSS and themes</li> <li><strong>src/utils/</strong> - Debugging and utilities</li> </ul> <h3>šŸ› Debugging</h3> <p>Debug tools are built-in:</p> <ul> <li>Add <code>?debug=true</code> to URL</li> <li>Use <code>MusPEDebug.enable()</code> in console</li> <li>Press Ctrl/Cmd + Shift + D</li> </ul> <h3>šŸŽØ Theming</h3> <p>Built-in theme system with CSS variables:</p> <ul> <li>Toggle with šŸŒ“ button or <code>ThemeConfig.toggleTheme()</code></li> <li>Customize in <code>src/styles/themes/</code></li> <li>All colors use CSS custom properties</li> </ul> </div> </div> </main> </div> \`; } afterRender() { console.log('Documentation page rendered'); } beforeDestroy() { console.log('Documentation page being destroyed'); } } // Register page window.DocumentationPage = DocumentationPage;`; await fs.writeFile(path.join(projectPath, 'src/pages/DocumentationPage.js'), documentationPage); // Components Page const componentsPage = `// Components Page - Showcase of available components class ComponentsPage { constructor() { this.name = 'ComponentsPage'; this.title = 'Components'; } render() { return \` <div class="page components-page"> <header class="page-header"> <h1>🧩 Components Showcase</h1> <p>Interactive examples of available UI components</p> </header> <main class="page-content"> <div class="container"> <div class="card"> <h2>Available Components</h2> <h3>Buttons</h3> <div style="margin: 1rem 0;"> <button class="btn btn-primary">Primary Button</button> <button class="btn btn-secondary">Secondary Button</button> <button class="btn btn-outline">Outline Button</button> </div> <h3>Theme Toggle</h3> <div style="margin: 1rem 0;"> <button class="btn btn-outline" onclick="window.ThemeConfig.toggleTheme()"> šŸŒ“ Toggle Theme </button> </div> <h3>Debug Tools</h3> <div style="margin: 1rem 0;"> <button class="btn btn-outline" onclick="MusPEDebug.toggle()"> šŸ› Toggle Debug </button> </div> <h3>Sample Card</h3> <div class="card"> <h4>This is a sample card</h4> <p>Cards can contain any content and are styled consistently.</p> </div> </div> </div> </main> </div> \`; } afterRender() { console.log('Components page rendered'); } beforeDestroy() { console.log('Components page being destroyed'); } } // Register page window.ComponentsPage = ComponentsPage;`; await fs.writeFile(path.join(projectPath, 'src/pages/ComponentsPage.js'), componentsPage); // Pages index file const pagesIndex = `// Pages Index - Centralized page imports // Import all pages console.log('šŸ“„ Loading pages...'); // Core pages are loaded automatically when scripts execute // HomePage, DocumentationPage, ComponentsPage are available globally console.log('šŸ“„ Pages loaded:', { HomePage: 'Available', DocumentationPage: 'Available', ComponentsPage: 'Available' });`; await fs.writeFile(path.join(projectPath, 'src/pages/index.js'), pagesIndex); } // Generate sample components async function generateSampleComponents(projectPath, framework) { // App Header Component const appHeader = `// App Header Component - Main navigation header class AppHeader { constructor(options = {}) { this.name = 'AppHeader'; this.title = options.title || window.AppConfig?.app?.name || 'MusPE App'; this.showNav = options.showNav !== false; this.element = null; } render() { return \` <header class="app-header"> <div class="header-content"> <div class="header-brand"> <h1>\${this.title}</h1> </div> \${this.showNav ? this.renderNavigation() : ''} <div class="header-actions"> <button class="theme-toggle" onclick="window.ThemeConfig.toggleTheme()" title="Toggle Theme"> šŸŒ“ </button> \${window.AppConfig?.debugging?.enabled ? \` <button class="debug-toggle" onclick="MusPEDebug.toggle()" title="Toggle Debug"> šŸ› </button> \` : ''} </div> </div> </header> \`; } renderNavigation() { const routes = window.AppConfig?.routes || []; const currentPath = window.location.hash.replace('#', '') || ''; return \` <nav class="app-nav"> <ul class="nav-list"> \${routes.map(route => \` <li class="nav-item"> <a href="#\${route.path}" class="\${currentPath === route.path ? 'active' : ''}" onclick="MusPERouter.navigate('/\${route.path}')"> \${route.title?.split(' - ')[0] || route.name} </a> </li> \`).join('')} </ul> </nav> \`; } afterRender() { this.element = document.querySelector('.app-header'); this.setupEventListeners(); } setupEventListeners() { // Listen for route changes to update active nav item window.addEventListener('hashchange', () => { this.updateActiveNavItem(); }); } updateActiveNavItem() { const currentPath = window.location.hash.replace('#', '') || ''; const navLinks = this.element?.querySelectorAll('.nav-list a'); navLinks?.forEach(link => { link.classList.remove('active'); const href = link.getAttribute('href')?.replace('#', '') || ''; if (href === currentPath) { link.classList.add('active'); } }); } updateTitle(newTitle) { this.title = newTitle; const titleElement = this.element?.querySelector('h1'); if (titleElement) { titleElement.textContent = newTitle; } } beforeDestroy() { // Cleanup if needed } } // Register component window.AppHeader = AppHeader;`; await fs.writeFile(path.join(projectPath, 'src/components/AppHeader.js'), appHeader); // Components index const componentsIndex = `// Components Index - Centralized component imports // Import all components console.log('🧩 Loading components...'); // Components are loaded automatically when scripts execute // AppHeader and other components are available globally console.log('🧩 Components loaded:', { AppHeader: 'Available' });`; await fs.writeFile(path.join(projectPath, 'src/components/index.js'), componentsIndex); } // Generate models async function generateModels(projectPath) { const userModel = `// User Model - Example data model class User { constructor(data = {}) { this.id = data.id || null; this.name = data.name || ''; this.email = data.email || ''; this.createdAt = data.createdAt || new Date(); } // Validation validate() { const errors = []; if (!this.name || this.name.trim().length < 2) { errors.push('Name must be at least 2 characters long'); } if (!this.email || !this.isValidEmail(this.email)) { errors.push('Valid email is required'); } return { isValid: errors.length === 0, errors }; } isValidEmail(email) { const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/; return emailRegex.test(email); } // Convert to JSON toJSON() { return { id: this.id, name: this.name, email: this.email, createdAt: this.createdAt }; } } window.User = User;`; await fs.writeFile(path.join(projectPath, 'src/models/User.js'), userModel); const modelsIndex = `// Models Index - Centralized model imports console.log('šŸ“Š Loading models...'); // Models are loaded automatically when scripts execute // User and other models are available globally console.log('šŸ“Š Models loaded:', { User: 'Available' });`; await fs.writeFile(path.join(projectPath, 'src/models/index.js'), modelsIndex); } // Generate routes configuration async function generateRoutesConfig(projectPath) { const routesConfig = `// Routes Configuration - Centralized routing class MusPERouter { constructor() { this.routes = window.AppConfig?.routes || []; this.currentRoute = null; this.init(); } init() { window.addEventListener('hashchange', () => this.handleRoute()); window.addEventListener('load', () => this.handleRoute()); // Make router globally available window.MusPERouter = this; } // Navigate to a route static navigate(path) { window.location.hash = path; } // Go back static back() { window.history.back(); } // Handle route changes async handleRoute() { const hash = window.location.hash.replace('#', '') || ''; const route = this.findRoute(hash); if (!route) { console.warn(\`Route not found: \${hash}\`); if (hash !== '') { MusPERouter.navigate('/'); } return; } this.currentRoute = route; // Update page title if (route.title) { document.title = route.title; } // Render the page await this.renderPage(route); // Debug logging if (window.AppConfig?.debugging?.enabled) { MusPEDebug.log(\`Navigated to: \${route.name} (\${route.path})\`); } } // Find route by path findRoute(path) { return this.routes.find(route => route.path === path); } // Render page component async renderPage(route) { const appContainer = docum