UNPKG

woaru

Version:

Universal Project Setup Autopilot - Analyze and automatically configure development tools for ANY programming language

360 lines • 13.8 kB
/** * Interactive Project Type Selector * Handles user interaction for project type and feature selection */ import inquirer from 'inquirer'; import chalk from 'chalk'; export class ProjectSelector { registry; constructor(registry) { this.registry = registry; } /** * Interactive project type selection */ async selectProjectType() { console.log(chalk.cyan.bold('\nšŸš€ WOARU Project Initializer')); console.log(chalk.gray('═'.repeat(50))); console.log(chalk.white("Let's set up your new project with best practices!\n")); const templates = this.registry.list(); if (templates.length === 0) { throw new Error('No project templates available'); } // Group templates by category for better organization const categories = this.groupTemplatesByCategory(templates); // If only one category, skip category selection if (categories.length === 1) { return this.selectFromTemplates(categories[0].templates); } // Select category first const categoryChoices = categories.map(cat => ({ name: `${this.getCategoryIcon(cat.name)} ${cat.name} (${cat.templates.length} templates)`, value: cat.name, short: cat.name, })); const { selectedCategory } = await inquirer.prompt([ { type: 'list', name: 'selectedCategory', message: 'What type of project are you building?', choices: categoryChoices, pageSize: 10, }, ]); const categoryTemplates = categories.find(cat => cat.name === selectedCategory)?.templates || []; return this.selectFromTemplates(categoryTemplates); } /** * Select specific template from a list */ async selectFromTemplates(templates) { const templateChoices = templates.map(template => ({ name: `${template.name}\n ${chalk.gray(template.description)}\n ${chalk.yellow(`Language: ${template.language}`)} ${chalk.blue(`Frameworks: ${template.frameworks.join(', ')}`)}`, value: template.id, short: template.name, })); const { selectedTemplate } = await inquirer.prompt([ { type: 'list', name: 'selectedTemplate', message: 'Choose your project template:', choices: templateChoices, pageSize: 8, }, ]); const template = this.registry.get(selectedTemplate); if (!template) { throw new Error(`Template "${selectedTemplate}" not found`); } return template; } /** * Interactive feature selection */ async selectFeatures(template) { if (!template.features || template.features.length === 0) { console.log(chalk.yellow('\nšŸ“¦ No optional features available for this template.')); return []; } console.log(chalk.cyan.bold('\nāš™ļø Optional Features')); console.log(chalk.gray('Select additional features for your project:\n')); // Group features by category const featuresByCategory = this.groupFeaturesByCategory(template.features); const selectedFeatures = []; // Add default features template.features .filter(feature => feature.default) .forEach(feature => selectedFeatures.push(feature.id)); // Interactive selection for each category for (const [category, features] of featuresByCategory.entries()) { if (features.length === 0) continue; console.log(chalk.blue.bold(`\n${category.charAt(0).toUpperCase() + category.slice(1)} Features:`)); const choices = features.map(feature => ({ name: `${feature.name} - ${chalk.gray(feature.description)}`, value: feature.id, checked: feature.default || false, })); const { categoryFeatures } = await inquirer.prompt([ { type: 'checkbox', name: 'categoryFeatures', message: `Select ${category} features:`, choices, pageSize: 15, }, ]); // Add selected features (avoiding duplicates) categoryFeatures.forEach((featureId) => { if (!selectedFeatures.includes(featureId)) { selectedFeatures.push(featureId); } }); } // Validate feature dependencies and conflicts const validatedFeatures = this.validateFeatureDependencies(template, selectedFeatures); if (validatedFeatures.length !== selectedFeatures.length) { console.log(chalk.yellow('\nāš ļø Some features were adjusted due to dependencies/conflicts.')); } return validatedFeatures; } /** * Configure project details */ async configureProject(template, features) { console.log(chalk.cyan.bold('\nšŸ“‹ Project Configuration')); console.log(chalk.gray('Configure your project details:\n')); const questions = [ { type: 'input', name: 'name', message: 'Project name:', default: 'my-project', validate: (input) => { if (!input.trim()) return 'Project name is required'; if (!/^[a-zA-Z0-9-_]+$/.test(input)) return 'Project name can only contain letters, numbers, hyphens, and underscores'; return true; }, }, { type: 'input', name: 'description', message: 'Project description (optional):', default: '', }, { type: 'input', name: 'author', message: 'Author name (optional):', default: '', }, ]; // Add package manager selection if template supports multiple if (this.supportsMultiplePackageManagers(template)) { const packageManagers = this.getAvailablePackageManagers(template); questions.push({ type: 'list', name: 'packageManager', message: 'Package manager:', choices: packageManagers.map(pm => ({ name: `${pm} ${this.getPackageManagerDescription(pm)}`, value: pm, })), default: template.packageManager, }); } // Additional configuration questions questions.push({ type: 'input', name: 'directory', message: 'Target directory:', default: (answers) => `./${answers.name}`, validate: (input) => { if (!input.trim()) return 'Directory is required'; return true; }, }, { type: 'confirm', name: 'gitInit', message: 'Initialize Git repository?', default: true, }, { type: 'confirm', name: 'installDeps', message: 'Install dependencies after generation?', default: true, }); const answers = await inquirer.prompt(questions); // Build template variables const variables = { projectName: answers.name, projectDescription: answers.description, author: answers.author, features: features.reduce((acc, featureId) => { acc[featureId] = true; return acc; }, {}), packageManager: answers.packageManager || template.packageManager, year: new Date().getFullYear(), date: new Date().toISOString().split('T')[0], }; return { name: answers.name, description: answers.description, author: answers.author, template, features, directory: answers.directory, packageManager: answers.packageManager || template.packageManager, gitInit: answers.gitInit, installDeps: answers.installDeps, variables, }; } /** * Confirm generation with summary */ async confirmGeneration(config) { console.log(chalk.cyan.bold('\nšŸ“Š Project Summary')); console.log(chalk.gray('═'.repeat(50))); console.log(chalk.white(`Project Name: ${chalk.green(config.name)}`)); console.log(chalk.white(`Template: ${chalk.green(config.template.name)}`)); console.log(chalk.white(`Language: ${chalk.green(config.template.language)}`)); console.log(chalk.white(`Package Manager: ${chalk.green(config.packageManager)}`)); console.log(chalk.white(`Directory: ${chalk.green(config.directory)}`)); if (config.features.length > 0) { console.log(chalk.white(`Features: ${chalk.green(config.features.join(', '))}`)); } console.log(chalk.white(`Git Init: ${config.gitInit ? chalk.green('Yes') : chalk.red('No')}`)); console.log(chalk.white(`Install Dependencies: ${config.installDeps ? chalk.green('Yes') : chalk.red('No')}`)); const { confirm } = await inquirer.prompt([ { type: 'confirm', name: 'confirm', message: 'Generate project with these settings?', default: true, }, ]); return confirm; } /** * Group templates by category */ groupTemplatesByCategory(templates) { const categories = new Map(); templates.forEach(template => { const category = template.category; if (!categories.has(category)) { categories.set(category, []); } const categoryList = categories.get(category); if (categoryList) { categoryList.push(template); } }); return Array.from(categories.entries()).map(([name, templates]) => ({ name, templates, })); } /** * Group features by category */ groupFeaturesByCategory(features) { const categories = new Map(); features.forEach(feature => { const category = feature.category || 'general'; if (!categories.has(category)) { categories.set(category, []); } const categoryList = categories.get(category); if (categoryList) { categoryList.push(feature); } }); return categories; } /** * Validate feature dependencies and resolve conflicts */ validateFeatureDependencies(template, selectedFeatures) { const validFeatures = new Set(selectedFeatures); const featureMap = new Map(template.features.map(f => [f.id, f])); // Add required dependencies const toAdd = new Set(); for (const featureId of validFeatures) { const feature = featureMap.get(featureId); if (feature?.dependencies) { feature.dependencies.forEach(dep => { if (!validFeatures.has(dep)) { toAdd.add(dep); } }); } } toAdd.forEach(featureId => validFeatures.add(featureId)); // Remove conflicting features for (const featureId of Array.from(validFeatures)) { const feature = featureMap.get(featureId); if (feature?.conflicts) { feature.conflicts.forEach(conflict => { if (validFeatures.has(conflict)) { // Remove the conflicting feature (keep the one that was selected first) validFeatures.delete(conflict); } }); } } return Array.from(validFeatures); } /** * Get category icon */ getCategoryIcon(category) { const icons = { frontend: '🌐', backend: 'āš™ļø', fullstack: 'šŸš€', mobile: 'šŸ“±', desktop: 'šŸ–„ļø', }; return icons[category] || 'šŸ“¦'; } /** * Check if template supports multiple package managers */ supportsMultiplePackageManagers(template) { // For now, only frontend projects support multiple package managers return (template.category === 'frontend' && template.language === 'TypeScript'); } /** * Get available package managers for template */ getAvailablePackageManagers(template) { if (template.language === 'Python') { return ['pip', 'poetry', 'pipenv']; } if (template.category === 'frontend') { return ['npm', 'yarn', 'pnpm']; } return [template.packageManager]; } /** * Get package manager description */ getPackageManagerDescription(pm) { const descriptions = { npm: '(Node Package Manager)', yarn: '(Fast, reliable, secure)', pnpm: '(Fast, disk space efficient)', pip: '(Python Package Installer)', poetry: '(Python dependency management)', pipenv: '(Python dev workflow)', }; return descriptions[pm] || ''; } } //# sourceMappingURL=ProjectSelector.js.map