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,573 lines (1,360 loc) • 98.8 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('\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: {}, }, }`; } // 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); } // 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 ${framework === 'tailwind' ? 'Tailwind CSS' : framework === 'bootstrap' ? 'Bootstrap' : 'custom styled'} application is ready to go.</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'); // Add some interactive elements 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="documentation-nav"> <ul class="doc-sections"> <li><a href="#getting-started" onclick="this.scrollToSection(event, 'getting-started')">Getting Started</a></li> <li><a href="#project-structure" onclick="this.scrollToSection(event, 'project-structure')">Project Structure</a></li> <li><a href="#creating-components" onclick="this.scrollToSection(event, 'creating-components')">Creating Components</a></li> <li><a href="#routing" onclick="this.scrollToSection(event, 'routing')">Routing</a></li> <li><a href="#debugging" onclick="this.scrollToSection(event, 'debugging')">Debugging</a></li> </ul> </div> <div class="documentation-content"> <section id="getting-started" class="doc-section"> <h2>šŸš€ Getting Started</h2> <div class="card"> <h3>Quick Start</h3> <pre><code>// Start development server npm start // Build for production npm run build // Enable debug mode npm run debug</code></pre> </div> </section> <section id="project-structure" class="doc-section"> <h2>šŸ“ Project Structure</h2> <div class="card"> <pre><code>src/ ā”œā”€ā”€ components/ # Reusable UI components ā”œā”€ā”€ pages/ # Application pages/views ā”œā”€ā”€ models/ # Data models ā”œā”€ā”€ services/ # API and business logic ā”œā”€ā”€ config/ # Application configuration ā”œā”€ā”€ routes/ # Route definitions ā”œā”€ā”€ styles/ # CSS and themes ā”œā”€ā”€ utils/ # Utility functions └── app.js # Main application file</code></pre> </div> </section> <section id="creating-components" class="doc-section"> <h2>🧩 Creating Components</h2> <div class="card"> <h3>Component Structure</h3> <pre><code>class MyComponent { constructor(options = {}) { this.name = 'MyComponent'; this.options = options; } render() { return \\\` &lt;div class="my-component"&gt; &lt;h3&gt;\\\${this.options.title}&lt;/h3&gt; &lt;p&gt;\\\${this.options.content}&lt;/p&gt; &lt;/div&gt; \\\`; } afterRender() { // Setup event listeners this.setupEvents(); } setupEvents() { // Component-specific interactions } beforeDestroy() { // Cleanup } }</code></pre> </div> </section> <section id="routing" class="doc-section"> <h2>šŸ›£ļø Routing</h2> <div class="card"> <h3>Navigate Between Pages</h3> <pre><code>// Navigate to a page MusPERouter.navigate('/page-name'); // Navigate with parameters MusPERouter.navigate('/user/123'); // Go back MusPERouter.back();</code></pre> </div> </section> <section id="debugging" class="doc-section"> <h2>šŸ› Debugging</h2> <div class="card"> <h3>Debug Tools</h3> <pre><code>// Enable debug mode MusPEDebug.enable(); // Log debug info MusPEDebug.log('Debug message'); // Show performance info MusPEDebug.showPerformance(); // Clear errors MusPEDebug.clearError();</code></pre> <p><strong>URL Parameters:</strong></p> <ul> <li><code>?debug=true</code> - Enable debug mode</li> <li><code>?theme=dark</code> - Switch to dark theme</li> </ul> </div> </section> </div> </div> </main> </div> \`; } afterRender() { console.log('Documentation page rendered'); this.setupScrollSpy(); } setupScrollSpy() { // Highlight current section in navigation const sections = document.querySelectorAll('.doc-section'); const navLinks = document.querySelectorAll('.doc-sections a'); const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const id = entry.target.id; navLinks.forEach(link => { link.classList.remove('active'); if (link.getAttribute('href') === \`#\${id}\`) { link.classList.add('active'); } }); } }); }, { threshold: 0.5 }); sections.forEach(section => observer.observe(section)); } scrollToSection(event, sectionId) { event.preventDefault(); const section = document.getElementById(sectionId); if (section) { section.scrollIntoView({ behavior: 'smooth' }); } } 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="components-grid"> <!-- Button Components --> <div class="component-demo"> <h3>Buttons</h3> <div class="demo-content"> <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> <details class="code-example"> <summary>View Code</summary> <pre><code>&lt;button class="btn btn-primary"&gt;Primary Button&lt;/button&gt;</code></pre> </details> </div> <!-- Card Component --> <div class="component-demo"> <h3>Cards</h3> <div class="demo-content"> <div class="card"> <h4>Sample Card</h4> <p>This is a sample card component with some content.</p> <button class="btn btn-primary">Action</button> </div> </div> <details class="code-example"> <summary>View Code</summary> <pre><code>&lt;div class="card"&gt; &lt;h4&gt;Sample Card&lt;/h4&gt; &lt;p&gt;Content here&lt;/p&gt; &lt;/div&gt;</code></pre> </details> </div> <!-- Loading Component --> <div class="component-demo"> <h3>Loading Indicator</h3> <div class="demo-content"> <div class="loader"></div> </div> <button class="btn btn-outline" onclick="this.demoLoading()">Demo Loading</button> </div> <!-- Theme Toggle --> <div class="component-demo"> <h3>Theme Toggle</h3> <div class="demo-content"> <button class="btn btn-outline" onclick="window.ThemeConfig.toggleTheme()"> šŸŒ“ Toggle Theme </button> </div> </div> <!-- Debug Panel --> <div class="component-demo"> <h3>Debug Tools</h3> <div class="demo-content"> <button class="btn btn-outline" onclick="MusPEDebug.toggle()"> šŸ› Toggle Debug </button> <button class="btn btn-outline" onclick="MusPEDebug.showPerformance()"> šŸ“Š Show Performance </button> </div> </div> <!-- Modal Demo --> <div class="component-demo"> <h3>Modal</h3> <div class="demo-content"> <button class="btn btn-primary" onclick="this.showModal()"> Open Modal </button> </div> </div> </div> </div> </main> </div> \`; } afterRender() { console.log('Components page rendered'); } demoLoading() { const loadingOverlay = document.createElement('div'); loadingOverlay.className = 'loading-overlay'; loadingOverlay.innerHTML = \` <div class="loading-screen"> <div class="loader"></div> <p>Loading...</p> </div> \`; document.body.appendChild(loadingOverlay); setTimeout(() => { document.body.removeChild(loadingOverlay); }, 2000); } showModal() { const modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.style.cssText = \` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; \`; modal.innerHTML = \` <div class="modal-content" style=" background: var(--surface); border-radius: var(--radius-lg); padding: var(--spacing-xl); max-width: 400px; margin: var(--spacing-md); "> <h3>šŸŽ‰ Modal Example</h3> <p>This is a sample modal component built with MusPE.</p> <button class="btn btn-primary" onclick="document.body.removeChild(this.closest('.modal-overlay'))"> Close Modal </button> </div> \`; modal.addEventListener('click', (e) => { if (e.target === modal) { document.body.removeChild(modal); } }); document.body.appendChild(modal); } 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 // This file is automatically loaded and makes all pages available globally // Core pages import('./HomePage.js'); import('./DocumentationPage.js'); import('./ComponentsPage.js'); // You can add more pages here as you create them // import('./AboutPage.js'); // import('./ContactPage.js'); 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); // Navigation Component const navigation = `// Navigation Component - Responsive navigation menu class Navigation { constructor(options = {}) { this.name = 'Navigation'; this.items = options.items || []; this.isMobile = window.innerWidth < 768; this.isOpen = false; } render() { return \` <nav class="navigation"> <button class="nav-toggle" onclick="this.toggle()"> <span class="hamburger"></span> </button> <ul class="nav-menu \${this.isOpen ? 'nav-menu--open' : ''}"> \${this.items.map(item => \` <li class="nav-menu__item"> <a href="\${item.href}" class="nav-menu__link"> \${item.icon ? \`<span class="nav-icon">\${item.icon}</span>\` : ''} \${item.text} </a> </li> \`).join('')} </ul> </nav> \`; } toggle() { this.isOpen = !this.isOpen; const menu = document.querySelector('.nav-menu'); if (menu) { menu.classList.toggle('nav-menu--open', this.isOpen); } } close() { this.isOpen = false; const menu = document.querySelector('.nav-menu'); if (menu) { menu.classList.remove('nav-menu--open'); } } afterRender() { // Close menu when clicking outside document.addEventListener('click', (e) => { if (!e.target.closest('.navigation')) { this.close(); } }); // Close menu on route change window.addEventListener('hashchange', () => { this.close(); }); } } window.Navigation = Navigation;`; await fs.writeFile(path.join(projectPath, 'src/components/Navigation.js'), navigation); // Card Component const card = `// Card Component - Reusable card layout class Card { constructor(options = {}) { this.name = 'Card'; this.title = options.title || ''; this.content = options.content || ''; this.actions = options.actions || []; this.className = options.className || ''; } render() { return \` <div class="card \${this.className}"> \${this.title ? \`<div class="card-header"><h3>\${this.title}</h3></div>\` : ''} <div class="card-body"> \${this.content} </div> \${this.actions.length > 0 ? \` <div class="card-actions"> \${this.actions.map(action => \` <button class="btn \${action.className || 'btn-primary'}" onclick="\${action.onclick || ''}"> \${action.text} </button> \`).join('')} </div> \` : ''} </div> \`; } afterRender() { // Add any interactive behavior } } window.Card = Card;`; await fs.writeFile(path.join(projectPath, 'src/components/Card.js'), card); // Components index const componentsIndex = `// Components Index - Centralized component imports // Import all components // This file is automatically loaded and makes all components available globally // Core components import('./AppHeader.js'); import('./Navigation.js'); import('./Card.js'); // You can add more components here as you create them // import('./Button.js'); // import('./Modal.js'); // import('./Form.js'); console.log('🧩 Components loaded:', { AppHeader: 'Available', Navigation: 'Available', Card: 'Available' });`; await fs.writeFile(path.join(projectPath, 'src/components/index.js'), componentsIndex); } 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); } 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 Cordova utilities const cordovaUtilsSource = path.join(__dirname, '../utils/cordova.js'); const cordovaUtilsTarget = path.join(utilsDir, 'cordova.js'); if (await fs.pathExists(cordovaUtilsSource)) { await fs.copy(cordovaUtilsSource, cordovaUtilsTarget); } // 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); } } 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; borde