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
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 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 \\\`
<div class="my-component">
<h3>\\\${this.options.title}</h3>
<p>\\\${this.options.content}</p>
</div>
\\\`;
}
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><button class="btn btn-primary">Primary Button</button></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><div class="card">
<h4>Sample Card</h4>
<p>Content here</p>
</div></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