diff --git a/tools/cli/installers/lib/core/config-collector.js b/tools/cli/installers/lib/core/config-collector.js index f4eaf5e3d..b49075ae0 100644 --- a/tools/cli/installers/lib/core/config-collector.js +++ b/tools/cli/installers/lib/core/config-collector.js @@ -136,10 +136,12 @@ class ConfigCollector { * @param {string} projectDir - Target project directory * @param {Object} options - Additional options * @param {Map} options.customModulePaths - Map of module ID to source path for custom modules + * @param {boolean} options.skipPrompts - Skip prompts and use defaults (for --yes flag) */ async collectAllConfigurations(modules, projectDir, options = {}) { // Store custom module paths for use in collectModuleConfig this.customModulePaths = options.customModulePaths || new Map(); + this.skipPrompts = options.skipPrompts || false; await this.loadExistingConfig(projectDir); // Check if core was already collected (e.g., in early collection phase) @@ -583,47 +585,60 @@ class ConfigCollector { // If there are questions to ask, prompt for accepting defaults vs customizing if (questions.length > 0) { const moduleDisplayName = moduleConfig.header || `${moduleName.toUpperCase()} Module`; - console.log(); - console.log(chalk.cyan('?') + ' ' + chalk.magenta(moduleDisplayName)); - let customize = true; - if (moduleName === 'core') { - // Core module: no confirm prompt, so add spacing manually to match visual style - console.log(chalk.gray('│')); + + // Skip prompts mode: use all defaults without asking + if (this.skipPrompts) { + console.log(chalk.cyan('Using default configuration for'), chalk.magenta(moduleDisplayName)); + // Use defaults for all questions + for (const question of questions) { + const hasDefault = question.default !== undefined && question.default !== null && question.default !== ''; + if (hasDefault && typeof question.default !== 'function') { + allAnswers[question.name] = question.default; + } + } } else { - // Non-core modules: show "Accept Defaults?" confirm prompt (clack adds spacing) - const customizeAnswer = await prompts.prompt([ - { - type: 'confirm', - name: 'customize', - message: 'Accept Defaults (no to customize)?', - default: true, - }, - ]); - customize = customizeAnswer.customize; - } + console.log(); + console.log(chalk.cyan('?') + ' ' + chalk.magenta(moduleDisplayName)); + let customize = true; + if (moduleName === 'core') { + // Core module: no confirm prompt, so add spacing manually to match visual style + console.log(chalk.gray('│')); + } else { + // Non-core modules: show "Accept Defaults?" confirm prompt (clack adds spacing) + const customizeAnswer = await prompts.prompt([ + { + type: 'confirm', + name: 'customize', + message: 'Accept Defaults (no to customize)?', + default: true, + }, + ]); + customize = customizeAnswer.customize; + } - if (customize && moduleName !== 'core') { - // Accept defaults - only ask questions that have NO default value - const questionsWithoutDefaults = questions.filter((q) => q.default === undefined || q.default === null || q.default === ''); + if (customize && moduleName !== 'core') { + // Accept defaults - only ask questions that have NO default value + const questionsWithoutDefaults = questions.filter((q) => q.default === undefined || q.default === null || q.default === ''); - if (questionsWithoutDefaults.length > 0) { - console.log(chalk.dim(`\n Asking required questions for ${moduleName.toUpperCase()}...`)); - const promptedAnswers = await prompts.prompt(questionsWithoutDefaults); + if (questionsWithoutDefaults.length > 0) { + console.log(chalk.dim(`\n Asking required questions for ${moduleName.toUpperCase()}...`)); + const promptedAnswers = await prompts.prompt(questionsWithoutDefaults); + Object.assign(allAnswers, promptedAnswers); + } + + // For questions with defaults that weren't asked, we need to process them with their default values + const questionsWithDefaults = questions.filter((q) => q.default !== undefined && q.default !== null && q.default !== ''); + for (const question of questionsWithDefaults) { + // Skip function defaults - these are dynamic and will be evaluated later + if (typeof question.default === 'function') { + continue; + } + allAnswers[question.name] = question.default; + } + } else { + const promptedAnswers = await prompts.prompt(questions); Object.assign(allAnswers, promptedAnswers); } - - // For questions with defaults that weren't asked, we need to process them with their default values - const questionsWithDefaults = questions.filter((q) => q.default !== undefined && q.default !== null && q.default !== ''); - for (const question of questionsWithDefaults) { - // Skip function defaults - these are dynamic and will be evaluated later - if (typeof question.default === 'function') { - continue; - } - allAnswers[question.name] = question.default; - } - } else { - const promptedAnswers = await prompts.prompt(questions); - Object.assign(allAnswers, promptedAnswers); } } diff --git a/tools/cli/installers/lib/core/installer.js b/tools/cli/installers/lib/core/installer.js index edb15112c..cfba0ab94 100644 --- a/tools/cli/installers/lib/core/installer.js +++ b/tools/cli/installers/lib/core/installer.js @@ -353,11 +353,13 @@ class Installer { const modulesWithoutCore = allModulesForConfig.filter((m) => m !== 'core'); moduleConfigs = await this.configCollector.collectAllConfigurations(modulesWithoutCore, path.resolve(config.directory), { customModulePaths, + skipPrompts: config.skipPrompts, }); } else { // Core not collected yet, include it moduleConfigs = await this.configCollector.collectAllConfigurations(allModulesForConfig, path.resolve(config.directory), { customModulePaths, + skipPrompts: config.skipPrompts, }); } } @@ -680,7 +682,8 @@ class Installer { } else { // Pass pre-selected IDEs from early prompt (if available) // This allows IDE selection to happen before file copying, improving UX - const preSelectedIdes = config.ides && config.ides.length > 0 ? config.ides : null; + // Use config.ides if it's an array (even if empty), null means prompt + const preSelectedIdes = Array.isArray(config.ides) ? config.ides : null; toolSelection = await this.collectToolConfigurations( path.resolve(config.directory), config.modules, diff --git a/tools/cli/lib/ui.js b/tools/cli/lib/ui.js index 7b1cae51b..2ed7ab9f1 100644 --- a/tools/cli/lib/ui.js +++ b/tools/cli/lib/ui.js @@ -43,7 +43,7 @@ class UI { // Use provided directory from command-line const expandedDir = this.expandUserPath(options.directory); const validation = this.validateDirectorySync(expandedDir); - if (validation !== true) { + if (validation) { throw new Error(`Invalid directory: ${validation}`); } confirmedDirectory = expandedDir; @@ -308,7 +308,7 @@ class UI { for (const customPath of paths) { const expandedPath = this.expandUserPath(customPath); const validation = this.validateCustomContentPathSync(expandedPath); - if (validation !== true) { + if (validation) { console.log(chalk.yellow(`⚠️ Skipping invalid custom content path: ${customPath} - ${validation}`)); continue; } @@ -405,6 +405,10 @@ class UI { .map((m) => m.trim()) .filter(Boolean); console.log(chalk.cyan('Using modules from command-line:'), chalk.bold(selectedModules.join(', '))); + } else if (options.yes) { + // Use default modules when --yes flag is set + selectedModules = await this.getDefaultModules(installedModuleIds); + console.log(chalk.cyan('Using default modules (--yes flag):'), chalk.bold(selectedModules.join(', '))); } else { selectedModules = await this.selectAllModules(installedModuleIds); } @@ -425,7 +429,7 @@ class UI { for (const customPath of paths) { const expandedPath = this.expandUserPath(customPath); const validation = this.validateCustomContentPathSync(expandedPath); - if (validation !== true) { + if (validation) { console.log(chalk.yellow(`⚠️ Skipping invalid custom content path: ${customPath} - ${validation}`)); continue; } @@ -487,6 +491,7 @@ class UI { skipIde: toolSelection.skipIde, coreConfig: coreConfig, customContent: customContentConfig, + skipPrompts: options.yes || false, }; } @@ -1124,6 +1129,33 @@ class UI { return selected ? selected.filter((m) => m !== '__NONE__') : []; } + /** + * Get default modules for non-interactive mode + * @param {Set} installedModuleIds - Already installed module IDs + * @returns {Array} Default module codes + */ + async getDefaultModules(installedModuleIds = new Set()) { + const { ModuleManager } = require('../installers/lib/modules/manager'); + const moduleManager = new ModuleManager(); + const { modules: localModules } = await moduleManager.listAvailable(); + + const defaultModules = []; + + // Add default-selected local modules (typically BMM) + for (const mod of localModules) { + if (mod.defaultSelected === true || installedModuleIds.has(mod.id)) { + defaultModules.push(mod.id); + } + } + + // If no defaults found, use 'bmm' as the fallback default + if (defaultModules.length === 0) { + defaultModules.push('bmm'); + } + + return defaultModules; + } + /** * Prompt for directory selection * @returns {Object} Directory answer from prompt