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
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('$')} 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