Modify installation now will remove modules that get unselected, with an option to confirm the deletion

This commit is contained in:
Brian Madison
2026-01-15 22:09:23 -06:00
parent 577c1aa218
commit b952d28fb3
11 changed files with 115 additions and 12 deletions

View File

@@ -0,0 +1 @@
# This file ensures the directory is tracked by git

View File

@@ -0,0 +1 @@
# This file ensures the directory is tracked by git

View File

@@ -0,0 +1 @@
# This file ensures the directory is tracked by git

View File

@@ -0,0 +1 @@
# This file ensures the directory is tracked by git

View File

@@ -0,0 +1 @@
# This file ensures the directory is tracked by git

1
docs/E-PRD/.gitkeep Normal file
View File

@@ -0,0 +1 @@
# This file ensures the directory is tracked by git

View File

@@ -0,0 +1 @@
# This file ensures the directory is tracked by git

1
docs/F-Testing/.gitkeep Normal file
View File

@@ -0,0 +1 @@
# This file ensures the directory is tracked by git

View File

@@ -0,0 +1 @@
# This file ensures the directory is tracked by git

View File

@@ -444,6 +444,60 @@ class Installer {
config._isUpdate = true;
config._existingInstall = existingInstall;
// Detect modules that were previously installed but are NOT in the new selection (to be removed)
const previouslyInstalledModules = new Set(existingInstall.modules.map((m) => m.id));
const newlySelectedModules = new Set(config.modules || []);
// Find modules to remove (installed but not in new selection)
// Exclude 'core' from being removable
const modulesToRemove = [...previouslyInstalledModules].filter((m) => !newlySelectedModules.has(m) && m !== 'core');
// If there are modules to remove, ask for confirmation
if (modulesToRemove.length > 0) {
const prompts = require('../../../lib/prompts');
spinner.stop();
console.log('');
console.log(chalk.yellow.bold('⚠️ Modules to be removed:'));
for (const moduleId of modulesToRemove) {
const moduleInfo = existingInstall.modules.find((m) => m.id === moduleId);
const displayName = moduleInfo?.name || moduleId;
const modulePath = path.join(bmadDir, moduleId);
console.log(chalk.red(` - ${displayName} (${modulePath})`));
}
console.log('');
const confirmRemoval = await prompts.confirm({
message: `Remove ${modulesToRemove.length} module(s) from BMAD installation?`,
default: false,
});
if (confirmRemoval) {
// Remove module folders
for (const moduleId of modulesToRemove) {
const modulePath = path.join(bmadDir, moduleId);
try {
if (await fs.pathExists(modulePath)) {
await fs.remove(modulePath);
console.log(chalk.dim(` ✓ Removed: ${moduleId}`));
}
} catch (error) {
console.warn(chalk.yellow(` Warning: Failed to remove ${moduleId}: ${error.message}`));
}
}
console.log(chalk.green(` ✓ Removed ${modulesToRemove.length} module(s)`));
} else {
console.log(chalk.dim(' → Module removal cancelled'));
// Add the modules back to the selection since user cancelled removal
for (const moduleId of modulesToRemove) {
if (!config.modules) config.modules = [];
config.modules.push(moduleId);
}
}
spinner.start('Preparing update...');
}
// Detect custom and modified files BEFORE updating (compare current files vs files-manifest.csv)
const existingFilesManifest = await this.readFilesManifest(bmadDir);
const { customFiles, modifiedFiles } = await this.detectCustomFiles(bmadDir, existingFilesManifest);

View File

@@ -252,19 +252,52 @@ class UI {
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
console.log(chalk.dim(` Found existing modules: ${[...installedModuleIds].join(', ')}`));
const changeModuleSelection = await prompts.confirm({
message: 'Modify official module selection (BMad Method, BMad Builder)?',
// Ask about BMad Method Module (bmm)
const wantsBmm = await prompts.confirm({
message:
'Select the BMad Method Module for installation?\n ---> This is the Full BMad Method Agile AI Driven Development Framework Including BMad Quick Flow',
default: installedModuleIds.has('bmm'),
});
// Ask about BMad Builder Module (bmb)
const wantsBmb = await prompts.confirm({
message: 'Select the BMad Builder Module for installation?\n ---> Create Your Own Custom BMad Agents, Workflows and Modules',
default: installedModuleIds.has('bmb'),
});
let selectedOfficialModules = [];
if (wantsBmm) {
selectedOfficialModules.push('bmm');
}
if (wantsBmb) {
selectedOfficialModules.push('bmb');
}
// Ask about other external modules
// Check if any external modules are already installed (not bmm, bmb, or core)
const installedExternalModules = [...installedModuleIds].filter((id) => !['bmm', 'bmb', 'core'].includes(id));
let selectedExternalModules = [];
// If external modules are already installed, skip confirm and go straight to selection
// Otherwise ask if they want to choose external modules
if (installedExternalModules.length > 0) {
const externalModuleChoices = await this.getExternalModuleChoices();
selectedExternalModules = await this.selectExternalModules(externalModuleChoices, installedExternalModules);
} else {
const wantsExternalModules = await prompts.confirm({
message: 'Would you like to choose any other Recommended BMad Core Modules for installation?',
default: false,
});
let selectedModules = [];
if (changeModuleSelection) {
// Show module selection with existing modules pre-selected
const moduleChoices = await this.getModuleChoices(new Set(installedModuleIds), { hasCustomContent: false });
selectedModules = await this.selectModules(moduleChoices, [...installedModuleIds]);
} else {
selectedModules = [...installedModuleIds];
if (wantsExternalModules) {
const externalModuleChoices = await this.getExternalModuleChoices();
selectedExternalModules = await this.selectExternalModules(externalModuleChoices, []);
}
}
// Combine official and external modules
let selectedModules = [...selectedOfficialModules, ...selectedExternalModules];
// After module selection, ask about custom modules
console.log('');
@@ -744,16 +777,23 @@ class UI {
/**
* Prompt for external module selection
* @param {Array} externalModuleChoices - Available external module choices
* @param {Array} defaultSelections - Module codes to pre-select
* @returns {Array} Selected external module codes
*/
async selectExternalModules(externalModuleChoices) {
async selectExternalModules(externalModuleChoices, defaultSelections = []) {
// Build a message showing available modules
const availableNames = externalModuleChoices.map((c) => c.name).join(', ');
const message = `Select official BMad modules to install ${availableNames ? chalk.dim(`(${availableNames})`) : ''} ${chalk.dim('(↑/↓ navigates multiselect, SPACE toggles, A to toggles All, ENTER confirm)')}:`;
// Mark choices as checked based on defaultSelections
const choicesWithDefaults = externalModuleChoices.map((choice) => ({
...choice,
checked: defaultSelections.includes(choice.value),
}));
// Add a "None" option at the end for users who changed their mind
const choicesWithSkipOption = [
...externalModuleChoices,
...choicesWithDefaults,
{
name: '⚠ None / I changed my mind - skip external module installation',
value: '__NONE__',