fix: guard remaining unguarded prompts with skipPrompts for non-interactive mode
Add skipPrompts guards to 4 remaining unguarded interactive prompts in
installer.js that caused hangs in non-interactive mode (--yes flag):
- Module removal confirmation: preserves modules by default
- Update action selection: defaults to 'update'
- Custom module missing sources: keeps all modules
- Custom module delete confirmation: unreachable via early return
Additional robustness fixes:
- Defensive type-check before .trim() on prompt result (symbol guard)
- Console.log suppression scoped per-IDE instead of global try/finally
- process.exit flush via setImmediate for legacy v4 exit path
- JSDoc updated for new skipPrompts parameter
Follows established pattern from commit f967fdde (IDE skipPrompts guards).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ef0c911722
commit
17017881fd
|
|
@ -415,6 +415,9 @@ class Installer {
|
||||||
let action = null;
|
let action = null;
|
||||||
if (config.actionType === 'update') {
|
if (config.actionType === 'update') {
|
||||||
action = 'update';
|
action = 'update';
|
||||||
|
} else if (config.skipPrompts) {
|
||||||
|
// Non-interactive mode: default to update
|
||||||
|
action = 'update';
|
||||||
} else {
|
} else {
|
||||||
// Fallback: Ask the user (backwards compatibility for other code paths)
|
// Fallback: Ask the user (backwards compatibility for other code paths)
|
||||||
await prompts.log.warn('Existing BMAD installation detected');
|
await prompts.log.warn('Existing BMAD installation detected');
|
||||||
|
|
@ -440,6 +443,14 @@ class Installer {
|
||||||
|
|
||||||
// If there are modules to remove, ask for confirmation
|
// If there are modules to remove, ask for confirmation
|
||||||
if (modulesToRemove.length > 0) {
|
if (modulesToRemove.length > 0) {
|
||||||
|
if (config.skipPrompts) {
|
||||||
|
// Non-interactive mode: preserve modules (matches prompt default: false)
|
||||||
|
for (const moduleId of modulesToRemove) {
|
||||||
|
if (!config.modules) config.modules = [];
|
||||||
|
config.modules.push(moduleId);
|
||||||
|
}
|
||||||
|
spinner.start('Preparing update...');
|
||||||
|
} else {
|
||||||
const prompts = require('../../../lib/prompts');
|
const prompts = require('../../../lib/prompts');
|
||||||
if (spinner.isSpinning) {
|
if (spinner.isSpinning) {
|
||||||
spinner.stop('Reviewing module changes');
|
spinner.stop('Reviewing module changes');
|
||||||
|
|
@ -483,6 +494,7 @@ class Installer {
|
||||||
|
|
||||||
spinner.start('Preparing update...');
|
spinner.start('Preparing update...');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Detect custom and modified files BEFORE updating (compare current files vs files-manifest.csv)
|
// Detect custom and modified files BEFORE updating (compare current files vs files-manifest.csv)
|
||||||
const existingFilesManifest = await this.readFilesManifest(bmadDir);
|
const existingFilesManifest = await this.readFilesManifest(bmadDir);
|
||||||
|
|
@ -1085,13 +1097,6 @@ class Installer {
|
||||||
// Check if any IDE might need prompting (no pre-collected config)
|
// Check if any IDE might need prompting (no pre-collected config)
|
||||||
const needsPrompting = validIdes.some((ide) => !ideConfigurations[ide]);
|
const needsPrompting = validIdes.some((ide) => !ideConfigurations[ide]);
|
||||||
|
|
||||||
// Temporarily suppress console output if not verbose
|
|
||||||
const originalLog = console.log;
|
|
||||||
if (!config.verbose) {
|
|
||||||
console.log = () => {};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (const ide of validIdes) {
|
for (const ide of validIdes) {
|
||||||
if (!needsPrompting || ideConfigurations[ide]) {
|
if (!needsPrompting || ideConfigurations[ide]) {
|
||||||
// All IDEs pre-configured, or this specific IDE has config: keep spinner running
|
// All IDEs pre-configured, or this specific IDE has config: keep spinner running
|
||||||
|
|
@ -1105,6 +1110,13 @@ class Installer {
|
||||||
|
|
||||||
// Silent when this IDE has pre-collected config (no prompts for THIS IDE)
|
// Silent when this IDE has pre-collected config (no prompts for THIS IDE)
|
||||||
const ideHasConfig = Boolean(ideConfigurations[ide]);
|
const ideHasConfig = Boolean(ideConfigurations[ide]);
|
||||||
|
|
||||||
|
// Suppress stray console output for pre-configured IDEs (no user interaction)
|
||||||
|
const originalLog = console.log;
|
||||||
|
if (!config.verbose && ideHasConfig) {
|
||||||
|
console.log = () => {};
|
||||||
|
}
|
||||||
|
try {
|
||||||
const setupResult = await this.ideManager.setup(ide, projectDir, bmadDir, {
|
const setupResult = await this.ideManager.setup(ide, projectDir, bmadDir, {
|
||||||
selectedModules: allModules || [],
|
selectedModules: allModules || [],
|
||||||
preCollectedConfig: ideConfigurations[ide] || null,
|
preCollectedConfig: ideConfigurations[ide] || null,
|
||||||
|
|
@ -1123,15 +1135,15 @@ class Installer {
|
||||||
} else {
|
} else {
|
||||||
addResult(ide, 'error', setupResult.error || 'failed');
|
addResult(ide, 'error', setupResult.error || 'failed');
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
console.log = originalLog;
|
||||||
|
}
|
||||||
|
|
||||||
// Restart spinner if we stopped it for prompting
|
// Restart spinner if we stopped it for prompting
|
||||||
if (needsPrompting && !spinner.isSpinning) {
|
if (needsPrompting && !spinner.isSpinning) {
|
||||||
spinner.start('Configuring IDEs...');
|
spinner.start('Configuring IDEs...');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
console.log = originalLog;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1396,6 +1408,7 @@ class Installer {
|
||||||
projectRoot,
|
projectRoot,
|
||||||
'update',
|
'update',
|
||||||
existingInstall.modules.map((m) => m.id),
|
existingInstall.modules.map((m) => m.id),
|
||||||
|
config.skipPrompts || false,
|
||||||
);
|
);
|
||||||
|
|
||||||
spinner.start('Preparing update...');
|
spinner.start('Preparing update...');
|
||||||
|
|
@ -2259,6 +2272,7 @@ class Installer {
|
||||||
projectRoot,
|
projectRoot,
|
||||||
'update',
|
'update',
|
||||||
installedModules,
|
installedModules,
|
||||||
|
config.skipPrompts || false,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { validCustomModules, keptModulesWithoutSources } = customModuleResult;
|
const { validCustomModules, keptModulesWithoutSources } = customModuleResult;
|
||||||
|
|
@ -2516,7 +2530,9 @@ class Installer {
|
||||||
|
|
||||||
if (proceed === 'exit') {
|
if (proceed === 'exit') {
|
||||||
await prompts.log.info('Please remove the .bmad-method folder and any v4 rules/commands, then run the installer again.');
|
await prompts.log.info('Please remove the .bmad-method folder and any v4 rules/commands, then run the installer again.');
|
||||||
process.exit(0);
|
// Allow event loop to flush pending I/O before exit
|
||||||
|
setImmediate(() => process.exit(0));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await prompts.log.warn('Proceeding with installation despite legacy v4 folder');
|
await prompts.log.warn('Proceeding with installation despite legacy v4 folder');
|
||||||
|
|
@ -2700,9 +2716,10 @@ class Installer {
|
||||||
* @param {string} projectRoot - Project root directory
|
* @param {string} projectRoot - Project root directory
|
||||||
* @param {string} operation - Current operation ('update', 'compile', etc.)
|
* @param {string} operation - Current operation ('update', 'compile', etc.)
|
||||||
* @param {Array} installedModules - Array of installed module IDs (will be modified)
|
* @param {Array} installedModules - Array of installed module IDs (will be modified)
|
||||||
|
* @param {boolean} [skipPrompts=false] - Skip interactive prompts and keep all modules with missing sources
|
||||||
* @returns {Object} Object with validCustomModules array and keptModulesWithoutSources array
|
* @returns {Object} Object with validCustomModules array and keptModulesWithoutSources array
|
||||||
*/
|
*/
|
||||||
async handleMissingCustomSources(customModuleSources, bmadDir, projectRoot, operation, installedModules) {
|
async handleMissingCustomSources(customModuleSources, bmadDir, projectRoot, operation, installedModules, skipPrompts = false) {
|
||||||
const validCustomModules = [];
|
const validCustomModules = [];
|
||||||
const keptModulesWithoutSources = []; // Track modules kept without sources
|
const keptModulesWithoutSources = []; // Track modules kept without sources
|
||||||
const customModulesWithMissingSources = [];
|
const customModulesWithMissingSources = [];
|
||||||
|
|
@ -2745,6 +2762,14 @@ class Installer {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Non-interactive mode: keep all modules with missing sources
|
||||||
|
if (skipPrompts) {
|
||||||
|
for (const missing of customModulesWithMissingSources) {
|
||||||
|
keptModulesWithoutSources.push(missing.id);
|
||||||
|
}
|
||||||
|
return { validCustomModules, keptModulesWithoutSources };
|
||||||
|
}
|
||||||
|
|
||||||
await prompts.log.warn(`Found ${customModulesWithMissingSources.length} custom module(s) with missing sources:`);
|
await prompts.log.warn(`Found ${customModulesWithMissingSources.length} custom module(s) with missing sources:`);
|
||||||
|
|
||||||
let keptCount = 0;
|
let keptCount = 0;
|
||||||
|
|
@ -2809,6 +2834,13 @@ class Installer {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Defensive: handleCancel should have exited, but guard against symbol propagation
|
||||||
|
if (typeof newSourcePath !== 'string') {
|
||||||
|
keptCount++;
|
||||||
|
keptModulesWithoutSources.push(missing.id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Update the source in manifest
|
// Update the source in manifest
|
||||||
const resolvedPath = path.resolve(newSourcePath.trim());
|
const resolvedPath = path.resolve(newSourcePath.trim());
|
||||||
missing.info.sourcePath = resolvedPath;
|
missing.info.sourcePath = resolvedPath;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue