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

536 lines (456 loc) 15.1 kB
const fs = require('fs-extra'); const path = require('path'); const chalk = require('chalk'); const inquirer = require('inquirer'); const ora = require('ora'); async function validateProject(options = {}) { const spinner = ora('Validating MusPE project...').start(); try { const projectRoot = findProjectRoot(); if (!projectRoot) { spinner.fail('Not in a MusPE project directory'); return false; } const issues = []; const warnings = []; // Check configuration const configIssues = await validateConfiguration(projectRoot); issues.push(...configIssues.errors); warnings.push(...configIssues.warnings); // Check dependencies const depIssues = await validateDependencies(projectRoot); issues.push(...depIssues.errors); warnings.push(...depIssues.warnings); // Check file structure const structureIssues = await validateFileStructure(projectRoot); issues.push(...structureIssues.errors); warnings.push(...structureIssues.warnings); // Check code quality const codeIssues = await validateCodeQuality(projectRoot); issues.push(...codeIssues.errors); warnings.push(...codeIssues.warnings); // Display results spinner.succeed('Project validation completed'); if (issues.length === 0 && warnings.length === 0) { console.log(chalk.green('✅ Project validation passed!')); console.log(chalk.gray('No issues found.')); } else { if (issues.length > 0) { console.log(chalk.red(`\n❌ Found ${issues.length} error(s):`)); issues.forEach(issue => { console.log(chalk.red(` • ${issue.message}`)); if (issue.fix) { console.log(chalk.gray(` Fix: ${issue.fix}`)); } }); } if (warnings.length > 0) { console.log(chalk.yellow(`\n⚠️ Found ${warnings.length} warning(s):`)); warnings.forEach(warning => { console.log(chalk.yellow(` • ${warning.message}`)); if (warning.suggestion) { console.log(chalk.gray(` Suggestion: ${warning.suggestion}`)); } }); } // Offer to fix issues if (options.fix && issues.length > 0) { const { shouldFix } = await inquirer.prompt([ { type: 'confirm', name: 'shouldFix', message: 'Would you like to automatically fix the issues that can be auto-fixed?', default: true } ]); if (shouldFix) { await autoFixIssues(projectRoot, issues.filter(issue => issue.autoFixable)); } } } return issues.length === 0; } catch (error) { spinner.fail('Validation failed'); console.error(chalk.red(error.message)); return false; } } async function validateConfiguration(projectRoot) { const errors = []; const warnings = []; // Check muspe.config.js const configPath = path.join(projectRoot, 'muspe.config.js'); if (!await fs.pathExists(configPath)) { errors.push({ message: 'Missing muspe.config.js file', fix: 'Run "muspe init" to create configuration', autoFixable: true }); } else { try { const config = require(configPath); // Validate required fields if (!config.template) { warnings.push({ message: 'Template not specified in config', suggestion: 'Add template field to muspe.config.js' }); } if (!config.framework) { warnings.push({ message: 'Framework not specified in config', suggestion: 'Add framework field to muspe.config.js' }); } // Validate server config if (config.server && config.server.port) { if (typeof config.server.port !== 'number' || config.server.port < 1000 || config.server.port > 65535) { errors.push({ message: 'Invalid server port in configuration', fix: 'Set port to a valid number between 1000-65535' }); } } } catch (error) { errors.push({ message: 'Invalid muspe.config.js syntax', fix: 'Check JavaScript syntax in configuration file' }); } } // Check package.json const packagePath = path.join(projectRoot, 'package.json'); if (!await fs.pathExists(packagePath)) { errors.push({ message: 'Missing package.json file', fix: 'Run "npm init" to create package.json', autoFixable: true }); } else { try { const pkg = await fs.readJSON(packagePath); if (!pkg.name) { warnings.push({ message: 'Package name not specified', suggestion: 'Add name field to package.json' }); } if (!pkg.muspe) { warnings.push({ message: 'MusPE metadata missing from package.json', suggestion: 'Add muspe field with template and framework info' }); } } catch (error) { errors.push({ message: 'Invalid package.json syntax', fix: 'Check JSON syntax in package.json' }); } } return { errors, warnings }; } async function validateDependencies(projectRoot) { const errors = []; const warnings = []; const packagePath = path.join(projectRoot, 'package.json'); if (await fs.pathExists(packagePath)) { try { const pkg = await fs.readJSON(packagePath); const deps = { ...pkg.dependencies, ...pkg.devDependencies }; // Check for required MusPE dependencies const requiredDeps = ['@material/web', 'swiper']; const recommendedDeps = ['tailwindcss', 'vite']; requiredDeps.forEach(dep => { if (!deps[dep]) { errors.push({ message: `Missing required dependency: ${dep}`, fix: `Run "npm install ${dep}"`, autoFixable: true }); } }); recommendedDeps.forEach(dep => { if (!deps[dep]) { warnings.push({ message: `Missing recommended dependency: ${dep}`, suggestion: `Consider installing ${dep} for better development experience` }); } }); // Check for outdated dependencies const criticalDeps = ['@material/web', 'swiper', 'vite']; const outdatedVersions = { '@material/web': '1.0.0', 'swiper': '11.0.0', 'vite': '5.0.0' }; criticalDeps.forEach(dep => { if (deps[dep] && outdatedVersions[dep]) { const currentVersion = deps[dep].replace(/[^0-9.]/g, ''); const requiredVersion = outdatedVersions[dep]; if (isVersionOlder(currentVersion, requiredVersion)) { warnings.push({ message: `Outdated dependency: ${dep} (${currentVersion} < ${requiredVersion})`, suggestion: `Update to latest version: npm install ${dep}@latest` }); } } }); } catch (error) { errors.push({ message: 'Unable to read package.json dependencies', fix: 'Check package.json file syntax' }); } } return { errors, warnings }; } async function validateFileStructure(projectRoot) { const errors = []; const warnings = []; // Required directories const requiredDirs = [ 'src', 'src/components', 'src/pages', 'src/styles', 'src/scripts', 'public' ]; // Optional but recommended directories const recommendedDirs = [ 'src/layouts', 'src/templates', 'src/assets', 'src/utils', 'src/services' ]; // Check required directories for (const dir of requiredDirs) { const dirPath = path.join(projectRoot, dir); if (!await fs.pathExists(dirPath)) { errors.push({ message: `Missing required directory: ${dir}`, fix: `Create directory: mkdir -p ${dir}`, autoFixable: true }); } } // Check recommended directories for (const dir of recommendedDirs) { const dirPath = path.join(projectRoot, dir); if (!await fs.pathExists(dirPath)) { warnings.push({ message: `Missing recommended directory: ${dir}`, suggestion: `Create directory for better organization: mkdir ${dir}` }); } } // Check for essential files const requiredFiles = [ 'public/index.html', 'src/scripts/main.js', 'src/styles/main.css' ]; for (const file of requiredFiles) { const filePath = path.join(projectRoot, file); if (!await fs.pathExists(filePath)) { errors.push({ message: `Missing required file: ${file}`, fix: `Create file: touch ${file}`, autoFixable: true }); } } return { errors, warnings }; } async function validateCodeQuality(projectRoot) { const errors = []; const warnings = []; // Check for common code issues in main files const mainJsPath = path.join(projectRoot, 'src/scripts/main.js'); if (await fs.pathExists(mainJsPath)) { try { const content = await fs.readFile(mainJsPath, 'utf-8'); // Check for console.log statements if (content.includes('console.log') && !content.includes('// DEBUG')) { warnings.push({ message: 'Console.log statements found in main.js', suggestion: 'Remove or comment console.log statements for production' }); } // Check for proper ES6 imports if (content.includes('require(') && content.includes('import ')) { warnings.push({ message: 'Mixed import styles (require/import) found', suggestion: 'Use consistent import style (prefer ES6 imports)' }); } // Check for Material.io imports if (!content.includes('@material/web')) { warnings.push({ message: 'Material.io components not imported', suggestion: 'Import Material.io components for enhanced UI' }); } } catch (error) { warnings.push({ message: 'Unable to analyze main.js code quality', suggestion: 'Check file syntax and permissions' }); } } // Check HTML validation const indexPath = path.join(projectRoot, 'public/index.html'); if (await fs.pathExists(indexPath)) { try { const content = await fs.readFile(indexPath, 'utf-8'); // Check for proper viewport meta tag if (!content.includes('name="viewport"')) { errors.push({ message: 'Missing viewport meta tag for mobile responsiveness', fix: 'Add <meta name="viewport" content="width=device-width, initial-scale=1.0">', autoFixable: true }); } // Check for proper charset if (!content.includes('charset="UTF-8"')) { warnings.push({ message: 'Missing or incorrect charset declaration', suggestion: 'Add <meta charset="UTF-8"> for proper character encoding' }); } // Check for accessibility if (!content.includes('lang=')) { warnings.push({ message: 'Missing language attribute on html element', suggestion: 'Add lang="en" to <html> for accessibility' }); } } catch (error) { warnings.push({ message: 'Unable to analyze index.html', suggestion: 'Check file syntax and permissions' }); } } return { errors, warnings }; } async function autoFixIssues(projectRoot, fixableIssues) { const spinner = ora('Auto-fixing issues...').start(); try { for (const issue of fixableIssues) { await autoFixIssue(projectRoot, issue); } spinner.succeed(`Auto-fixed ${fixableIssues.length} issue(s)`); } catch (error) { spinner.fail('Auto-fix failed'); console.error(chalk.red(error.message)); } } async function autoFixIssue(projectRoot, issue) { switch (issue.message) { case 'Missing muspe.config.js file': await createDefaultConfig(projectRoot); break; case 'Missing package.json file': await createDefaultPackageJson(projectRoot); break; case 'Missing viewport meta tag for mobile responsiveness': await fixViewportMeta(projectRoot); break; default: if (issue.message.includes('Missing required directory:')) { const dir = issue.message.split(': ')[1]; await fs.ensureDir(path.join(projectRoot, dir)); } else if (issue.message.includes('Missing required file:')) { const file = issue.message.split(': ')[1]; await fs.ensureFile(path.join(projectRoot, file)); } break; } } async function createDefaultConfig(projectRoot) { const config = `module.exports = { template: 'mobile', framework: 'tailwind', server: { port: 3000, host: 'localhost', open: true }, build: { outDir: 'dist', minify: true, sourcemap: true }, pwa: { enabled: false, manifest: './public/manifest.json', serviceWorker: './src/sw.js' } };`; await fs.writeFile(path.join(projectRoot, 'muspe.config.js'), config); } async function createDefaultPackageJson(projectRoot) { const pkg = { name: path.basename(projectRoot), version: '1.0.0', description: 'A MusPE application', main: 'src/scripts/main.js', scripts: { start: 'muspe serve', build: 'muspe build', dev: 'muspe serve --open' }, dependencies: {}, devDependencies: {}, muspe: { template: 'mobile', framework: 'tailwind', version: '1.0.0' } }; await fs.writeJSON(path.join(projectRoot, 'package.json'), pkg, { spaces: 2 }); } async function fixViewportMeta(projectRoot) { const indexPath = path.join(projectRoot, 'public/index.html'); if (await fs.pathExists(indexPath)) { let content = await fs.readFile(indexPath, 'utf-8'); if (!content.includes('name="viewport"')) { // Add viewport meta tag after charset content = content.replace( /<meta charset="UTF-8">/, '<meta charset="UTF-8">\n <meta name="viewport" content="width=device-width, initial-scale=1.0">' ); await fs.writeFile(indexPath, content); } } } function findProjectRoot() { let currentDir = process.cwd(); while (currentDir !== path.dirname(currentDir)) { const configPath = path.join(currentDir, 'muspe.config.js'); const packagePath = path.join(currentDir, 'package.json'); if (fs.existsSync(configPath) || (fs.existsSync(packagePath) && JSON.parse(fs.readFileSync(packagePath, 'utf-8')).muspe)) { return currentDir; } currentDir = path.dirname(currentDir); } return null; } function isVersionOlder(current, required) { const currentParts = current.split('.').map(Number); const requiredParts = required.split('.').map(Number); for (let i = 0; i < Math.max(currentParts.length, requiredParts.length); i++) { const currentPart = currentParts[i] || 0; const requiredPart = requiredParts[i] || 0; if (currentPart < requiredPart) return true; if (currentPart > requiredPart) return false; } return false; } module.exports = { validateProject };