fix: complete non-interactive installation support

- Fix validation checks using truthy instead of !== true
- Add skipPrompts flag to skip module config prompts with --yes
- Add getDefaultModules() for automatic module selection with --yes
- Fix IDE selection to use array check instead of length check

Co-Authored-By: AiderDesk <https://github.com/hotovo/aider-desk>
This commit is contained in:
wladimiiir 2026-02-04 22:37:20 +01:00
parent 8688e3eb13
commit 4aec04437e
3 changed files with 90 additions and 40 deletions

View File

@ -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);
}
}

View File

@ -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,

View File

@ -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