UNPKG

accs-cli

Version:

ACCS CLI — Full-featured developer tool for scaffolding, running, building, and managing multi-language projects

269 lines (233 loc) 8.31 kB
/** * List templates command - Show available project templates */ import path from 'path'; import chalk from 'chalk'; import boxen from 'boxen'; import { logger } from '../utils/logger.js'; import { FileUtils } from '../utils/file-utils.js'; const BUILT_IN_TEMPLATES = { 'vanilla-js': { name: 'Vanilla JavaScript', description: 'Plain HTML, CSS, and JavaScript project', tags: ['web', 'frontend', 'beginner'], features: ['HTML5', 'CSS3', 'Vanilla JS', 'Responsive'] }, 'node-express': { name: 'Node.js Express', description: 'Express.js web server with modern setup', tags: ['backend', 'api', 'server'], features: ['Express.js', 'CORS', 'Error handling', 'REST API'] }, 'react': { name: 'React', description: 'React application with Vite', tags: ['frontend', 'spa', 'modern'], features: ['React 18', 'Vite', 'Hot reload', 'JSX'] }, 'vue': { name: 'Vue.js', description: 'Vue.js application with Vite', tags: ['frontend', 'spa', 'progressive'], features: ['Vue 3', 'Vite', 'Composition API', 'SFC'] }, 'typescript': { name: 'TypeScript', description: 'TypeScript project with modern tooling', tags: ['typescript', 'typed', 'modern'], features: ['TypeScript', 'ES modules', 'Type checking', 'Modern config'] }, 'python-flask': { name: 'Python Flask', description: 'Flask web application', tags: ['python', 'backend', 'web'], features: ['Flask', 'Jinja2', 'SQLAlchemy', 'Blueprint'] }, 'php-basic': { name: 'PHP Basic', description: 'Basic PHP web application', tags: ['php', 'backend', 'web'], features: ['PHP 8+', 'MVC pattern', 'Session handling', 'Database ready'] }, 'static-site': { name: 'Static Site', description: 'Simple static website template', tags: ['static', 'simple', 'portfolio'], features: ['Multi-page', 'SEO friendly', 'Fast loading', 'Mobile first'] } }; export function listTemplatesCommand(program) { program .command('list-templates') .alias('templates') .option('-f, --filter <tag>', 'Filter by tag') .option('-d, --detailed', 'Show detailed information') .option('-j, --json', 'Output as JSON') .description('List available project templates') .action(async (options) => { try { await listTemplates(options); } catch (error) { logger.error('Failed to list templates:', error.message); process.exit(1); } }); } async function listTemplates(options) { // Get built-in templates let templates = { ...BUILT_IN_TEMPLATES }; // Add custom templates from templates directory const customTemplates = await getCustomTemplates(); templates = { ...templates, ...customTemplates }; // Filter by tag if specified if (options.filter) { templates = Object.fromEntries( Object.entries(templates).filter(([, template]) => template.tags?.includes(options.filter.toLowerCase()) ) ); } // Output as JSON if requested if (options.json) { console.log(JSON.stringify(templates, null, 2)); return; } // Display templates displayTemplates(templates, options); } async function getCustomTemplates() { const templatesDir = FileUtils.getTemplatesDir(); const customTemplates = {}; if (!FileUtils.exists(templatesDir)) { return customTemplates; } try { const entries = await import('fs').then(fs => fs.promises.readdir(templatesDir)); for (const entry of entries) { const templatePath = path.join(templatesDir, entry); if (!FileUtils.isDirectory(templatePath)) continue; // Skip built-in templates if (BUILT_IN_TEMPLATES[entry]) continue; // Look for template metadata const metaPath = path.join(templatePath, 'template.json'); if (FileUtils.exists(metaPath)) { try { const meta = await FileUtils.readJson(metaPath); customTemplates[entry] = { name: meta.name || entry, description: meta.description || 'Custom template', tags: meta.tags || ['custom'], features: meta.features || [], custom: true }; } catch (error) { logger.warn(`Invalid template metadata for ${entry}`); } } else { // Default metadata for custom templates without template.json customTemplates[entry] = { name: entry.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()), description: 'Custom template', tags: ['custom'], features: [], custom: true }; } } } catch (error) { logger.warn('Could not read custom templates:', error.message); } return customTemplates; } function displayTemplates(templates, options) { const templateCount = Object.keys(templates).length; console.log(boxen( chalk.blue.bold(`📋 Available Templates (${templateCount})`), { padding: 1, margin: 1, borderStyle: 'round', borderColor: 'blue' } )); if (templateCount === 0) { logger.warn('No templates found matching your criteria'); return; } // Group templates by category const categories = groupTemplatesByCategory(templates); // Display each category Object.entries(categories).forEach(([category, categoryTemplates]) => { logger.section(category); categoryTemplates.forEach(([key, template]) => { displayTemplate(key, template, options); }); }); // Show usage instructions logger.separator(); logger.info('Usage:'); logger.info(` ${chalk.cyan('accs init my-project --template <template-name>')}`); logger.info(` ${chalk.cyan('accs init my-project')} ${chalk.gray('(interactive selection)')}`); if (options.filter) { logger.info(`\nFiltered by tag: ${chalk.yellow(options.filter)}`); logger.info(`Use ${chalk.cyan('accs list-templates')} to see all templates`); } // Show available tags const allTags = [...new Set( Object.values(templates).flatMap(t => t.tags || []) )].sort(); if (allTags.length > 0 && !options.filter) { logger.info(`\nAvailable tags: ${allTags.map(tag => chalk.yellow(tag)).join(', ')}`); logger.info(`Filter by tag: ${chalk.cyan('accs list-templates --filter <tag>')}`); } } function groupTemplatesByCategory(templates) { const categories = { 'Frontend': [], 'Backend': [], 'Full Stack': [], 'Static Sites': [], 'Language Specific': [], 'Custom': [] }; Object.entries(templates).forEach(([key, template]) => { if (template.custom) { categories['Custom'].push([key, template]); } else if (template.tags?.includes('frontend') || template.tags?.includes('spa')) { categories['Frontend'].push([key, template]); } else if (template.tags?.includes('backend') || template.tags?.includes('api') || template.tags?.includes('server')) { categories['Backend'].push([key, template]); } else if (template.tags?.includes('static') || template.tags?.includes('portfolio')) { categories['Static Sites'].push([key, template]); } else if (template.tags?.includes('python') || template.tags?.includes('php') || template.tags?.includes('typescript')) { categories['Language Specific'].push([key, template]); } else { categories['Frontend'].push([key, template]); } }); // Remove empty categories Object.keys(categories).forEach(category => { if (categories[category].length === 0) { delete categories[category]; } }); return categories; } function displayTemplate(key, template, options) { const nameColor = template.custom ? 'magenta' : 'cyan'; const customBadge = template.custom ? chalk.magenta(' [Custom]') : ''; console.log(` ${chalk[nameColor].bold(key)}${customBadge}`); console.log(` ${template.description}`); if (options.detailed) { // Show tags if (template.tags && template.tags.length > 0) { const tagString = template.tags.map(tag => chalk.yellow(`#${tag}`)).join(' '); console.log(` Tags: ${tagString}`); } // Show features if (template.features && template.features.length > 0) { console.log(` Features: ${template.features.join(', ')}`); } console.log(''); } }